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: }