[BACK]Return to ldapclient.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / ldap

Annotation of src/usr.bin/ldap/ldapclient.c, Revision 1.9

1.9     ! martijn     1: /*     $OpenBSD: ldapclient.c,v 1.8 2018/11/06 20:38:42 jsing Exp $    */
1.1       reyk        2:
                      3: /*
                      4:  * Copyright (c) 2018 Reyk Floeter <reyk@openbsd.org>
                      5:  *
                      6:  * Permission to use, copy, modify, and distribute this software for any
                      7:  * purpose with or without fee is hereby granted, provided that the above
                      8:  * copyright notice and this permission notice appear in all copies.
                      9:  *
                     10:  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
                     11:  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
                     12:  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
                     13:  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
                     14:  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
                     15:  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
                     16:  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
                     17:  */
                     18:
                     19: #include <sys/param.h>
                     20: #include <sys/queue.h>
                     21: #include <sys/socket.h>
1.2       reyk       22: #include <sys/stat.h>
1.1       reyk       23: #include <sys/tree.h>
                     24: #include <sys/un.h>
                     25:
                     26: #include <netinet/in.h>
                     27: #include <arpa/inet.h>
                     28:
                     29: #include <stdio.h>
                     30: #include <stdlib.h>
                     31: #include <stdint.h>
                     32: #include <unistd.h>
                     33: #include <ctype.h>
                     34: #include <err.h>
                     35: #include <errno.h>
                     36: #include <event.h>
                     37: #include <fcntl.h>
                     38: #include <limits.h>
                     39: #include <netdb.h>
                     40: #include <pwd.h>
                     41: #include <readpassphrase.h>
                     42: #include <resolv.h>
                     43: #include <signal.h>
                     44: #include <string.h>
                     45: #include <vis.h>
                     46:
                     47: #include "aldap.h"
                     48: #include "log.h"
                     49:
                     50: #define F_STARTTLS     0x01
                     51: #define F_TLS          0x02
                     52: #define F_NEEDAUTH     0x04
                     53: #define F_LDIF         0x08
                     54:
                     55: #define LDAPHOST       "localhost"
                     56: #define LDAPFILTER     "(objectClass=*)"
                     57: #define LDIF_LINELENGTH        79
