xref: /openbsd-src/usr.bin/ldap/ldapclient.c (revision 832b780b4152848e2f144a06216506fff06f5005)
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