xref: /openbsd/usr.bin/ldap/ldapclient.c (revision 832b780b)
1 /*	$OpenBSD: ldapclient.c,v 1.13 2021/09/02 21:09:29 deraadt 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/queue.h>
20 #include <sys/socket.h>
21 #include <sys/stat.h>
22 #include <sys/tree.h>
23 #include <sys/un.h>
24 
25 #include <netinet/in.h>
26 #include <arpa/inet.h>
27 
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <stdint.h>
31 #include <unistd.h>
32 #include <ctype.h>
33 #include <err.h>
34 #include <errno.h>
35 #include <event.h>
36 #include <fcntl.h>
37 #include <limits.h>
38 #include <netdb.h>
39 #include <pwd.h>
40 #include <readpassphrase.h>
41 #include <resolv.h>
42 #include <signal.h>
43 #include <string.h>
44 #include <vis.h>
45 
46 #include "aldap.h"
47 #include "log.h"
48 
49 #define F_STARTTLS	0x01
50 #define F_TLS		0x02
51 #define F_NEEDAUTH	0x04
52 #define F_LDIF		0x08
53 
54 #define LDAPHOST	"localhost"
55 #define LDAPFILTER	"(objectClass=*)"
56 #define LDIF_LINELENGTH	79
57 #define LDAPPASSMAX	1024
58 
59 #define MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))
60 
61 struct ldapc {
62 	struct aldap		*ldap_al;
63 	char			*ldap_host;
64 	int			 ldap_port;
65 	const char		*ldap_capath;
66 	char			*ldap_binddn;
67 	char			*ldap_secret;
68 	unsigned int		 ldap_flags;
69 	enum protocol_op	 ldap_req;
70 	enum aldap_protocol	 ldap_protocol;
71 	struct aldap_url	 ldap_url;
72 };
73 
74 struct ldapc_search {
75 	int			 ls_sizelimit;
76 	int			 ls_timelimit;
77 	char			*ls_basedn;
78 	char			*ls_filter;
79 	int			 ls_scope;
80 	char			**ls_attr;
81 };
82 
83 __dead void	 usage(void);
84 int		 ldapc_connect(struct ldapc *);
85 int		 ldapc_search(struct ldapc *, struct ldapc_search *);
86 int		 ldapc_printattr(struct ldapc *, const char *,
87 		    const struct ber_octetstring *);
88 void		 ldapc_disconnect(struct ldapc *);
89 int		 ldapc_parseurl(struct ldapc *, struct ldapc_search *,
90 		    const char *);
91 const char	*ldapc_resultcode(enum result_code);
92 const char	*url_decode(char *);
93 
94 __dead void
usage(void)95 usage(void)
96 {
97 	extern char	*__progname;
98 
99 	fprintf(stderr,
100 "usage: %s search [-LvWxZ] [-b basedn] [-c CAfile] [-D binddn] [-H host]\n"
101 "	    [-l timelimit] [-s scope] [-w secret] [-y secretfile] [-z sizelimit]\n"
102 "	    [filter] [attributes ...]\n",
103 	    __progname);
104 
105 	exit(1);
106 }
107 
108 int
main(int argc,char * argv[])109 main(int argc, char *argv[])
110 {
111 	char			 passbuf[LDAPPASSMAX];
112 	const char		*errstr, *url = NULL, *secretfile = NULL;
113 	struct stat		 st;
114 	struct ldapc		 ldap;
115 	struct ldapc_search	 ls;
116 	int			 ch;
117 	int			 verbose = 1;
118 	FILE			*fp;
119 
120 	if (pledge("stdio inet unix tty rpath dns", NULL) == -1)
121 		err(1, "pledge");
122 
123 	log_init(verbose, 0);
124 
125 	memset(&ldap, 0, sizeof(ldap));
126 	memset(&ls, 0, sizeof(ls));
127 	ls.ls_scope = -1;
128 	ldap.ldap_port = -1;
129 
130 	/*
131 	 * Check the command.  Currently only "search" is supported but
132 	 * it could be extended with others such as add, modify, or delete.
133 	 */
134 	if (argc < 2)
135 		usage();
136 	else if (strcmp("search", argv[1]) == 0)
137 		ldap.ldap_req = LDAP_REQ_SEARCH;
138 	else
139 		usage();
140 	argc--;
141 	argv++;
142 
143 	while ((ch = getopt(argc, argv, "b:c:D:H:Ll:s:vWw:xy:Zz:")) != -1) {
144 		switch (ch) {
145 		case 'b':
146 			ls.ls_basedn = optarg;
147 			break;
148 		case 'c':
149 			ldap.ldap_capath = optarg;
150 			break;
151 		case 'D':
152 			ldap.ldap_binddn = optarg;
153 			ldap.ldap_flags |= F_NEEDAUTH;
154 			break;
155 		case 'H':
156 			url = optarg;
157 			break;
158 		case 'L':
159 			ldap.ldap_flags |= F_LDIF;
160 			break;
161 		case 'l':
162 			ls.ls_timelimit = strtonum(optarg, 0, INT_MAX,
163 			    &errstr);
164 			if (errstr != NULL)
165 				errx(1, "timelimit %s", errstr);
166 			break;
167 		case 's':
168 			if (strcasecmp("base", optarg) == 0)
169 				ls.ls_scope = LDAP_SCOPE_BASE;
170 			else if (strcasecmp("one", optarg) == 0)
171 				ls.ls_scope = LDAP_SCOPE_ONELEVEL;
172 			else if (strcasecmp("sub", optarg) == 0)
173 				ls.ls_scope = LDAP_SCOPE_SUBTREE;
174 			else
175 				errx(1, "invalid scope: %s", optarg);
176 			break;
177 		case 'v':
178 			verbose++;
179 			break;
180 		case 'w':
181 			ldap.ldap_secret = optarg;
182 			ldap.ldap_flags |= F_NEEDAUTH;
183 			break;
184 		case 'W':
185 			ldap.ldap_flags |= F_NEEDAUTH;
186 			break;
187 		case 'x':
188 			/* provided for compatibility */
189 			break;
190 		case 'y':
191 			secretfile = optarg;
192 			ldap.ldap_flags |= F_NEEDAUTH;
193 			break;
194 		case 'Z':
195 			ldap.ldap_flags |= F_STARTTLS;
196 			break;
197 		case 'z':
198 			ls.ls_sizelimit = strtonum(optarg, 0, INT_MAX,
199 			    &errstr);
200 			if (errstr != NULL)
201 				errx(1, "sizelimit %s", errstr);
202 			break;
203 		default:
204 			usage();
205 		}
206 	}
207 	argc -= optind;
208 	argv += optind;
209 
210 	log_setverbose(verbose);
211 
212 	if (url != NULL && ldapc_parseurl(&ldap, &ls, url) == -1)
213 		errx(1, "ldapurl");
214 
215 	/* Set the default after parsing URL and/or options */
216 	if (ldap.ldap_host == NULL)
217 		ldap.ldap_host = LDAPHOST;
218 	if (ldap.ldap_port == -1)
219 		ldap.ldap_port = ldap.ldap_protocol == LDAPS ?
220 		    LDAPS_PORT : LDAP_PORT;
221 	if (ldap.ldap_protocol == LDAP && (ldap.ldap_flags & F_STARTTLS))
222 		ldap.ldap_protocol = LDAPTLS;
223 	if (ldap.ldap_capath == NULL)
224 		ldap.ldap_capath = tls_default_ca_cert_file();
225 	if (ls.ls_basedn == NULL)
226 		ls.ls_basedn = "";
227 	if (ls.ls_scope == -1)
228 		ls.ls_scope = LDAP_SCOPE_SUBTREE;
229 	if (ls.ls_filter == NULL)
230 		ls.ls_filter = LDAPFILTER;
231 
232 	if (ldap.ldap_flags & F_NEEDAUTH) {
233 		if (ldap.ldap_binddn == NULL) {
234 			log_warnx("missing -D binddn");
235 			usage();
236 		}
237 		if (secretfile != NULL) {
238 			if (ldap.ldap_secret != NULL)
239 				errx(1, "conflicting -w/-y options");
240 
241 			/* read password from stdin or file (first line) */
242 			if (strcmp(secretfile, "-") == 0)
243 				fp = stdin;
244 			else if (stat(secretfile, &st) == -1)
245 				err(1, "failed to access %s", secretfile);
246 			else if (S_ISREG(st.st_mode) && (st.st_mode & S_IROTH))
247 				errx(1, "%s is world-readable", secretfile);
248 			else if ((fp = fopen(secretfile, "r")) == NULL)
249 				err(1, "failed to open %s", secretfile);
250 			if (fgets(passbuf, sizeof(passbuf), fp) == NULL)
251 				err(1, "failed to read %s", secretfile);
252 			if (fp != stdin)
253 				fclose(fp);
254 
255 			passbuf[strcspn(passbuf, "\n")] = '\0';
256 			ldap.ldap_secret = passbuf;
257 		}
258 		if (ldap.ldap_secret == NULL) {
259 			if (readpassphrase("Password: ",
260 			    passbuf, sizeof(passbuf), RPP_REQUIRE_TTY) == NULL)
261 				errx(1, "failed to read LDAP password");
262 			ldap.ldap_secret = passbuf;
263 		}
264 	}
265 
266 	if (pledge("stdio inet unix rpath dns", NULL) == -1)
267 		err(1, "pledge");
268 
269 	/* optional search filter */
270 	if (argc && strchr(argv[0], '=') != NULL) {
271 		ls.ls_filter = argv[0];
272 		argc--;
273 		argv++;
274 	}
275 	/* search attributes */
276 	if (argc)
277 		ls.ls_attr = argv;
278 
279 	if (ldapc_connect(&ldap) == -1)
280 		errx(1, "LDAP connection failed");
281 
282 	if (pledge("stdio", NULL) == -1)
283 		err(1, "pledge");
284 
285 	if (ldapc_search(&ldap, &ls) == -1)
286 		errx(1, "LDAP search failed");
287 
288 	ldapc_disconnect(&ldap);
289 	aldap_free_url(&ldap.ldap_url);
290 
291 	return (0);
292 }
293 
294 int
ldapc_search(struct ldapc * ldap,struct ldapc_search * ls)295 ldapc_search(struct ldapc *ldap, struct ldapc_search *ls)
296 {
297 	struct aldap_page_control	*pg = NULL;
298 	struct aldap_message		*m;
299 	const char			*errstr;
300 	const char			*searchdn, *dn = NULL;
301 	char				*outkey;
302 	struct aldap_stringset		*outvalues;
303 	int				 ret, code, fail = 0;
304 	size_t				 i;
305 
306 	if (ldap->ldap_flags & F_LDIF)
307 		printf("version: 1\n");
308 	do {
309 		if (aldap_search(ldap->ldap_al, ls->ls_basedn, ls->ls_scope,
310 		    ls->ls_filter, ls->ls_attr, 0, ls->ls_sizelimit,
311 		    ls->ls_timelimit, pg) == -1) {
312 			aldap_get_errno(ldap->ldap_al, &errstr);
313 			log_warnx("LDAP search failed: %s", errstr);
314 			return (-1);
315 		}
316 
317 		if (pg != NULL) {
318 			aldap_freepage(pg);
319 			pg = NULL;
320 		}
321 
322 		while ((m = aldap_parse(ldap->ldap_al)) != NULL) {
323 			if (ldap->ldap_al->msgid != m->msgid) {
324 				goto fail;
325 			}
326 
327 			if ((code = aldap_get_resultcode(m)) != LDAP_SUCCESS) {
328 				log_warnx("LDAP search failed: %s(%d)",
329 				    ldapc_resultcode(code), code);
330 				break;
331 			}
332 
333 			if (m->message_type == LDAP_RES_SEARCH_RESULT) {
334 				if (m->page != NULL && m->page->cookie_len != 0)
335 					pg = m->page;
336 				else
337 					pg = NULL;
338 
339 				aldap_freemsg(m);
340 				break;
341 			}
342 
343 			if (m->message_type != LDAP_RES_SEARCH_ENTRY) {
344 				goto fail;
345 			}
346 
347 			if (aldap_count_attrs(m) < 1) {
348 				aldap_freemsg(m);
349 				continue;
350 			}
351 
352 			if ((searchdn = aldap_get_dn(m)) == NULL)
353 				goto fail;
354 
355 			if (dn != NULL)
356 				printf("\n");
357 			else
358 				dn = ls->ls_basedn;
359 			if (strcmp(dn, searchdn) != 0)
360 				printf("dn: %s\n", searchdn);
361 
362 			for (ret = aldap_first_attr(m, &outkey, &outvalues);
363 			    ret != -1;
364 			    ret = aldap_next_attr(m, &outkey, &outvalues)) {
365 				for (i = 0; i < outvalues->len; i++) {
366 					if (ldapc_printattr(ldap, outkey,
367 					    &(outvalues->str[i])) == -1) {
368 						fail = 1;
369 						break;
370 					}
371 				}
372 			}
373 			free(outkey);
374 			aldap_free_attr(outvalues);
375 
376 			aldap_freemsg(m);
377 		}
378 	} while (pg != NULL && fail == 0);
379 
380 	if (fail)
381 		return (-1);
382 	return (0);
383  fail:
384 	ldapc_disconnect(ldap);
385 	return (-1);
386 }
387 
388 int
ldapc_printattr(struct ldapc * ldap,const char * key,const struct ber_octetstring * value)389 ldapc_printattr(struct ldapc *ldap, const char *key,
390     const struct ber_octetstring *value)
391 {
392 	char			*p = NULL, *out;
393 	const unsigned char	*cp;
394 	int			 encode;
395 	size_t			 i, inlen, outlen, left;
396 
397 	if (ldap->ldap_flags & F_LDIF) {
398 		/* OpenLDAP encodes the userPassword by default */
399 		if (strcasecmp("userPassword", key) == 0)
400 			encode = 1;
401 		else
402 			encode = 0;
403 
404 		/*
405 		 * The LDIF format a set of characters that can be included
406 		 * in SAFE-STRINGs. String value that do not match the
407 		 * criteria must be encoded as Base64.
408 		 */
409 		cp = (const unsigned char *)value->ostr_val;
410 		/* !SAFE-INIT-CHAR: SAFE-CHAR minus %x20 %x3A %x3C */
411 		if (*cp == ' ' ||
412 		    *cp == ':' ||
413 		    *cp == '<')
414 			encode = 1;
415 		for (i = 0; encode == 0 && i < value->ostr_len - 1; i++) {
416 			/* !SAFE-CHAR %x01-09 / %x0B-0C / %x0E-7F */
417 			if (cp[i] > 127 ||
418 			    cp[i] == '\0' ||
419 			    cp[i] == '\n' ||
420 			    cp[i] == '\r')
421 				encode = 1;
422 		}
423 
424 		if (!encode) {
425 			if (asprintf(&p, "%s: %s", key,
426 			    (const char *)value->ostr_val) == -1) {
427 				log_warnx("asprintf");
428 				return (-1);
429 			}
430 		} else {
431 			outlen = (((value->ostr_len + 2) / 3) * 4) + 1;
432 
433 			if ((out = calloc(1, outlen)) == NULL ||
434 			    b64_ntop(value->ostr_val, value->ostr_len, out,
435 			    outlen) == -1) {
436 				log_warnx("Base64 encoding failed");
437 				free(p);
438 				return (-1);
439 			}
440 
441 			/* Base64 is indicated with a double-colon */
442 			if (asprintf(&p, "%s:: %s", key, out) == -1) {
443 				log_warnx("asprintf");
444 				free(out);
445 				return (-1);
446 			}
447 			free(out);
448 		}
449 
450 		/* Wrap lines */
451 		for (outlen = 0, inlen = strlen(p);
452 		    outlen < inlen;
453 		    outlen += LDIF_LINELENGTH - 1) {
454 			if (outlen)
455 				putchar(' ');
456 			if (outlen > LDIF_LINELENGTH)
457 				outlen--;
458 			/* max. line length - newline - optional indent */
459 			left = MINIMUM(inlen - outlen, outlen ?
460 			    LDIF_LINELENGTH - 2 :
461 			    LDIF_LINELENGTH - 1);
462 			fwrite(p + outlen, left, 1, stdout);
463 			putchar('\n');
464 		}
465 	} else {
466 		/*
467 		 * Use vis(1) instead of base64 encoding of non-printable
468 		 * values.  This is much nicer as it always prdocues a
469 		 * human-readable visual output.  This can safely be done
470 		 * on all values no matter if they include non-printable
471 		 * characters.
472 		 */
473 		p = calloc(1, 4 * value->ostr_len + 1);
474 		if (strvisx(p, value->ostr_val, value->ostr_len,
475 		    VIS_SAFE|VIS_NL) == -1) {
476 			log_warn("visual encoding failed");
477 			return (-1);
478 		}
479 
480 		printf("%s: %s\n", key, p);
481 	}
482 
483 	free(p);
484 	return (0);
485 }
486 
487 int
ldapc_connect(struct ldapc * ldap)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
ldapc_disconnect(struct ldapc * ldap)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 *
ldapc_resultcode(enum result_code code)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
ldapc_parseurl(struct ldapc * ldap,struct ldapc_search * ls,const char * url)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 *
url_decode(char * url)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