xref: /openbsd-src/usr.bin/ldap/ldapclient.c (revision c90a81c56dcebd6a1b73fe4aff9b03385b8e63b3)
1 /*	$OpenBSD: ldapclient.c,v 1.11 2018/11/29 14:25:07 tedu Exp $	*/
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>
22 #include <sys/stat.h>
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
58 #define LDAPPASSMAX	1024
59 
60 struct ldapc {
61 	struct aldap		*ldap_al;
62 	char			*ldap_host;
63 	int			 ldap_port;
64 	const 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 *,
86 		    const struct ber_octetstring *);
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,
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",
102 	    __progname);
103 
104 	exit(1);
105 }
106 
107 int
108 main(int argc, char *argv[])
109 {
110 	char			 passbuf[LDAPPASSMAX];
111 	const char		*errstr, *url = NULL, *secretfile = NULL;
112 	struct stat		 st;
113 	struct ldapc		 ldap;
114 	struct ldapc_search	 ls;
115 	int			 ch;
116 	int			 verbose = 1;
117 	FILE			*fp;
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 
142 	while ((ch = getopt(argc, argv, "b:c:D:H:Ll:s:vWw:xy:Zz:")) != -1) {
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;
189 		case 'y':
190 			secretfile = optarg;
191 			ldap.ldap_flags |= F_NEEDAUTH;
192 			break;
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)
223 		ldap.ldap_capath = tls_default_ca_cert_file();
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();
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;
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;
301 	struct aldap_stringset		*outvalues;
302 	int				 ret, code, fail = 0;
303 	size_t				 i;
304 
305 	if (ldap->ldap_flags & F_LDIF)
306 		printf("version: 1\n");
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)) {
364 				for (i = 0; i < outvalues->len; i++) {
365 					if (ldapc_printattr(ldap, outkey,
366 					    &(outvalues->str[i])) == -1) {
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
388 ldapc_printattr(struct ldapc *ldap, const char *key,
389     const struct ber_octetstring *value)
390 {
391 	char			*p = NULL, *out;
392 	const unsigned char	*cp;
393 	int			 encode;
394 	size_t			 i, inlen, outlen, left;
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 		 */
408 		cp = (const unsigned char *)value->ostr_val;
409 		/* !SAFE-INIT-CHAR: SAFE-CHAR minus %x20 %x3A %x3C */
410 		if (*cp == ' ' ||
411 		    *cp == ':' ||
412 		    *cp == '<')
413 			encode = 1;
414 		for (i = 0; encode == 0 && i < value->ostr_len - 1; i++) {
415 			/* !SAFE-CHAR %x01-09 / %x0B-0C / %x0E-7F */
416 			if (cp[i] > 127 ||
417 			    cp[i] == '\0' ||
418 			    cp[i] == '\n' ||
419 			    cp[i] == '\r')
420 				encode = 1;
421 		}
422 
423 		if (!encode) {
424 			if (asprintf(&p, "%s: %s", key,
425 			    (const char *)value->ostr_val) == -1) {
426 				log_warnx("asprintf");
427 				return (-1);
428 			}
429 		} else {
430 			outlen = (((value->ostr_len + 2) / 3) * 4) + 1;
431 
432 			if ((out = calloc(1, outlen)) == NULL ||
433 			    b64_ntop(value->ostr_val, value->ostr_len, out,
434 			    outlen) == -1) {
435 				log_warnx("Base64 encoding failed");
436 				free(p);
437 				return (-1);
438 			}
439 
440 			/* Base64 is indicated with a double-colon */
441 			if (asprintf(&p, "%s:: %s", key, out) == -1) {
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;
452 		    outlen += LDIF_LINELENGTH - 1) {
453 			if (outlen)
454 				putchar(' ');
455 			if (outlen > LDIF_LINELENGTH)
456 				outlen--;
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 		 */
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) {
475 			log_warn("visual encoding failed");
476 			return (-1);
477 		}
478 
479 		printf("%s: %s\n", key, p);
480 		free(p);
481 	}
482 
483 	free(p);
484 	return (0);
485 }
486 
487 int
488 ldapc_connect(struct ldapc *ldap)
489 {
490 	struct addrinfo		 ai, *res, *res0;
491 	struct sockaddr_un	 un;
492 	int			 ret = -1, saved_errno, fd = -1, code;
493 	struct aldap_message	*m;
494 	const char		*errstr;
495 	struct tls_config	*tls_config;
496 	char			 port[6];
497 
498 	if (ldap->ldap_protocol == LDAPI) {
499 		memset(&un, 0, sizeof(un));
500 		un.sun_family = AF_UNIX;
501 		if (strlcpy(un.sun_path, ldap->ldap_host,
502 		    sizeof(un.sun_path)) >= sizeof(un.sun_path)) {
503 			log_warnx("socket '%s' too long", ldap->ldap_host);
504 			goto done;
505 		}
506 		if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ||
507 		    connect(fd, (struct sockaddr *)&un, sizeof(un)) == -1)
508 			goto done;
509 		goto init;
510 	}
511 
512 	memset(&ai, 0, sizeof(ai));
513 	ai.ai_family = AF_UNSPEC;
514 	ai.ai_socktype = SOCK_STREAM;
515 	ai.ai_protocol = IPPROTO_TCP;
516 	(void)snprintf(port, sizeof(port), "%u", ldap->ldap_port);
517 	if ((code = getaddrinfo(ldap->ldap_host, port,
518 	    &ai, &res0)) != 0) {
519 		log_warnx("%s", gai_strerror(code));
520 		goto done;
521 	}
522 	for (res = res0; res; res = res->ai_next, fd = -1) {
523 		if ((fd = socket(res->ai_family, res->ai_socktype,
524 		    res->ai_protocol)) == -1)
525 			continue;
526 
527 		if (connect(fd, res->ai_addr, res->ai_addrlen) >= 0)
528 			break;
529 
530 		saved_errno = errno;
531 		close(fd);
532 		errno = saved_errno;
533 	}
534 	freeaddrinfo(res0);
535 	if (fd == -1)
536 		goto done;
537 
538  init:
539 	if ((ldap->ldap_al = aldap_init(fd)) == NULL) {
540 		warn("LDAP init failed");
541 		close(fd);
542 		goto done;
543 	}
544 
545 	if (ldap->ldap_flags & F_STARTTLS) {
546 		log_debug("%s: requesting STARTTLS", __func__);
547 		if (aldap_req_starttls(ldap->ldap_al) == -1) {
548 			log_warnx("failed to request STARTTLS");
549 			goto done;
550 		}
551 
552 		if ((m = aldap_parse(ldap->ldap_al)) == NULL) {
553 			log_warnx("failed to parse STARTTLS response");
554 			goto done;
555 		}
556 
557 		if (ldap->ldap_al->msgid != m->msgid ||
558 		    (code = aldap_get_resultcode(m)) != LDAP_SUCCESS) {
559 			log_warnx("STARTTLS failed: %s(%d)",
560 			    ldapc_resultcode(code), code);
561 			aldap_freemsg(m);
562 			goto done;
563 		}
564 		aldap_freemsg(m);
565 	}
566 
567 	if (ldap->ldap_flags & (F_STARTTLS | F_TLS)) {
568 		log_debug("%s: starting TLS", __func__);
569 
570 		if ((tls_config = tls_config_new()) == NULL) {
571 			log_warnx("TLS config failed");
572 			goto done;
573 		}
574 
575 		if (tls_config_set_ca_file(tls_config,
576 		    ldap->ldap_capath) == -1) {
577 			log_warnx("unable to set CA %s", ldap->ldap_capath);
578 			goto done;
579 		}
580 
581 		if (aldap_tls(ldap->ldap_al, tls_config, ldap->ldap_host) < 0) {
582 			aldap_get_errno(ldap->ldap_al, &errstr);
583 			log_warnx("TLS failed: %s", errstr);
584 			goto done;
585 		}
586 	}
587 
588 	if (ldap->ldap_flags & F_NEEDAUTH) {
589 		log_debug("%s: bind request", __func__);
590 		if (aldap_bind(ldap->ldap_al, ldap->ldap_binddn,
591 		    ldap->ldap_secret) == -1) {
592 			log_warnx("bind request failed");
593 			goto done;
594 		}
595 
596 		if ((m = aldap_parse(ldap->ldap_al)) == NULL) {
597 			log_warnx("failed to parse bind response");
598 			goto done;
599 		}
600 
601 		if (ldap->ldap_al->msgid != m->msgid ||
602 		    (code = aldap_get_resultcode(m)) != LDAP_SUCCESS) {
603 			log_warnx("bind failed: %s(%d)",
604 			    ldapc_resultcode(code), code);
605 			aldap_freemsg(m);
606 			goto done;
607 		}
608 		aldap_freemsg(m);
609 	}
610 
611 	log_debug("%s: connected", __func__);
612 
613 	ret = 0;
614  done:
615 	if (ret != 0)
616 		ldapc_disconnect(ldap);
617 	if (ldap->ldap_secret != NULL)
618 		explicit_bzero(ldap->ldap_secret,
619 		    strlen(ldap->ldap_secret));
620 	return (ret);
621 }
622 
623 void
624 ldapc_disconnect(struct ldapc *ldap)
625 {
626 	if (ldap->ldap_al == NULL)
627 		return;
628 	aldap_close(ldap->ldap_al);
629 	ldap->ldap_al = NULL;
630 }
631 
632 const char *
633 ldapc_resultcode(enum result_code code)
634 {
635 #define CODE(_X)	case _X:return (#_X)
636 	switch (code) {
637 	CODE(LDAP_SUCCESS);
638 	CODE(LDAP_OPERATIONS_ERROR);
639 	CODE(LDAP_PROTOCOL_ERROR);
640 	CODE(LDAP_TIMELIMIT_EXCEEDED);
641 	CODE(LDAP_SIZELIMIT_EXCEEDED);
642 	CODE(LDAP_COMPARE_FALSE);
643 	CODE(LDAP_COMPARE_TRUE);
644 	CODE(LDAP_STRONG_AUTH_NOT_SUPPORTED);
645 	CODE(LDAP_STRONG_AUTH_REQUIRED);
646 	CODE(LDAP_REFERRAL);
647 	CODE(LDAP_ADMINLIMIT_EXCEEDED);
648 	CODE(LDAP_UNAVAILABLE_CRITICAL_EXTENSION);
649 	CODE(LDAP_CONFIDENTIALITY_REQUIRED);
650 	CODE(LDAP_SASL_BIND_IN_PROGRESS);
651 	CODE(LDAP_NO_SUCH_ATTRIBUTE);
652 	CODE(LDAP_UNDEFINED_TYPE);
653 	CODE(LDAP_INAPPROPRIATE_MATCHING);
654 	CODE(LDAP_CONSTRAINT_VIOLATION);
655 	CODE(LDAP_TYPE_OR_VALUE_EXISTS);
656 	CODE(LDAP_INVALID_SYNTAX);
657 	CODE(LDAP_NO_SUCH_OBJECT);
658 	CODE(LDAP_ALIAS_PROBLEM);
659 	CODE(LDAP_INVALID_DN_SYNTAX);
660 	CODE(LDAP_ALIAS_DEREF_PROBLEM);
661 	CODE(LDAP_INAPPROPRIATE_AUTH);
662 	CODE(LDAP_INVALID_CREDENTIALS);
663 	CODE(LDAP_INSUFFICIENT_ACCESS);
664 	CODE(LDAP_BUSY);
665 	CODE(LDAP_UNAVAILABLE);
666 	CODE(LDAP_UNWILLING_TO_PERFORM);
667 	CODE(LDAP_LOOP_DETECT);
668 	CODE(LDAP_NAMING_VIOLATION);
669 	CODE(LDAP_OBJECT_CLASS_VIOLATION);
670 	CODE(LDAP_NOT_ALLOWED_ON_NONLEAF);
671 	CODE(LDAP_NOT_ALLOWED_ON_RDN);
672 	CODE(LDAP_ALREADY_EXISTS);
673 	CODE(LDAP_NO_OBJECT_CLASS_MODS);
674 	CODE(LDAP_AFFECTS_MULTIPLE_DSAS);
675 	CODE(LDAP_OTHER);
676 	default:
677 		return ("UNKNOWN_ERROR");
678 	}
679 };
680 
681 int
682 ldapc_parseurl(struct ldapc *ldap, struct ldapc_search *ls, const char *url)
683 {
684 	struct aldap_url	*lu = &ldap->ldap_url;
685 	size_t			 i;
686 
687 	memset(lu, 0, sizeof(*lu));
688 	lu->scope = -1;
689 
690 	if (aldap_parse_url(url, lu) == -1) {
691 		log_warnx("failed to parse LDAP URL");
692 		return (-1);
693 	}
694 
695 	/* The protocol part is optional and we default to ldap:// */
696 	if (lu->protocol == -1)
697 		lu->protocol = LDAP;
698 	else if (lu->protocol == LDAPI) {
699 		if (lu->port != 0 ||
700 		    url_decode(lu->host) == NULL) {
701 			log_warnx("invalid ldapi:// URL");
702 			return (-1);
703 		}
704 	} else if ((ldap->ldap_flags & F_STARTTLS) &&
705 	    lu->protocol != LDAPTLS) {
706 		log_warnx("conflicting protocol arguments");
707 		return (-1);
708 	} else if (lu->protocol == LDAPTLS)
709 		ldap->ldap_flags |= F_TLS|F_STARTTLS;
710 	else if (lu->protocol == LDAPS)
711 		ldap->ldap_flags |= F_TLS;
712 	ldap->ldap_protocol = lu->protocol;
713 
714 	ldap->ldap_host = lu->host;
715 	if (lu->port)
716 		ldap->ldap_port = lu->port;
717 
718 	/* The distinguished name has to be URL-encoded */
719 	if (lu->dn != NULL && ls->ls_basedn != NULL &&
720 	    strcasecmp(ls->ls_basedn, lu->dn) != 0) {
721 		log_warnx("conflicting basedn arguments");
722 		return (-1);
723 	}
724 	if (lu->dn != NULL) {
725 		if (url_decode(lu->dn) == NULL)
726 			return (-1);
727 		ls->ls_basedn = lu->dn;
728 	}
729 
730 	if (lu->scope != -1) {
731 		if (ls->ls_scope != -1 && (ls->ls_scope != lu->scope)) {
732 			log_warnx("conflicting scope arguments");
733 			return (-1);
734 		}
735 		ls->ls_scope = lu->scope;
736 	}
737 
738 	/* URL-decode optional attributes and the search filter */
739 	if (lu->attributes[0] != NULL) {
740 		for (i = 0; i < MAXATTR && lu->attributes[i] != NULL; i++)
741 			if (url_decode(lu->attributes[i]) == NULL)
742 				return (-1);
743 		ls->ls_attr = lu->attributes;
744 	}
745 	if (lu->filter != NULL) {
746 		if (url_decode(lu->filter) == NULL)
747 			return (-1);
748 		ls->ls_filter = lu->filter;
749 	}
750 
751 	return (0);
752 }
753 
754 /* From usr.sbin/httpd/httpd.c */
755 const char *
756 url_decode(char *url)
757 {
758 	char		*p, *q;
759 	char		 hex[3];
760 	unsigned long	 x;
761 
762 	hex[2] = '\0';
763 	p = q = url;
764 
765 	while (*p != '\0') {
766 		switch (*p) {
767 		case '%':
768 			/* Encoding character is followed by two hex chars */
769 			if (!(isxdigit((unsigned char)p[1]) &&
770 			    isxdigit((unsigned char)p[2])))
771 				return (NULL);
772 
773 			hex[0] = p[1];
774 			hex[1] = p[2];
775 
776 			/*
777 			 * We don't have to validate "hex" because it is
778 			 * guaranteed to include two hex chars followed by nul.
779 			 */
780 			x = strtoul(hex, NULL, 16);
781 			*q = (char)x;
782 			p += 2;
783 			break;
784 		default:
785 			*q = *p;
786 			break;
787 		}
788 		p++;
789 		q++;
790 	}
791 	*q = '\0';
792 
793 	return (url);
794 }
795