1.2       reyk       58: #define LDAPPASSMAX    1024
1.1       reyk       59:
                     60: struct ldapc {
                     61:        struct aldap            *ldap_al;
                     62:        char                    *ldap_host;
                     63:        int                      ldap_port;
                     64:        char                    *ldap_capath;
                     65:        char                    *ldap_binddn;
                     66:        char                    *ldap_secret;
                     67:        unsigned int             ldap_flags;
                     68:        enum protocol_op         ldap_req;
                     69:        enum aldap_protocol      ldap_protocol;
                     70:        struct aldap_url         ldap_url;
                     71: };
                     72:
                     73: struct ldapc_search {
                     74:        int                      ls_sizelimit;
                     75:        int                      ls_timelimit;
                     76:        char                    *ls_basedn;
                     77:        char                    *ls_filter;
                     78:        int                      ls_scope;
                     79:        char                    **ls_attr;
                     80: };
                     81:
                     82: __dead void     usage(void);
                     83: int             ldapc_connect(struct ldapc *);
                     84: int             ldapc_search(struct ldapc *, struct ldapc_search *);
                     85: int             ldapc_printattr(struct ldapc *, const char *, const char *);
                     86: void            ldapc_disconnect(struct ldapc *);
                     87: int             ldapc_parseurl(struct ldapc *, struct ldapc_search *,
                     88:                    const char *);
                     89: const char     *ldapc_resultcode(enum result_code);
                     90: const char     *url_decode(char *);
                     91:
                     92: __dead void
                     93: usage(void)
                     94: {
                     95:        extern char     *__progname;
                     96:
                     97:        fprintf(stderr,
1.3       jmc        98: "usage: %s search [-LvWxZ] [-b basedn] [-c CAfile] [-D binddn] [-H host]\n"
                     99: "          [-l timelimit] [-s scope] [-w secret] [-y secretfile] [-z sizelimit]\n"
                    100: "          [filter] [attributes ...]\n",
1.1       reyk      101:            __progname);
                    102:
                    103:        exit(1);
                    104: }
                    105:
                    106: int
                    107: main(int argc, char *argv[])
                    108: {
1.2       reyk      109:        char                     passbuf[LDAPPASSMAX];
                    110:        const char              *errstr, *url = NULL, *secretfile = NULL;
                    111:        struct stat              st;
1.1       reyk      112:        struct ldapc             ldap;
                    113:        struct ldapc_search      ls;
                    114:        int                      ch;
                    115:        int                      verbose = 1;
1.2       reyk      116:        FILE                    *fp;
1.1       reyk      117:
                    118:        if (pledge("stdio inet unix tty rpath dns", NULL) == -1)
                    119:                err(1, "pledge");
                    120:
                    121:        log_init(verbose, 0);
                    122:
                    123:        memset(&ldap, 0, sizeof(ldap));
                    124:        memset(&ls, 0, sizeof(ls));
                    125:        ls.ls_scope = -1;
                    126:        ldap.ldap_port = -1;
                    127:
                    128:        /*
                    129:         * Check the command.  Currently only "search" is supported but
                    130:         * it could be extended with others such as add, modify, or delete.
                    131:         */
                    132:        if (argc < 2)
                    133:                usage();
                    134:        else if (strcmp("search", argv[1]) == 0)
                    135:                ldap.ldap_req = LDAP_REQ_SEARCH;
                    136:        else
                    137:                usage();
                    138:        argc--;
                    139:        argv++;
                    140:
1.2       reyk      141:        while ((ch = getopt(argc, argv, "b:c:D:H:Ll:s:vWw:xy:Zz:")) != -1) {
1.1       reyk      142:                switch (ch) {
                    143:                case 'b':
                    144:                        ls.ls_basedn = optarg;
                    145:                        break;
                    146:                case 'c':
                    147:                        ldap.ldap_capath = optarg;
                    148:                        break;
                    149:                case 'D':
                    150:                        ldap.ldap_binddn = optarg;
                    151:                        ldap.ldap_flags |= F_NEEDAUTH;
                    152:                        break;
                    153:                case 'H':
                    154:                        url = optarg;
                    155:                        break;
                    156:                case 'L':
                    157:                        ldap.ldap_flags |= F_LDIF;
                    158:                        break;
                    159:                case 'l':
                    160:                        ls.ls_timelimit = strtonum(optarg, 0, INT_MAX,
                    161:                            &errstr);
                    162:                        if (errstr != NULL)
                    163:                                errx(1, "timelimit %s", errstr);
                    164:                        break;
                    165:                case 's':
                    166:                        if (strcasecmp("base", optarg) == 0)
                    167:                                ls.ls_scope = LDAP_SCOPE_BASE;
                    168:                        else if (strcasecmp("one", optarg) == 0)
                    169:                                ls.ls_scope = LDAP_SCOPE_ONELEVEL;
                    170:                        else if (strcasecmp("sub", optarg) == 0)
                    171:                                ls.ls_scope = LDAP_SCOPE_SUBTREE;
                    172:                        else
                    173:                                errx(1, "invalid scope: %s", optarg);
                    174:                        break;
                    175:                case 'v':
                    176:                        verbose++;
                    177:                        break;
                    178:                case 'w':
                    179:                        ldap.ldap_secret = optarg;
                    180:                        ldap.ldap_flags |= F_NEEDAUTH;
                    181:                        break;
                    182:                case 'W':
                    183:                        ldap.ldap_flags |= F_NEEDAUTH;
                    184:                        break;
                    185:                case 'x':
                    186:                        /* provided for compatibility */
                    187:                        break;
1.2       reyk      188:                case 'y':
                    189:                        secretfile = optarg;
                    190:                        ldap.ldap_flags |= F_NEEDAUTH;
                    191:                        break;
1.1       reyk      192:                case 'Z':
                    193:                        ldap.ldap_flags |= F_STARTTLS;
                    194:                        break;
                    195:                case 'z':
                    196:                        ls.ls_sizelimit = strtonum(optarg, 0, INT_MAX,
                    197:                            &errstr);
                    198:                        if (errstr != NULL)
                    199:                                errx(1, "sizelimit %s", errstr);
                    200:                        break;
                    201:                default:
                    202:                        usage();
                    203:                }
                    204:        }
                    205:        argc -= optind;
                    206:        argv += optind;
                    207:
                    208:        log_setverbose(verbose);
                    209:
                    210:        if (url != NULL && ldapc_parseurl(&ldap, &ls, url) == -1)
                    211:                errx(1, "ldapurl");
                    212:
                    213:        /* Set the default after parsing URL and/or options */
                    214:        if (ldap.ldap_host == NULL)
                    215:                ldap.ldap_host = LDAPHOST;
                    216:        if (ldap.ldap_port == -1)
                    217:                ldap.ldap_port = ldap.ldap_protocol == LDAPS ?
                    218:                    LDAPS_PORT : LDAP_PORT;
                    219:        if (ldap.ldap_protocol == LDAP && (ldap.ldap_flags & F_STARTTLS))
                    220:                ldap.ldap_protocol = LDAPTLS;
                    221:        if (ldap.ldap_capath == NULL)
1.8       jsing     222:                ldap.ldap_capath = TLS_CA_CERT_FILE;
1.1       reyk      223:        if (ls.ls_basedn == NULL)
                    224:                ls.ls_basedn = "";
                    225:        if (ls.ls_scope == -1)
                    226:                ls.ls_scope = LDAP_SCOPE_SUBTREE;
                    227:        if (ls.ls_filter == NULL)
                    228:                ls.ls_filter = LDAPFILTER;
                    229:
                    230:        if (ldap.ldap_flags & F_NEEDAUTH) {
                    231:                if (ldap.ldap_binddn == NULL) {
                    232:                        log_warnx("missing -D binddn");
                    233:                        usage();
1.2       reyk      234:                }
                    235:                if (secretfile != NULL) {
                    236:                        if (ldap.ldap_secret != NULL)
                    237:                                errx(1, "conflicting -w/-y options");
                    238:
                    239:                        /* read password from stdin or file (first line) */
                    240:                        if (strcmp(secretfile, "-") == 0)
                    241:                                fp = stdin;
                    242:                        else if (stat(secretfile, &st) == -1)
                    243:                                err(1, "failed to access %s", secretfile);
                    244:                        else if (S_ISREG(st.st_mode) && (st.st_mode & S_IROTH))
                    245:                                errx(1, "%s is world-readable", secretfile);
                    246:                        else if ((fp = fopen(secretfile, "r")) == NULL)
                    247:                                err(1, "failed to open %s", secretfile);
                    248:                        if (fgets(passbuf, sizeof(passbuf), fp) == NULL)
                    249:                                err(1, "failed to read %s", secretfile);
                    250:                        if (fp != stdin)
                    251:                                fclose(fp);
                    252:
                    253:                        passbuf[strcspn(passbuf, "\n")] = '\0';
                    254:                        ldap.ldap_secret = passbuf;
1.1       reyk      255:                }
                    256:                if (ldap.ldap_secret == NULL) {
                    257:                        if (readpassphrase("Password: ",
                    258:                            passbuf, sizeof(passbuf), RPP_REQUIRE_TTY) == NULL)
                    259:                                errx(1, "failed to read LDAP password");
                    260:                        ldap.ldap_secret = passbuf;
                    261:                }
                    262:        }
                    263:
                    264:        if (pledge("stdio inet unix rpath dns", NULL) == -1)
                    265:                err(1, "pledge");
                    266:
                    267:        /* optional search filter */
                    268:        if (argc && strchr(argv[0], '=') != NULL) {
                    269:                ls.ls_filter = argv[0];
                    270:                argc--;
                    271:                argv++;
                    272:        }
                    273:        /* search attributes */
                    274:        if (argc)
                    275:                ls.ls_attr = argv;
                    276:
                    277:        if (ldapc_connect(&ldap) == -1)
                    278:                errx(1, "LDAP connection failed");
                    279:
                    280:        if (pledge("stdio", NULL) == -1)
                    281:                err(1, "pledge");
                    282:
                    283:        if (ldapc_search(&ldap, &ls) == -1)
                    284:                errx(1, "LDAP search failed");
                    285:
                    286:        ldapc_disconnect(&ldap);
                    287:        aldap_free_url(&ldap.ldap_url);
                    288:
                    289:        return (0);
                    290: }
                    291:
                    292: int
                    293: ldapc_search(struct ldapc *ldap, struct ldapc_search *ls)
                    294: {
                    295:        struct aldap_page_control       *pg = NULL;
                    296:        struct aldap_message            *m;
                    297:        const char                      *errstr;
                    298:        const char                      *searchdn, *dn = NULL;
                    299:        char                            *outkey;
                    300:        char                            **outvalues;
                    301:        int                              ret, i, code, fail = 0;
                    302:
1.5       martijn   303:        if (ldap->ldap_flags & F_LDIF)
                    304:                printf("version: 1\n");
1.1       reyk      305:        do {
                    306:                if (aldap_search(ldap->ldap_al, ls->ls_basedn, ls->ls_scope,
                    307:                    ls->ls_filter, ls->ls_attr, 0, ls->ls_sizelimit,
                    308:                    ls->ls_timelimit, pg) == -1) {
                    309:                        aldap_get_errno(ldap->ldap_al, &errstr);
                    310:                        log_warnx("LDAP search failed: %s", errstr);
                    311:                        return (-1);
                    312:                }
                    313:
                    314:                if (pg != NULL) {
                    315:                        aldap_freepage(pg);
                    316:                        pg = NULL;
                    317:                }
                    318:
                    319:                while ((m = aldap_parse(ldap->ldap_al)) != NULL) {
                    320:                        if (ldap->ldap_al->msgid != m->msgid) {
                    321:                                goto fail;
                    322:                        }
                    323:
                    324:                        if ((code = aldap_get_resultcode(m)) != LDAP_SUCCESS) {
                    325:                                log_warnx("LDAP search failed: %s(%d)",
                    326:                                    ldapc_resultcode(code), code);
                    327:                                break;
                    328:                        }
                    329:
                    330:                        if (m->message_type == LDAP_RES_SEARCH_RESULT) {
                    331:                                if (m->page != NULL && m->page->cookie_len != 0)
                    332:                                        pg = m->page;
                    333:                                else
                    334:                                        pg = NULL;
                    335:
                    336:                                aldap_freemsg(m);
                    337:                                break;
                    338:                        }
                    339:
                    340:                        if (m->message_type != LDAP_RES_SEARCH_ENTRY) {
                    341:                                goto fail;
                    342:                        }
                    343:
                    344:                        if (aldap_count_attrs(m) < 1) {
                    345:                                aldap_freemsg(m);
                    346:                                continue;
                    347:                        }
                    348:
                    349:                        if ((searchdn = aldap_get_dn(m)) == NULL)
                    350:                                goto fail;
                    351:
                    352:                        if (dn != NULL)
                    353:                                printf("\n");
                    354:                        else
                    355:                                dn = ls->ls_basedn;
                    356:                        if (strcmp(dn, searchdn) != 0)
                    357:                                printf("dn: %s\n", searchdn);
                    358:
                    359:                        for (ret = aldap_first_attr(m, &outkey, &outvalues);
                    360:                            ret != -1;
                    361:                            ret = aldap_next_attr(m, &outkey, &outvalues)) {
                    362:                                for (i = 0; outvalues != NULL &&
                    363:                                    outvalues[i] != NULL; i++) {
                    364:                                        if (ldapc_printattr(ldap, outkey,
                    365:                                            outvalues[i]) == -1) {
                    366:                                                fail = 1;
                    367:                                                break;
                    368:                                        }
                    369:                                }
                    370:                        }
                    371:                        free(outkey);
                    372:                        aldap_free_attr(outvalues);
                    373:
                    374:                        aldap_freemsg(m);
                    375:                }
                    376:        } while (pg != NULL && fail == 0);
                    377:
                    378:        if (fail)
                    379:                return (-1);
                    380:        return (0);
                    381:  fail:
                    382:        ldapc_disconnect(ldap);
                    383:        return (-1);
                    384: }
                    385:
                    386: int
                    387: ldapc_printattr(struct ldapc *ldap, const char *key, const char *value)
                    388: {
                    389:        char                    *p = NULL, *out;
                    390:        const unsigned char     *cp;
                    391:        int                      encode;
                    392:        size_t                   inlen, outlen, left;
                    393:
                    394:        if (ldap->ldap_flags & F_LDIF) {
                    395:                /* OpenLDAP encodes the userPassword by default */
                    396:                if (strcasecmp("userPassword", key) == 0)
                    397:                        encode = 1;
                    398:                else
                    399:                        encode = 0;
                    400:
                    401:                /*
                    402:                 * The LDIF format a set of characters that can be included
                    403:                 * in SAFE-STRINGs. String value that do not match the
                    404:                 * criteria must be encoded as Base64.
                    405:                 */
1.6       martijn   406:                cp = (const unsigned char *)value;
                    407:                /* !SAFE-INIT-CHAR: SAFE-CHAR minus %x20 %x3A %x3C */
                    408:                if (*cp == ' ' ||
                    409:                    *cp == ':' ||
                    410:                    *cp == '<')
                    411:                        encode = 1;
                    412:                for (; encode == 0 &&*cp != '\0'; cp++) {
1.1       reyk      413:                        /* !SAFE-CHAR %x01-09 / %x0B-0C / %x0E-7F */
                    414:                        if (*cp > 127 ||
                    415:                            *cp == '\0' ||
                    416:                            *cp == '\n' ||
                    417:                            *cp == '\r')
                    418:                                encode = 1;
                    419:                }
                    420:
                    421:                if (!encode) {
                    422:                        if (asprintf(&p, "%s: %s", key, value) == -1) {
                    423:                                log_warnx("asprintf");
                    424:                                return (-1);
                    425:                        }
                    426:                } else {
                    427:                        inlen = strlen(value);
1.7       martijn   428:                        outlen = (((inlen + 2) / 3) * 4) + 1;
1.1       reyk      429:
                    430:                        if ((out = calloc(1, outlen)) == NULL ||
                    431:                            b64_ntop(value, inlen, out, outlen) == -1) {
                    432:                                log_warnx("Base64 encoding failed");
                    433:                                free(p);
                    434:                                return (-1);
                    435:                        }
                    436:
                    437:                        /* Base64 is indicated with a double-colon */
1.9     ! martijn   438:                        if (asprintf(&p, "%s:: %s", key, out) == -1) {
1.1       reyk      439:                                log_warnx("asprintf");
                    440:                                free(out);
                    441:                                return (-1);
                    442:                        }
                    443:                        free(out);
                    444:                }
                    445:
                    446:                /* Wrap lines */
                    447:                for (outlen = 0, inlen = strlen(p);
                    448:                    outlen < inlen;
1.4       martijn   449:                    outlen += LDIF_LINELENGTH - 1) {
1.1       reyk      450:                        if (outlen)
                    451:                                putchar(' ');
1.4       martijn   452:                        if (outlen > LDIF_LINELENGTH)
                    453:                                outlen--;
1.1       reyk      454:                        /* max. line length - newline - optional indent */
                    455:                        left = MIN(inlen - outlen, outlen ?
                    456:                            LDIF_LINELENGTH - 2 :
                    457:                            LDIF_LINELENGTH - 1);
                    458:                        fwrite(p + outlen, left, 1, stdout);
                    459:                        putchar('\n');
                    460:                }
                    461:        } else {
                    462:                /*
                    463:                 * Use vis(1) instead of base64 encoding of non-printable
                    464:                 * values.  This is much nicer as it always prdocues a
                    465:                 * human-readable visual output.  This can safely be done
                    466:                 * on all values no matter if they include non-printable
                    467:                 * characters.
                    468:                 */
                    469:                if (stravis(&p, value, VIS_SAFE|VIS_NL) == -1) {
                    470:                        log_warn("visual encoding failed");
                    471:                        return (-1);
                    472:                }
                    473:
                    474:                printf("%s: %s\n", key, p);
                    475:        }
                    476:
                    477:        free(p);
                    478:        return (0);
                    479: }
                    480:
                    481: int
                    482: ldapc_connect(struct ldapc *ldap)
                    483: {
                    484:        struct addrinfo          ai, *res, *res0;
                    485:        struct sockaddr_un       un;
                    486:        int                      ret = -1, saved_errno, fd = -1, code;
                    487:        struct aldap_message    *m;
                    488:        const char              *errstr;
                    489:        struct tls_config       *tls_config;
                    490:        char                     port[6];
                    491:
                    492:        if (ldap->ldap_protocol == LDAPI) {
                    493:                memset(&un, 0, sizeof(un));
                    494:                un.sun_family = AF_UNIX;
                    495:                if (strlcpy(un.sun_path, ldap->ldap_host,
                    496:                    sizeof(un.sun_path)) >= sizeof(un.sun_path)) {
                    497:                        log_warnx("socket '%s' too long", ldap->ldap_host);
                    498:                        goto done;
                    499:                }
                    500:                if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ||
                    501:                    connect(fd, (struct sockaddr *)&un, sizeof(un)) == -1)
                    502:                        goto done;
                    503:                goto init;
                    504:        }
                    505:
                    506:        memset(&ai, 0, sizeof(ai));
                    507:        ai.ai_family = AF_UNSPEC;
                    508:        ai.ai_socktype = SOCK_STREAM;
                    509:        ai.ai_protocol = IPPROTO_TCP;
                    510:        (void)snprintf(port, sizeof(port), "%u", ldap->ldap_port);
                    511:        if ((code = getaddrinfo(ldap->ldap_host, port,
                    512:            &ai, &res0)) != 0) {
                    513:                log_warnx("%s", gai_strerror(code));
                    514:                goto done;
                    515:        }
                    516:        for (res = res0; res; res = res->ai_next, fd = -1) {
                    517:                if ((fd = socket(res->ai_family, res->ai_socktype,
                    518:                    res->ai_protocol)) == -1)
                    519:                        continue;
                    520:
                    521:                if (connect(fd, res->ai_addr, res->ai_addrlen) >= 0)
                    522:                        break;
                    523:
                    524:                saved_errno = errno;
                    525:                close(fd);
                    526:                errno = saved_errno;
                    527:        }
                    528:        freeaddrinfo(res0);
                    529:        if (fd == -1)
                    530:                goto done;
                    531:
                    532:  init:
                    533:        if ((ldap->ldap_al = aldap_init(fd)) == NULL) {
                    534:                warn("LDAP init failed");
                    535:                close(fd);
                    536:                goto done;
                    537:        }
                    538:
                    539:        if (ldap->ldap_flags & F_STARTTLS) {
                    540:                log_debug("%s: requesting STARTTLS", __func__);
                    541:                if (aldap_req_starttls(ldap->ldap_al) == -1) {
                    542:                        log_warnx("failed to request STARTTLS");
                    543:                        goto done;
                    544:                }
                    545:
                    546:                if ((m = aldap_parse(ldap->ldap_al)) == NULL) {
                    547:                        log_warnx("failed to parse STARTTLS response");
                    548:                        goto done;
                    549:                }
                    550:
                    551:                if (ldap->ldap_al->msgid != m->msgid ||
                    552:                    (code = aldap_get_resultcode(m)) != LDAP_SUCCESS) {
                    553:                        log_warnx("STARTTLS failed: %s(%d)",
                    554:                            ldapc_resultcode(code), code);
                    555:                        aldap_freemsg(m);
                    556:                        goto done;
                    557:                }
                    558:                aldap_freemsg(m);
                    559:        }
                    560:
                    561:        if (ldap->ldap_flags & (F_STARTTLS | F_TLS)) {
                    562:                log_debug("%s: starting TLS", __func__);
                    563:
                    564:                if ((tls_config = tls_config_new()) == NULL) {
                    565:                        log_warnx("TLS config failed");
                    566:                        goto done;
                    567:                }
                    568:
                    569:                if (tls_config_set_ca_file(tls_config,
                    570:                    ldap->ldap_capath) == -1) {
                    571:                        log_warnx("unable to set CA %s", ldap->ldap_capath);
                    572:                        goto done;
                    573:                }
                    574:
                    575:                if (aldap_tls(ldap->ldap_al, tls_config, ldap->ldap_host) < 0) {
                    576:                        aldap_get_errno(ldap->ldap_al, &errstr);
                    577:                        log_warnx("TLS failed: %s", errstr);
                    578:                        goto done;
                    579:                }
                    580:        }
                    581:
                    582:        if (ldap->ldap_flags & F_NEEDAUTH) {
                    583:                log_debug("%s: bind request", __func__);
                    584:                if (aldap_bind(ldap->ldap_al, ldap->ldap_binddn,
                    585:                    ldap->ldap_secret) == -1) {
                    586:                        log_warnx("bind request failed");
                    587:                        goto done;
                    588:                }
                    589:
                    590:                if ((m = aldap_parse(ldap->ldap_al)) == NULL) {
                    591:                        log_warnx("failed to parse bind response");
                    592:                        goto done;
                    593:                }
                    594:
                    595:                if (ldap->ldap_al->msgid != m->msgid ||
                    596:                    (code = aldap_get_resultcode(m)) != LDAP_SUCCESS) {
                    597:                        log_warnx("bind failed: %s(%d)",
                    598:                            ldapc_resultcode(code), code);
                    599:                        aldap_freemsg(m);
                    600:                        goto done;
                    601:                }
                    602:                aldap_freemsg(m);
                    603:        }
                    604:
                    605:        log_debug("%s: connected", __func__);
                    606:
                    607:        ret = 0;
                    608:  done:
                    609:        if (ret != 0)
                    610:                ldapc_disconnect(ldap);
                    611:        if (ldap->ldap_secret != NULL)
                    612:                explicit_bzero(ldap->ldap_secret,
                    613:                    strlen(ldap->ldap_secret));
                    614:        return (ret);
                    615: }
                    616:
                    617: void
                    618: ldapc_disconnect(struct ldapc *ldap)
                    619: {
                    620:        if (ldap->ldap_al == NULL)
                    621:                return;
                    622:        aldap_close(ldap->ldap_al);
                    623:        ldap->ldap_al = NULL;
                    624: }
                    625:
                    626: const char *
                    627: ldapc_resultcode(enum result_code code)
                    628: {
                    629: #define CODE(_X)       case _X:return (#_X)
                    630:        switch (code) {
                    631:        CODE(LDAP_SUCCESS);
                    632:        CODE(LDAP_OPERATIONS_ERROR);
                    633:        CODE(LDAP_PROTOCOL_ERROR);
                    634:        CODE(LDAP_TIMELIMIT_EXCEEDED);
                    635:        CODE(LDAP_SIZELIMIT_EXCEEDED);
                    636:        CODE(LDAP_COMPARE_FALSE);
                    637:        CODE(LDAP_COMPARE_TRUE);
                    638:        CODE(LDAP_STRONG_AUTH_NOT_SUPPORTED);
                    639:        CODE(LDAP_STRONG_AUTH_REQUIRED);
                    640:        CODE(LDAP_REFERRAL);
                    641:        CODE(LDAP_ADMINLIMIT_EXCEEDED);
                    642:        CODE(LDAP_UNAVAILABLE_CRITICAL_EXTENSION);
                    643:        CODE(LDAP_CONFIDENTIALITY_REQUIRED);
                    644:        CODE(LDAP_SASL_BIND_IN_PROGRESS);
                    645:        CODE(LDAP_NO_SUCH_ATTRIBUTE);
                    646:        CODE(LDAP_UNDEFINED_TYPE);
                    647:        CODE(LDAP_INAPPROPRIATE_MATCHING);
                    648:        CODE(LDAP_CONSTRAINT_VIOLATION);
                    649:        CODE(LDAP_TYPE_OR_VALUE_EXISTS);
                    650:        CODE(LDAP_INVALID_SYNTAX);
                    651:        CODE(LDAP_NO_SUCH_OBJECT);
                    652:        CODE(LDAP_ALIAS_PROBLEM);
                    653:        CODE(LDAP_INVALID_DN_SYNTAX);
                    654:        CODE(LDAP_ALIAS_DEREF_PROBLEM);
                    655:        CODE(LDAP_INAPPROPRIATE_AUTH);
                    656:        CODE(LDAP_INVALID_CREDENTIALS);
                    657:        CODE(LDAP_INSUFFICIENT_ACCESS);
                    658:        CODE(LDAP_BUSY);
                    659:        CODE(LDAP_UNAVAILABLE);
                    660:        CODE(LDAP_UNWILLING_TO_PERFORM);
                    661:        CODE(LDAP_LOOP_DETECT);
                    662:        CODE(LDAP_NAMING_VIOLATION);
                    663:        CODE(LDAP_OBJECT_CLASS_VIOLATION);
                    664:        CODE(LDAP_NOT_ALLOWED_ON_NONLEAF);
                    665:        CODE(LDAP_NOT_ALLOWED_ON_RDN);
                    666:        CODE(LDAP_ALREADY_EXISTS);
                    667:        CODE(LDAP_NO_OBJECT_CLASS_MODS);
                    668:        CODE(LDAP_AFFECTS_MULTIPLE_DSAS);
                    669:        CODE(LDAP_OTHER);
                    670:        default:
                    671:                return ("UNKNOWN_ERROR");
                    672:        }
                    673: };
                    674:
                    675: int
                    676: ldapc_parseurl(struct ldapc *ldap, struct ldapc_search *ls, const char *url)
                    677: {
                    678:        struct aldap_url        *lu = &ldap->ldap_url;
                    679:        size_t                   i;
                    680:
                    681:        memset(lu, 0, sizeof(*lu));
                    682:        lu->scope = -1;
                    683:
                    684:        if (aldap_parse_url(url, lu) == -1) {
                    685:                log_warnx("failed to parse LDAP URL");
                    686:                return (-1);
                    687:        }
                    688:
                    689:        /* The protocol part is optional and we default to ldap:// */
                    690:        if (lu->protocol == -1)
                    691:                lu->protocol = LDAP;
                    692:        else if (lu->protocol == LDAPI) {
                    693:                if (lu->port != 0 ||
                    694:                    url_decode(lu->host) == NULL) {
                    695:                        log_warnx("invalid ldapi:// URL");
                    696:                        return (-1);
                    697:                }
                    698:        } else if ((ldap->ldap_flags & F_STARTTLS) &&
                    699:            lu->protocol != LDAPTLS) {
                    700:                log_warnx("conflicting protocol arguments");
                    701:                return (-1);
                    702:        } else if (lu->protocol == LDAPTLS)
                    703:                ldap->ldap_flags |= F_TLS|F_STARTTLS;
                    704:        else if (lu->protocol == LDAPS)
                    705:                ldap->ldap_flags |= F_TLS;
                    706:        ldap->ldap_protocol = lu->protocol;
                    707:
                    708:        ldap->ldap_host = lu->host;
                    709:        if (lu->port)
                    710:                ldap->ldap_port = lu->port;
                    711:
                    712:        /* The distinguished name has to be URL-encoded */
                    713:        if (lu->dn != NULL && ls->ls_basedn != NULL &&
                    714:            strcasecmp(ls->ls_basedn, lu->dn) != 0) {
                    715:                log_warnx("conflicting basedn arguments");
                    716:                return (-1);
                    717:        }
                    718:        if (lu->dn != NULL) {
                    719:                if (url_decode(lu->dn) == NULL)
                    720:                        return (-1);
                    721:                ls->ls_basedn = lu->dn;
                    722:        }
                    723:
                    724:        if (lu->scope != -1) {
                    725:                if (ls->ls_scope != -1 && (ls->ls_scope != lu->scope)) {
                    726:                        log_warnx("conflicting scope arguments");
                    727:                        return (-1);
                    728:                }
                    729:                ls->ls_scope = lu->scope;
                    730:        }
                    731:
                    732:        /* URL-decode optional attributes and the search filter */
                    733:        if (lu->attributes[0] != NULL) {
                    734:                for (i = 0; i < MAXATTR && lu->attributes[i] != NULL; i++)
                    735:                        if (url_decode(lu->attributes[i]) == NULL)
                    736:                                return (-1);
                    737:                ls->ls_attr = lu->attributes;
                    738:        }
                    739:        if (lu->filter != NULL) {
                    740:                if (url_decode(lu->filter) == NULL)
                    741:                        return (-1);
                    742:                ls->ls_filter = lu->filter;
                    743:        }
                    744:
                    745:        return (0);
                    746: }
                    747:
                    748: /* From usr.sbin/httpd/httpd.c */
                    749: const char *
                    750: url_decode(char *url)
                    751: {
                    752:        char            *p, *q;
                    753:        char             hex[3];
                    754:        unsigned long    x;
                    755:
                    756:        hex[2] = '\0';
                    757:        p = q = url;
                    758:
                    759:        while (*p != '\0') {
                    760:                switch (*p) {
                    761:                case '%':
                    762:                        /* Encoding character is followed by two hex chars */
                    763:                        if (!(isxdigit((unsigned char)p[1]) &&
                    764:                            isxdigit((unsigned char)p[2])))
                    765:                                return (NULL);
                    766:
                    767:                        hex[0] = p[1];
                    768:                        hex[1] = p[2];
                    769:
                    770:                        /*
                    771:                         * We don't have to validate "hex" because it is
                    772:                         * guaranteed to include two hex chars followed by nul.
                    773:                         */
                    774:                        x = strtoul(hex, NULL, 16);
                    775:                        *q = (char)x;
                    776:                        p += 2;
                    777:                        break;
                    778:                default:
                    779:                        *q = *p;
                    780:                        break;
                    781:                }
                    782:                p++;
                    783:                q++;
                    784:        }
                    785:        *q = '\0';
                    786:
                    787:        return (url);
                    788: }