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