1*832b780bSderaadt /* $OpenBSD: ldapclient.c,v 1.13 2021/09/02 21:09:29 deraadt Exp $ */
29107066aSreyk
39107066aSreyk /*
49107066aSreyk * Copyright (c) 2018 Reyk Floeter <reyk@openbsd.org>
59107066aSreyk *
69107066aSreyk * Permission to use, copy, modify, and distribute this software for any
79107066aSreyk * purpose with or without fee is hereby granted, provided that the above
89107066aSreyk * copyright notice and this permission notice appear in all copies.
99107066aSreyk *
109107066aSreyk * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
119107066aSreyk * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
129107066aSreyk * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
139107066aSreyk * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
149107066aSreyk * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
159107066aSreyk * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
169107066aSreyk * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
179107066aSreyk */
189107066aSreyk
199107066aSreyk #include <sys/queue.h>
209107066aSreyk #include <sys/socket.h>
211f1da8f4Sreyk #include <sys/stat.h>
229107066aSreyk #include <sys/tree.h>
239107066aSreyk #include <sys/un.h>
249107066aSreyk
259107066aSreyk #include <netinet/in.h>
269107066aSreyk #include <arpa/inet.h>
279107066aSreyk
289107066aSreyk #include <stdio.h>
299107066aSreyk #include <stdlib.h>
309107066aSreyk #include <stdint.h>
319107066aSreyk #include <unistd.h>
329107066aSreyk #include <ctype.h>
339107066aSreyk #include <err.h>
349107066aSreyk #include <errno.h>
359107066aSreyk #include <event.h>
369107066aSreyk #include <fcntl.h>
379107066aSreyk #include <limits.h>
389107066aSreyk #include <netdb.h>
399107066aSreyk #include <pwd.h>
409107066aSreyk #include <readpassphrase.h>
419107066aSreyk #include <resolv.h>
429107066aSreyk #include <signal.h>
439107066aSreyk #include <string.h>
449107066aSreyk #include <vis.h>
459107066aSreyk
469107066aSreyk #include "aldap.h"
479107066aSreyk #include "log.h"
489107066aSreyk
499107066aSreyk #define F_STARTTLS 0x01
509107066aSreyk #define F_TLS 0x02
519107066aSreyk #define F_NEEDAUTH 0x04
529107066aSreyk #define F_LDIF 0x08
539107066aSreyk
549107066aSreyk #define LDAPHOST "localhost"
559107066aSreyk #define LDAPFILTER "(objectClass=*)"
569107066aSreyk #define LDIF_LINELENGTH 79
571f1da8f4Sreyk #define LDAPPASSMAX 1024
589107066aSreyk
59*832b780bSderaadt #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
60*832b780bSderaadt
619107066aSreyk struct ldapc {
629107066aSreyk struct aldap *ldap_al;
639107066aSreyk char *ldap_host;
649107066aSreyk int ldap_port;
65fb0a89eeStedu const char *ldap_capath;
669107066aSreyk char *ldap_binddn;
679107066aSreyk char *ldap_secret;
689107066aSreyk unsigned int ldap_flags;
699107066aSreyk enum protocol_op ldap_req;
709107066aSreyk enum aldap_protocol ldap_protocol;
719107066aSreyk struct aldap_url ldap_url;
729107066aSreyk };
739107066aSreyk
749107066aSreyk struct ldapc_search {
759107066aSreyk int ls_sizelimit;
769107066aSreyk int ls_timelimit;
779107066aSreyk char *ls_basedn;
789107066aSreyk char *ls_filter;
799107066aSreyk int ls_scope;
809107066aSreyk char **ls_attr;
819107066aSreyk };
829107066aSreyk
839107066aSreyk __dead void usage(void);
849107066aSreyk int ldapc_connect(struct ldapc *);
859107066aSreyk int ldapc_search(struct ldapc *, struct ldapc_search *);
862f59fca6Smartijn int ldapc_printattr(struct ldapc *, const char *,
872f59fca6Smartijn const struct ber_octetstring *);
889107066aSreyk void ldapc_disconnect(struct ldapc *);
899107066aSreyk int ldapc_parseurl(struct ldapc *, struct ldapc_search *,
909107066aSreyk const char *);
919107066aSreyk const char *ldapc_resultcode(enum result_code);
929107066aSreyk const char *url_decode(char *);
939107066aSreyk
949107066aSreyk __dead void
usage(void)959107066aSreyk usage(void)
969107066aSreyk {
979107066aSreyk extern char *__progname;
989107066aSreyk
999107066aSreyk fprintf(stderr,
10095259b0aSjmc "usage: %s search [-LvWxZ] [-b basedn] [-c CAfile] [-D binddn] [-H host]\n"
10195259b0aSjmc " [-l timelimit] [-s scope] [-w secret] [-y secretfile] [-z sizelimit]\n"
10295259b0aSjmc " [filter] [attributes ...]\n",
1039107066aSreyk __progname);
1049107066aSreyk
1059107066aSreyk exit(1);
1069107066aSreyk }
1079107066aSreyk
1089107066aSreyk int
main(int argc,char * argv[])1099107066aSreyk main(int argc, char *argv[])
1109107066aSreyk {
1111f1da8f4Sreyk char passbuf[LDAPPASSMAX];
1121f1da8f4Sreyk const char *errstr, *url = NULL, *secretfile = NULL;
1131f1da8f4Sreyk struct stat st;
1149107066aSreyk struct ldapc ldap;
1159107066aSreyk struct ldapc_search ls;
1169107066aSreyk int ch;
1179107066aSreyk int verbose = 1;
1181f1da8f4Sreyk FILE *fp;
1199107066aSreyk
1209107066aSreyk if (pledge("stdio inet unix tty rpath dns", NULL) == -1)
1219107066aSreyk err(1, "pledge");
1229107066aSreyk
1239107066aSreyk log_init(verbose, 0);
1249107066aSreyk
1259107066aSreyk memset(&ldap, 0, sizeof(ldap));
1269107066aSreyk memset(&ls, 0, sizeof(ls));
1279107066aSreyk ls.ls_scope = -1;
1289107066aSreyk ldap.ldap_port = -1;
1299107066aSreyk
1309107066aSreyk /*
1319107066aSreyk * Check the command. Currently only "search" is supported but
1329107066aSreyk * it could be extended with others such as add, modify, or delete.
1339107066aSreyk */
1349107066aSreyk if (argc < 2)
1359107066aSreyk usage();
1369107066aSreyk else if (strcmp("search", argv[1]) == 0)
1379107066aSreyk ldap.ldap_req = LDAP_REQ_SEARCH;
1389107066aSreyk else
1399107066aSreyk usage();
1409107066aSreyk argc--;
1419107066aSreyk argv++;
1429107066aSreyk
1431f1da8f4Sreyk while ((ch = getopt(argc, argv, "b:c:D:H:Ll:s:vWw:xy:Zz:")) != -1) {
1449107066aSreyk switch (ch) {
1459107066aSreyk case 'b':
1469107066aSreyk ls.ls_basedn = optarg;
1479107066aSreyk break;
1489107066aSreyk case 'c':
1499107066aSreyk ldap.ldap_capath = optarg;
1509107066aSreyk break;
1519107066aSreyk case 'D':
1529107066aSreyk ldap.ldap_binddn = optarg;
1539107066aSreyk ldap.ldap_flags |= F_NEEDAUTH;
1549107066aSreyk break;
1559107066aSreyk case 'H':
1569107066aSreyk url = optarg;
1579107066aSreyk break;
1589107066aSreyk case 'L':
1599107066aSreyk ldap.ldap_flags |= F_LDIF;
1609107066aSreyk break;
1619107066aSreyk case 'l':
1629107066aSreyk ls.ls_timelimit = strtonum(optarg, 0, INT_MAX,
1639107066aSreyk &errstr);
1649107066aSreyk if (errstr != NULL)
1659107066aSreyk errx(1, "timelimit %s", errstr);
1669107066aSreyk break;
1679107066aSreyk case 's':
1689107066aSreyk if (strcasecmp("base", optarg) == 0)
1699107066aSreyk ls.ls_scope = LDAP_SCOPE_BASE;
1709107066aSreyk else if (strcasecmp("one", optarg) == 0)
1719107066aSreyk ls.ls_scope = LDAP_SCOPE_ONELEVEL;
1729107066aSreyk else if (strcasecmp("sub", optarg) == 0)
1739107066aSreyk ls.ls_scope = LDAP_SCOPE_SUBTREE;
1749107066aSreyk else
1759107066aSreyk errx(1, "invalid scope: %s", optarg);
1769107066aSreyk break;
1779107066aSreyk case 'v':
1789107066aSreyk verbose++;
1799107066aSreyk break;
1809107066aSreyk case 'w':
1819107066aSreyk ldap.ldap_secret = optarg;
1829107066aSreyk ldap.ldap_flags |= F_NEEDAUTH;
1839107066aSreyk break;
1849107066aSreyk case 'W':
1859107066aSreyk ldap.ldap_flags |= F_NEEDAUTH;
1869107066aSreyk break;
1879107066aSreyk case 'x':
1889107066aSreyk /* provided for compatibility */
1899107066aSreyk break;
1901f1da8f4Sreyk case 'y':
1911f1da8f4Sreyk secretfile = optarg;
1921f1da8f4Sreyk ldap.ldap_flags |= F_NEEDAUTH;
1931f1da8f4Sreyk break;
1949107066aSreyk case 'Z':
1959107066aSreyk ldap.ldap_flags |= F_STARTTLS;
1969107066aSreyk break;
1979107066aSreyk case 'z':
1989107066aSreyk ls.ls_sizelimit = strtonum(optarg, 0, INT_MAX,
1999107066aSreyk &errstr);
2009107066aSreyk if (errstr != NULL)
2019107066aSreyk errx(1, "sizelimit %s", errstr);
2029107066aSreyk break;
2039107066aSreyk default:
2049107066aSreyk usage();
2059107066aSreyk }
2069107066aSreyk }
2079107066aSreyk argc -= optind;
2089107066aSreyk argv += optind;
2099107066aSreyk
2109107066aSreyk log_setverbose(verbose);
2119107066aSreyk
2129107066aSreyk if (url != NULL && ldapc_parseurl(&ldap, &ls, url) == -1)
2139107066aSreyk errx(1, "ldapurl");
2149107066aSreyk
2159107066aSreyk /* Set the default after parsing URL and/or options */
2169107066aSreyk if (ldap.ldap_host == NULL)
2179107066aSreyk ldap.ldap_host = LDAPHOST;
2189107066aSreyk if (ldap.ldap_port == -1)
2199107066aSreyk ldap.ldap_port = ldap.ldap_protocol == LDAPS ?
2209107066aSreyk LDAPS_PORT : LDAP_PORT;
2219107066aSreyk if (ldap.ldap_protocol == LDAP && (ldap.ldap_flags & F_STARTTLS))
2229107066aSreyk ldap.ldap_protocol = LDAPTLS;
2239107066aSreyk if (ldap.ldap_capath == NULL)
224fb0a89eeStedu ldap.ldap_capath = tls_default_ca_cert_file();
2259107066aSreyk if (ls.ls_basedn == NULL)
2269107066aSreyk ls.ls_basedn = "";
2279107066aSreyk if (ls.ls_scope == -1)
2289107066aSreyk ls.ls_scope = LDAP_SCOPE_SUBTREE;
2299107066aSreyk if (ls.ls_filter == NULL)
2309107066aSreyk ls.ls_filter = LDAPFILTER;
2319107066aSreyk
2329107066aSreyk if (ldap.ldap_flags & F_NEEDAUTH) {
2339107066aSreyk if (ldap.ldap_binddn == NULL) {
2349107066aSreyk log_warnx("missing -D binddn");
2359107066aSreyk usage();
2369107066aSreyk }
2371f1da8f4Sreyk if (secretfile != NULL) {
2381f1da8f4Sreyk if (ldap.ldap_secret != NULL)
2391f1da8f4Sreyk errx(1, "conflicting -w/-y options");
2401f1da8f4Sreyk
2411f1da8f4Sreyk /* read password from stdin or file (first line) */
2421f1da8f4Sreyk if (strcmp(secretfile, "-") == 0)
2431f1da8f4Sreyk fp = stdin;
2441f1da8f4Sreyk else if (stat(secretfile, &st) == -1)
2451f1da8f4Sreyk err(1, "failed to access %s", secretfile);
2461f1da8f4Sreyk else if (S_ISREG(st.st_mode) && (st.st_mode & S_IROTH))
2471f1da8f4Sreyk errx(1, "%s is world-readable", secretfile);
2481f1da8f4Sreyk else if ((fp = fopen(secretfile, "r")) == NULL)
2491f1da8f4Sreyk err(1, "failed to open %s", secretfile);
2501f1da8f4Sreyk if (fgets(passbuf, sizeof(passbuf), fp) == NULL)
2511f1da8f4Sreyk err(1, "failed to read %s", secretfile);
2521f1da8f4Sreyk if (fp != stdin)
2531f1da8f4Sreyk fclose(fp);
2541f1da8f4Sreyk
2551f1da8f4Sreyk passbuf[strcspn(passbuf, "\n")] = '\0';
2561f1da8f4Sreyk ldap.ldap_secret = passbuf;
2571f1da8f4Sreyk }
2589107066aSreyk if (ldap.ldap_secret == NULL) {
2599107066aSreyk if (readpassphrase("Password: ",
2609107066aSreyk passbuf, sizeof(passbuf), RPP_REQUIRE_TTY) == NULL)
2619107066aSreyk errx(1, "failed to read LDAP password");
2629107066aSreyk ldap.ldap_secret = passbuf;
2639107066aSreyk }
2649107066aSreyk }
2659107066aSreyk
2669107066aSreyk if (pledge("stdio inet unix rpath dns", NULL) == -1)
2679107066aSreyk err(1, "pledge");
2689107066aSreyk
2699107066aSreyk /* optional search filter */
2709107066aSreyk if (argc && strchr(argv[0], '=') != NULL) {
2719107066aSreyk ls.ls_filter = argv[0];
2729107066aSreyk argc--;
2739107066aSreyk argv++;
2749107066aSreyk }
2759107066aSreyk /* search attributes */
2769107066aSreyk if (argc)
2779107066aSreyk ls.ls_attr = argv;
2789107066aSreyk
2799107066aSreyk if (ldapc_connect(&ldap) == -1)
2809107066aSreyk errx(1, "LDAP connection failed");
2819107066aSreyk
2829107066aSreyk if (pledge("stdio", NULL) == -1)
2839107066aSreyk err(1, "pledge");
2849107066aSreyk
2859107066aSreyk if (ldapc_search(&ldap, &ls) == -1)
2869107066aSreyk errx(1, "LDAP search failed");
2879107066aSreyk
2889107066aSreyk ldapc_disconnect(&ldap);
2899107066aSreyk aldap_free_url(&ldap.ldap_url);
2909107066aSreyk
2919107066aSreyk return (0);
2929107066aSreyk }
2939107066aSreyk
2949107066aSreyk int
ldapc_search(struct ldapc * ldap,struct ldapc_search * ls)2959107066aSreyk ldapc_search(struct ldapc *ldap, struct ldapc_search *ls)
2969107066aSreyk {
2979107066aSreyk struct aldap_page_control *pg = NULL;
2989107066aSreyk struct aldap_message *m;
2999107066aSreyk const char *errstr;
3009107066aSreyk const char *searchdn, *dn = NULL;
3019107066aSreyk char *outkey;
3022f59fca6Smartijn struct aldap_stringset *outvalues;
3032f59fca6Smartijn int ret, code, fail = 0;
3042f59fca6Smartijn size_t i;
3059107066aSreyk
30624a8eabdSmartijn if (ldap->ldap_flags & F_LDIF)
30724a8eabdSmartijn printf("version: 1\n");
3089107066aSreyk do {
3099107066aSreyk if (aldap_search(ldap->ldap_al, ls->ls_basedn, ls->ls_scope,
3109107066aSreyk ls->ls_filter, ls->ls_attr, 0, ls->ls_sizelimit,
3119107066aSreyk ls->ls_timelimit, pg) == -1) {
3129107066aSreyk aldap_get_errno(ldap->ldap_al, &errstr);
3139107066aSreyk log_warnx("LDAP search failed: %s", errstr);
3149107066aSreyk return (-1);
3159107066aSreyk }
3169107066aSreyk
3179107066aSreyk if (pg != NULL) {
3189107066aSreyk aldap_freepage(pg);
3199107066aSreyk pg = NULL;
3209107066aSreyk }
3219107066aSreyk
3229107066aSreyk while ((m = aldap_parse(ldap->ldap_al)) != NULL) {
3239107066aSreyk if (ldap->ldap_al->msgid != m->msgid) {
3249107066aSreyk goto fail;
3259107066aSreyk }
3269107066aSreyk
3279107066aSreyk if ((code = aldap_get_resultcode(m)) != LDAP_SUCCESS) {
3289107066aSreyk log_warnx("LDAP search failed: %s(%d)",
3299107066aSreyk ldapc_resultcode(code), code);
3309107066aSreyk break;
3319107066aSreyk }
3329107066aSreyk
3339107066aSreyk if (m->message_type == LDAP_RES_SEARCH_RESULT) {
3349107066aSreyk if (m->page != NULL && m->page->cookie_len != 0)
3359107066aSreyk pg = m->page;
3369107066aSreyk else
3379107066aSreyk pg = NULL;
3389107066aSreyk
3399107066aSreyk aldap_freemsg(m);
3409107066aSreyk break;
3419107066aSreyk }
3429107066aSreyk
3439107066aSreyk if (m->message_type != LDAP_RES_SEARCH_ENTRY) {
3449107066aSreyk goto fail;
3459107066aSreyk }
3469107066aSreyk
3479107066aSreyk if (aldap_count_attrs(m) < 1) {
3489107066aSreyk aldap_freemsg(m);
3499107066aSreyk continue;
3509107066aSreyk }
3519107066aSreyk
3529107066aSreyk if ((searchdn = aldap_get_dn(m)) == NULL)
3539107066aSreyk goto fail;
3549107066aSreyk
3559107066aSreyk if (dn != NULL)
3569107066aSreyk printf("\n");
3579107066aSreyk else
3589107066aSreyk dn = ls->ls_basedn;
3599107066aSreyk if (strcmp(dn, searchdn) != 0)
3609107066aSreyk printf("dn: %s\n", searchdn);
3619107066aSreyk
3629107066aSreyk for (ret = aldap_first_attr(m, &outkey, &outvalues);
3639107066aSreyk ret != -1;
3649107066aSreyk ret = aldap_next_attr(m, &outkey, &outvalues)) {
3652f59fca6Smartijn for (i = 0; i < outvalues->len; i++) {
3669107066aSreyk if (ldapc_printattr(ldap, outkey,
3672f59fca6Smartijn &(outvalues->str[i])) == -1) {
3689107066aSreyk fail = 1;
3699107066aSreyk break;
3709107066aSreyk }
3719107066aSreyk }
3729107066aSreyk }
3739107066aSreyk free(outkey);
3749107066aSreyk aldap_free_attr(outvalues);
3759107066aSreyk
3769107066aSreyk aldap_freemsg(m);
3779107066aSreyk }
3789107066aSreyk } while (pg != NULL && fail == 0);
3799107066aSreyk
3809107066aSreyk if (fail)
3819107066aSreyk return (-1);
3829107066aSreyk return (0);
3839107066aSreyk fail:
3849107066aSreyk ldapc_disconnect(ldap);
3859107066aSreyk return (-1);
3869107066aSreyk }
3879107066aSreyk
3889107066aSreyk int
ldapc_printattr(struct ldapc * ldap,const char * key,const struct ber_octetstring * value)3892f59fca6Smartijn ldapc_printattr(struct ldapc *ldap, const char *key,
3902f59fca6Smartijn const struct ber_octetstring *value)
3919107066aSreyk {
3929107066aSreyk char *p = NULL, *out;
3939107066aSreyk const unsigned char *cp;
3949107066aSreyk int encode;
3952f59fca6Smartijn size_t i, inlen, outlen, left;
3969107066aSreyk
3979107066aSreyk if (ldap->ldap_flags & F_LDIF) {
3989107066aSreyk /* OpenLDAP encodes the userPassword by default */
3999107066aSreyk if (strcasecmp("userPassword", key) == 0)
4009107066aSreyk encode = 1;
4019107066aSreyk else
4029107066aSreyk encode = 0;
4039107066aSreyk
4049107066aSreyk /*
4059107066aSreyk * The LDIF format a set of characters that can be included
4069107066aSreyk * in SAFE-STRINGs. String value that do not match the
4079107066aSreyk * criteria must be encoded as Base64.
4089107066aSreyk */
4092f59fca6Smartijn cp = (const unsigned char *)value->ostr_val;
410d1bcc882Smartijn /* !SAFE-INIT-CHAR: SAFE-CHAR minus %x20 %x3A %x3C */
411d1bcc882Smartijn if (*cp == ' ' ||
412d1bcc882Smartijn *cp == ':' ||
413d1bcc882Smartijn *cp == '<')
414d1bcc882Smartijn encode = 1;
4152f59fca6Smartijn for (i = 0; encode == 0 && i < value->ostr_len - 1; i++) {
4169107066aSreyk /* !SAFE-CHAR %x01-09 / %x0B-0C / %x0E-7F */
4172f59fca6Smartijn if (cp[i] > 127 ||
4182f59fca6Smartijn cp[i] == '\0' ||
4192f59fca6Smartijn cp[i] == '\n' ||
4202f59fca6Smartijn cp[i] == '\r')
4219107066aSreyk encode = 1;
4229107066aSreyk }
4239107066aSreyk
4249107066aSreyk if (!encode) {
4252f59fca6Smartijn if (asprintf(&p, "%s: %s", key,
4262f59fca6Smartijn (const char *)value->ostr_val) == -1) {
4279107066aSreyk log_warnx("asprintf");
4289107066aSreyk return (-1);
4299107066aSreyk }
4309107066aSreyk } else {
4312f59fca6Smartijn outlen = (((value->ostr_len + 2) / 3) * 4) + 1;
4329107066aSreyk
4339107066aSreyk if ((out = calloc(1, outlen)) == NULL ||
4342f59fca6Smartijn b64_ntop(value->ostr_val, value->ostr_len, out,
4352f59fca6Smartijn outlen) == -1) {
4369107066aSreyk log_warnx("Base64 encoding failed");
4379107066aSreyk free(p);
4389107066aSreyk return (-1);
4399107066aSreyk }
4409107066aSreyk
4419107066aSreyk /* Base64 is indicated with a double-colon */
442745c1469Smartijn if (asprintf(&p, "%s:: %s", key, out) == -1) {
4439107066aSreyk log_warnx("asprintf");
4449107066aSreyk free(out);
4459107066aSreyk return (-1);
4469107066aSreyk }
4479107066aSreyk free(out);
4489107066aSreyk }
4499107066aSreyk
4509107066aSreyk /* Wrap lines */
4519107066aSreyk for (outlen = 0, inlen = strlen(p);
4529107066aSreyk outlen < inlen;
453e8ff07ceSmartijn outlen += LDIF_LINELENGTH - 1) {
4549107066aSreyk if (outlen)
4559107066aSreyk putchar(' ');
456e8ff07ceSmartijn if (outlen > LDIF_LINELENGTH)
457e8ff07ceSmartijn outlen--;
4589107066aSreyk /* max. line length - newline - optional indent */
459*832b780bSderaadt left = MINIMUM(inlen - outlen, outlen ?
4609107066aSreyk LDIF_LINELENGTH - 2 :
4619107066aSreyk LDIF_LINELENGTH - 1);
4629107066aSreyk fwrite(p + outlen, left, 1, stdout);
4639107066aSreyk putchar('\n');
4649107066aSreyk }
4659107066aSreyk } else {
4669107066aSreyk /*
4679107066aSreyk * Use vis(1) instead of base64 encoding of non-printable
4689107066aSreyk * values. This is much nicer as it always prdocues a
4699107066aSreyk * human-readable visual output. This can safely be done
4709107066aSreyk * on all values no matter if they include non-printable
4719107066aSreyk * characters.
4729107066aSreyk */
4732f59fca6Smartijn p = calloc(1, 4 * value->ostr_len + 1);
4742f59fca6Smartijn if (strvisx(p, value->ostr_val, value->ostr_len,
4752f59fca6Smartijn VIS_SAFE|VIS_NL) == -1) {
4769107066aSreyk log_warn("visual encoding failed");
4779107066aSreyk return (-1);
4789107066aSreyk }
4799107066aSreyk
4809107066aSreyk printf("%s: %s\n", key, p);
4819107066aSreyk }
4829107066aSreyk
4839107066aSreyk free(p);
4849107066aSreyk return (0);
4859107066aSreyk }
4869107066aSreyk
4879107066aSreyk int
ldapc_connect(struct ldapc * ldap)4889107066aSreyk ldapc_connect(struct ldapc *ldap)
4899107066aSreyk {
4909107066aSreyk struct addrinfo ai, *res, *res0;
4919107066aSreyk struct sockaddr_un un;
4929107066aSreyk int ret = -1, saved_errno, fd = -1, code;
4939107066aSreyk struct aldap_message *m;
4949107066aSreyk const char *errstr;
4959107066aSreyk struct tls_config *tls_config;
4969107066aSreyk char port[6];
4979107066aSreyk
4989107066aSreyk if (ldap->ldap_protocol == LDAPI) {
4999107066aSreyk memset(&un, 0, sizeof(un));
5009107066aSreyk un.sun_family = AF_UNIX;
5019107066aSreyk if (strlcpy(un.sun_path, ldap->ldap_host,
5029107066aSreyk sizeof(un.sun_path)) >= sizeof(un.sun_path)) {
5039107066aSreyk log_warnx("socket '%s' too long", ldap->ldap_host);
5049107066aSreyk goto done;
5059107066aSreyk }
5069107066aSreyk if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ||
5079107066aSreyk connect(fd, (struct sockaddr *)&un, sizeof(un)) == -1)
5089107066aSreyk goto done;
5099107066aSreyk goto init;
5109107066aSreyk }
5119107066aSreyk
5129107066aSreyk memset(&ai, 0, sizeof(ai));
5139107066aSreyk ai.ai_family = AF_UNSPEC;
5149107066aSreyk ai.ai_socktype = SOCK_STREAM;
5159107066aSreyk ai.ai_protocol = IPPROTO_TCP;
5169107066aSreyk (void)snprintf(port, sizeof(port), "%u", ldap->ldap_port);
5179107066aSreyk if ((code = getaddrinfo(ldap->ldap_host, port,
5189107066aSreyk &ai, &res0)) != 0) {
5199107066aSreyk log_warnx("%s", gai_strerror(code));
5209107066aSreyk goto done;
5219107066aSreyk }
5229107066aSreyk for (res = res0; res; res = res->ai_next, fd = -1) {
5239107066aSreyk if ((fd = socket(res->ai_family, res->ai_socktype,
5249107066aSreyk res->ai_protocol)) == -1)
5259107066aSreyk continue;
5269107066aSreyk
5279107066aSreyk if (connect(fd, res->ai_addr, res->ai_addrlen) >= 0)
5289107066aSreyk break;
5299107066aSreyk
5309107066aSreyk saved_errno = errno;
5319107066aSreyk close(fd);
5329107066aSreyk errno = saved_errno;
5339107066aSreyk }
5349107066aSreyk freeaddrinfo(res0);
5359107066aSreyk if (fd == -1)
5369107066aSreyk goto done;
5379107066aSreyk
5389107066aSreyk init:
5399107066aSreyk if ((ldap->ldap_al = aldap_init(fd)) == NULL) {
5409107066aSreyk warn("LDAP init failed");
5419107066aSreyk close(fd);
5429107066aSreyk goto done;
5439107066aSreyk }
5449107066aSreyk
5459107066aSreyk if (ldap->ldap_flags & F_STARTTLS) {
5469107066aSreyk log_debug("%s: requesting STARTTLS", __func__);
5479107066aSreyk if (aldap_req_starttls(ldap->ldap_al) == -1) {
5489107066aSreyk log_warnx("failed to request STARTTLS");
5499107066aSreyk goto done;
5509107066aSreyk }
5519107066aSreyk
5529107066aSreyk if ((m = aldap_parse(ldap->ldap_al)) == NULL) {
5539107066aSreyk log_warnx("failed to parse STARTTLS response");
5549107066aSreyk goto done;
5559107066aSreyk }
5569107066aSreyk
5579107066aSreyk if (ldap->ldap_al->msgid != m->msgid ||
5589107066aSreyk (code = aldap_get_resultcode(m)) != LDAP_SUCCESS) {
5599107066aSreyk log_warnx("STARTTLS failed: %s(%d)",
5609107066aSreyk ldapc_resultcode(code), code);
5619107066aSreyk aldap_freemsg(m);
5629107066aSreyk goto done;
5639107066aSreyk }
5649107066aSreyk aldap_freemsg(m);
5659107066aSreyk }
5669107066aSreyk
5679107066aSreyk if (ldap->ldap_flags & (F_STARTTLS | F_TLS)) {
5689107066aSreyk log_debug("%s: starting TLS", __func__);
5699107066aSreyk
5709107066aSreyk if ((tls_config = tls_config_new()) == NULL) {
5719107066aSreyk log_warnx("TLS config failed");
5729107066aSreyk goto done;
5739107066aSreyk }
5749107066aSreyk
5759107066aSreyk if (tls_config_set_ca_file(tls_config,
5769107066aSreyk ldap->ldap_capath) == -1) {
5779107066aSreyk log_warnx("unable to set CA %s", ldap->ldap_capath);
5789107066aSreyk goto done;
5799107066aSreyk }
5809107066aSreyk
5819107066aSreyk if (aldap_tls(ldap->ldap_al, tls_config, ldap->ldap_host) < 0) {
5829107066aSreyk aldap_get_errno(ldap->ldap_al, &errstr);
5839107066aSreyk log_warnx("TLS failed: %s", errstr);
5849107066aSreyk goto done;
5859107066aSreyk }
5869107066aSreyk }
5879107066aSreyk
5889107066aSreyk if (ldap->ldap_flags & F_NEEDAUTH) {
5899107066aSreyk log_debug("%s: bind request", __func__);
5909107066aSreyk if (aldap_bind(ldap->ldap_al, ldap->ldap_binddn,
5919107066aSreyk ldap->ldap_secret) == -1) {
5929107066aSreyk log_warnx("bind request failed");
5939107066aSreyk goto done;
5949107066aSreyk }
5959107066aSreyk
5969107066aSreyk if ((m = aldap_parse(ldap->ldap_al)) == NULL) {
5979107066aSreyk log_warnx("failed to parse bind response");
5989107066aSreyk goto done;
5999107066aSreyk }
6009107066aSreyk
6019107066aSreyk if (ldap->ldap_al->msgid != m->msgid ||
6029107066aSreyk (code = aldap_get_resultcode(m)) != LDAP_SUCCESS) {
6039107066aSreyk log_warnx("bind failed: %s(%d)",
6049107066aSreyk ldapc_resultcode(code), code);
6059107066aSreyk aldap_freemsg(m);
6069107066aSreyk goto done;
6079107066aSreyk }
6089107066aSreyk aldap_freemsg(m);
6099107066aSreyk }
6109107066aSreyk
6119107066aSreyk log_debug("%s: connected", __func__);
6129107066aSreyk
6139107066aSreyk ret = 0;
6149107066aSreyk done:
6159107066aSreyk if (ret != 0)
6169107066aSreyk ldapc_disconnect(ldap);
6179107066aSreyk if (ldap->ldap_secret != NULL)
6189107066aSreyk explicit_bzero(ldap->ldap_secret,
6199107066aSreyk strlen(ldap->ldap_secret));
6209107066aSreyk return (ret);
6219107066aSreyk }
6229107066aSreyk
6239107066aSreyk void
ldapc_disconnect(struct ldapc * ldap)6249107066aSreyk ldapc_disconnect(struct ldapc *ldap)
6259107066aSreyk {
6269107066aSreyk if (ldap->ldap_al == NULL)
6279107066aSreyk return;
6289107066aSreyk aldap_close(ldap->ldap_al);
6299107066aSreyk ldap->ldap_al = NULL;
6309107066aSreyk }
6319107066aSreyk
6329107066aSreyk const char *
ldapc_resultcode(enum result_code code)6339107066aSreyk ldapc_resultcode(enum result_code code)
6349107066aSreyk {
6359107066aSreyk #define CODE(_X) case _X:return (#_X)
6369107066aSreyk switch (code) {
6379107066aSreyk CODE(LDAP_SUCCESS);
6389107066aSreyk CODE(LDAP_OPERATIONS_ERROR);
6399107066aSreyk CODE(LDAP_PROTOCOL_ERROR);
6409107066aSreyk CODE(LDAP_TIMELIMIT_EXCEEDED);
6419107066aSreyk CODE(LDAP_SIZELIMIT_EXCEEDED);
6429107066aSreyk CODE(LDAP_COMPARE_FALSE);
6439107066aSreyk CODE(LDAP_COMPARE_TRUE);
6449107066aSreyk CODE(LDAP_STRONG_AUTH_NOT_SUPPORTED);
6459107066aSreyk CODE(LDAP_STRONG_AUTH_REQUIRED);
6469107066aSreyk CODE(LDAP_REFERRAL);
6479107066aSreyk CODE(LDAP_ADMINLIMIT_EXCEEDED);
6489107066aSreyk CODE(LDAP_UNAVAILABLE_CRITICAL_EXTENSION);
6499107066aSreyk CODE(LDAP_CONFIDENTIALITY_REQUIRED);
6509107066aSreyk CODE(LDAP_SASL_BIND_IN_PROGRESS);
6519107066aSreyk CODE(LDAP_NO_SUCH_ATTRIBUTE);
6529107066aSreyk CODE(LDAP_UNDEFINED_TYPE);
6539107066aSreyk CODE(LDAP_INAPPROPRIATE_MATCHING);
6549107066aSreyk CODE(LDAP_CONSTRAINT_VIOLATION);
6559107066aSreyk CODE(LDAP_TYPE_OR_VALUE_EXISTS);
6569107066aSreyk CODE(LDAP_INVALID_SYNTAX);
6579107066aSreyk CODE(LDAP_NO_SUCH_OBJECT);
6589107066aSreyk CODE(LDAP_ALIAS_PROBLEM);
6599107066aSreyk CODE(LDAP_INVALID_DN_SYNTAX);
6609107066aSreyk CODE(LDAP_ALIAS_DEREF_PROBLEM);
6619107066aSreyk CODE(LDAP_INAPPROPRIATE_AUTH);
6629107066aSreyk CODE(LDAP_INVALID_CREDENTIALS);
6639107066aSreyk CODE(LDAP_INSUFFICIENT_ACCESS);
6649107066aSreyk CODE(LDAP_BUSY);
6659107066aSreyk CODE(LDAP_UNAVAILABLE);
6669107066aSreyk CODE(LDAP_UNWILLING_TO_PERFORM);
6679107066aSreyk CODE(LDAP_LOOP_DETECT);
6689107066aSreyk CODE(LDAP_NAMING_VIOLATION);
6699107066aSreyk CODE(LDAP_OBJECT_CLASS_VIOLATION);
6709107066aSreyk CODE(LDAP_NOT_ALLOWED_ON_NONLEAF);
6719107066aSreyk CODE(LDAP_NOT_ALLOWED_ON_RDN);
6729107066aSreyk CODE(LDAP_ALREADY_EXISTS);
6739107066aSreyk CODE(LDAP_NO_OBJECT_CLASS_MODS);
6749107066aSreyk CODE(LDAP_AFFECTS_MULTIPLE_DSAS);
6759107066aSreyk CODE(LDAP_OTHER);
6769107066aSreyk default:
6779107066aSreyk return ("UNKNOWN_ERROR");
6789107066aSreyk }
6799107066aSreyk };
6809107066aSreyk
6819107066aSreyk int
ldapc_parseurl(struct ldapc * ldap,struct ldapc_search * ls,const char * url)6829107066aSreyk ldapc_parseurl(struct ldapc *ldap, struct ldapc_search *ls, const char *url)
6839107066aSreyk {
6849107066aSreyk struct aldap_url *lu = &ldap->ldap_url;
6859107066aSreyk size_t i;
6869107066aSreyk
6879107066aSreyk memset(lu, 0, sizeof(*lu));
6889107066aSreyk lu->scope = -1;
6899107066aSreyk
6909107066aSreyk if (aldap_parse_url(url, lu) == -1) {
6919107066aSreyk log_warnx("failed to parse LDAP URL");
6929107066aSreyk return (-1);
6939107066aSreyk }
6949107066aSreyk
6959107066aSreyk /* The protocol part is optional and we default to ldap:// */
6969107066aSreyk if (lu->protocol == -1)
6979107066aSreyk lu->protocol = LDAP;
6989107066aSreyk else if (lu->protocol == LDAPI) {
6999107066aSreyk if (lu->port != 0 ||
7009107066aSreyk url_decode(lu->host) == NULL) {
7019107066aSreyk log_warnx("invalid ldapi:// URL");
7029107066aSreyk return (-1);
7039107066aSreyk }
7049107066aSreyk } else if ((ldap->ldap_flags & F_STARTTLS) &&
7059107066aSreyk lu->protocol != LDAPTLS) {
7069107066aSreyk log_warnx("conflicting protocol arguments");
7079107066aSreyk return (-1);
7089107066aSreyk } else if (lu->protocol == LDAPTLS)
7099107066aSreyk ldap->ldap_flags |= F_TLS|F_STARTTLS;
7109107066aSreyk else if (lu->protocol == LDAPS)
7119107066aSreyk ldap->ldap_flags |= F_TLS;
7129107066aSreyk ldap->ldap_protocol = lu->protocol;
7139107066aSreyk
7149107066aSreyk ldap->ldap_host = lu->host;
7159107066aSreyk if (lu->port)
7169107066aSreyk ldap->ldap_port = lu->port;
7179107066aSreyk
7189107066aSreyk /* The distinguished name has to be URL-encoded */
7199107066aSreyk if (lu->dn != NULL && ls->ls_basedn != NULL &&
7209107066aSreyk strcasecmp(ls->ls_basedn, lu->dn) != 0) {
7219107066aSreyk log_warnx("conflicting basedn arguments");
7229107066aSreyk return (-1);
7239107066aSreyk }
7249107066aSreyk if (lu->dn != NULL) {
7259107066aSreyk if (url_decode(lu->dn) == NULL)
7269107066aSreyk return (-1);
7279107066aSreyk ls->ls_basedn = lu->dn;
7289107066aSreyk }
7299107066aSreyk
7309107066aSreyk if (lu->scope != -1) {
7319107066aSreyk if (ls->ls_scope != -1 && (ls->ls_scope != lu->scope)) {
7329107066aSreyk log_warnx("conflicting scope arguments");
7339107066aSreyk return (-1);
7349107066aSreyk }
7359107066aSreyk ls->ls_scope = lu->scope;
7369107066aSreyk }
7379107066aSreyk
7389107066aSreyk /* URL-decode optional attributes and the search filter */
7399107066aSreyk if (lu->attributes[0] != NULL) {
7409107066aSreyk for (i = 0; i < MAXATTR && lu->attributes[i] != NULL; i++)
7419107066aSreyk if (url_decode(lu->attributes[i]) == NULL)
7429107066aSreyk return (-1);
7439107066aSreyk ls->ls_attr = lu->attributes;
7449107066aSreyk }
7459107066aSreyk if (lu->filter != NULL) {
7469107066aSreyk if (url_decode(lu->filter) == NULL)
7479107066aSreyk return (-1);
7489107066aSreyk ls->ls_filter = lu->filter;
7499107066aSreyk }
7509107066aSreyk
7519107066aSreyk return (0);
7529107066aSreyk }
7539107066aSreyk
7549107066aSreyk /* From usr.sbin/httpd/httpd.c */
7559107066aSreyk const char *
url_decode(char * url)7569107066aSreyk url_decode(char *url)
7579107066aSreyk {
7589107066aSreyk char *p, *q;
7599107066aSreyk char hex[3];
7609107066aSreyk unsigned long x;
7619107066aSreyk
7629107066aSreyk hex[2] = '\0';
7639107066aSreyk p = q = url;
7649107066aSreyk
7659107066aSreyk while (*p != '\0') {
7669107066aSreyk switch (*p) {
7679107066aSreyk case '%':
7689107066aSreyk /* Encoding character is followed by two hex chars */
7699107066aSreyk if (!(isxdigit((unsigned char)p[1]) &&
7709107066aSreyk isxdigit((unsigned char)p[2])))
7719107066aSreyk return (NULL);
7729107066aSreyk
7739107066aSreyk hex[0] = p[1];
7749107066aSreyk hex[1] = p[2];
7759107066aSreyk
7769107066aSreyk /*
7779107066aSreyk * We don't have to validate "hex" because it is
7789107066aSreyk * guaranteed to include two hex chars followed by nul.
7799107066aSreyk */
7809107066aSreyk x = strtoul(hex, NULL, 16);
7819107066aSreyk *q = (char)x;
7829107066aSreyk p += 2;
7839107066aSreyk break;
7849107066aSreyk default:
7859107066aSreyk *q = *p;
7869107066aSreyk break;
7879107066aSreyk }
7889107066aSreyk p++;
7899107066aSreyk q++;
7909107066aSreyk }
7919107066aSreyk *q = '\0';
7929107066aSreyk
7939107066aSreyk return (url);
7949107066aSreyk }
795