1 /*	$NetBSD: dict_ldap.c,v 1.4 2022/10/08 16:12:45 christos Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	dict_ldap 3
6 /* SUMMARY
7 /*	dictionary manager interface to LDAP maps
8 /* SYNOPSIS
9 /*	#include <dict_ldap.h>
10 /*
11 /*	DICT    *dict_ldap_open(attribute, dummy, dict_flags)
12 /*	const char *ldapsource;
13 /*	int	dummy;
14 /*	int	dict_flags;
15 /* DESCRIPTION
16 /*	dict_ldap_open() makes LDAP user information accessible via
17 /*	the generic dictionary operations described in dict_open(3).
18 /*
19 /*	Arguments:
20 /* .IP ldapsource
21 /*	Either the path to the LDAP configuration file (if it starts
22 /*	with '/' or '.'), or the prefix which will be used to obtain
23 /*	configuration parameters for this search.
24 /*
25 /*	In the first case, the configuration variables below are
26 /*	specified in the file as \fBname\fR=\fBvalue\fR pairs.
27 /*
28 /*	In the second case, the configuration variables are prefixed
29 /*	with the value of \fIldapsource\fR and an underscore,
30 /*	and they are specified in main.cf.  For example, if this
31 /*	value is \fBldapone\fR, the variables would look like
32 /*	\fBldapone_server_host\fR, \fBldapone_search_base\fR, and so on.
33 /* .IP dummy
34 /*	Not used; this argument exists only for compatibility with
35 /*	the dict_open(3) interface.
36 /* .PP
37 /*	Configuration parameters:
38 /* .IP server_host
39 /*	List of hosts at which all LDAP queries are directed.
40 /*	The host names can also be LDAP URLs if the LDAP client library used
41 /*	is OpenLDAP.
42 /* .IP server_port
43 /*	The port the LDAP server listens on.
44 /* .IP search_base
45 /*	The LDAP search base, for example: \fIO=organization name, C=country\fR.
46 /* .IP domain
47 /*	If specified, only lookups ending in this value will be queried.
48 /*	This can significantly reduce the query load on the LDAP server.
49 /* .IP timeout
50 /*	Deadline for LDAP open() and LDAP search() .
51 /* .IP query_filter
52 /*	The search filter template used to search for directory entries,
53 /*	for example \fI(mailacceptinggeneralid=%s)\fR. See ldap_table(5)
54 /*	for details.
55 /* .IP result_format
56 /*	The result template used to expand results from queries. Default
57 /*	is \fI%s\fR. See ldap_table(5) for details. Also supported under
58 /*	the name \fIresult_filter\fR for compatibility with older releases.
59 /* .IP result_attribute
60 /*	The attribute(s) returned by the search, in which to find
61 /*	RFC822 addresses, for example \fImaildrop\fR.
62 /* .IP special_result_attribute
63 /*	The attribute(s) of directory entries that can contain DNs or URLs.
64 /*	If found, a recursive subsequent search is done using their values.
65 /* .IP leaf_result_attribute
66 /*	These are only returned for "leaf" LDAP entries, i.e. those that are
67 /*	not "terminal" and have no values for any of the "special" result
68 /*	attributes.
69 /* .IP terminal_result_attribute
70 /*	If found, the LDAP entry is considered a terminal LDAP object, not
71 /*	subject to further direct or recursive expansion. Only the terminal
72 /*	result attributes are returned.
73 /* .IP scope
74 /*	LDAP search scope: sub, base, or one.
75 /* .IP bind
76 /*	Whether or not to bind to the server -- LDAP v3 implementations don't
77 /*	require it, which saves some overhead.
78 /* .IP bind_dn
79 /*	If you must bind to the server, do it with this distinguished name ...
80 /* .IP bind_pw
81 /*	\&... and this password.
82 /* .IP cache (no longer supported)
83 /*	Whether or not to turn on client-side caching.
84 /* .IP cache_expiry (no longer supported)
85 /*	If you do cache results, expire them after this many seconds.
86 /* .IP cache_size (no longer supported)
87 /*	The cache size in bytes. Does nothing if the cache is off, of course.
88 /* .IP recursion_limit
89 /*	Maximum recursion depth when expanding DN or URL references.
90 /*	Queries which exceed the recursion limit fail with
91 /*	dict->error = DICT_ERR_RETRY.
92 /* .IP expansion_limit
93 /*	Limit (if any) on the total number of lookup result values. Lookups which
94 /*	exceed the limit fail with dict->error=DICT_ERR_RETRY. Note that
95 /*	each value of a multivalued result attribute counts as one result.
96 /* .IP size_limit
97 /*	Limit on the number of entries returned by individual LDAP queries.
98 /*	Queries which exceed the limit fail with dict->error=DICT_ERR_RETRY.
99 /*	This is an *entry* count, for any single query performed during the
100 /*	possibly recursive lookup.
101 /* .IP chase_referrals
102 /*	Controls whether LDAP referrals are obeyed.
103 /* .IP dereference
104 /*	How to handle LDAP aliases. See ldap.h or ldap_open(3) man page.
105 /* .IP version
106 /*	Specifies the LDAP protocol version to use.  Default is version
107 /*	\fI2\fR.
108 /* .IP "\fBsasl_mechs (empty)\fR"
109 /*	Specifies a space-separated list of LDAP SASL Mechanisms.
110 /* .IP "\fBsasl_realm (empty)\fR"
111 /*	The realm to use for SASL binds.
112 /* .IP "\fBsasl_authz_id (empty)\fR"
113 /*	The SASL Authorization Identity to assert.
114 /* .IP "\fBsasl_minssf (0)\fR"
115 /*	The minimum SASL SSF to allow.
116 /* .IP start_tls
117 /*	Whether or not to issue STARTTLS upon connection to the server.
118 /*	At this time, STARTTLS and LDAP SSL are only available if the
119 /*	LDAP client library used is OpenLDAP.  Default is \fIno\fR.
120 /* .IP tls_ca_cert_file
121 /*	File containing certificates for all of the X509 Certification
122 /*	Authorities the client will recognize.  Takes precedence over
123 /*	tls_ca_cert_dir.
124 /* .IP tls_ca_cert_dir
125 /*	Directory containing X509 Certification Authority certificates
126 /*	in separate individual files.
127 /* .IP tls_cert
128 /*	File containing client's X509 certificate.
129 /* .IP tls_key
130 /*	File containing the private key corresponding to
131 /*	tls_cert.
132 /* .IP tls_require_cert
133 /*	Whether or not to request server's X509 certificate and check its
134 /*	validity. The value "no" means don't check the cert trust chain
135 /*	and (OpenLDAP 2.1+) don't check the peername. The value "yes" means
136 /*	check both the trust chain and the peername (with OpenLDAP <= 2.0.11,
137 /*	the peername checks use the reverse hostname from the LDAP servers's
138 /*	IP address, not the user supplied servername).
139 /* .IP tls_random_file
140 /*	Path of a file to obtain random bits from when /dev/[u]random is
141 /*	not available. Generally set to the name of the EGD/PRNGD socket.
142 /* .IP tls_cipher_suite
143 /*	Cipher suite to use in SSL/TLS negotiations.
144 /* .IP debuglevel
145 /*	Debug level.  See 'loglevel' option in slapd.conf(5) man page.
146 /*	Currently only in openldap libraries (and derivatives).
147 /* SEE ALSO
148 /*	dict(3) generic dictionary manager
149 /* AUTHOR(S)
150 /*	Prabhat K Singh
151 /*	VSNL, Bombay, India.
152 /*	prabhat@giasbm01.vsnl.net.in
153 /*
154 /*	Wietse Venema
155 /*	IBM T.J. Watson Research
156 /*	P.O. Box 704
157 /*	Yorktown Heights, NY 10598, USA
158 /*
159 /*	Wietse Venema
160 /*	Google, Inc.
161 /*	111 8th Avenue
162 /*	New York, NY 10011, USA
163 /*
164 /*	John Hensley
165 /*	john@sunislelodge.com
166 /*
167 /*	Current maintainers:
168 /*
169 /*	LaMont Jones
170 /*	lamont@debian.org
171 /*
172 /*	Victor Duchovni
173 /*	Morgan Stanley
174 /*	New York, USA
175 /*
176 /*	Liviu Daia
177 /*	Institute of Mathematics of the Romanian Academy
178 /*	P.O. BOX 1-764
179 /*	RO-014700 Bucharest, ROMANIA
180 /*--*/
181 
182 /* System library. */
183 
184 #include "sys_defs.h"
185 
186 #ifdef HAS_LDAP
187 
188 #include <sys/time.h>
189 #include <stdio.h>
190 #include <signal.h>
191 #include <setjmp.h>
192 #include <stdlib.h>
193 #include <lber.h>
194 #include <ldap.h>
195 #include <string.h>
196 #include <ctype.h>
197 #include <unistd.h>
198 
199 #ifdef STRCASECMP_IN_STRINGS_H
200 #include <strings.h>
201 #endif
202 
203  /*
204   * Older APIs have weird memory freeing behavior.
205   */
206 #if !defined(LDAP_API_VERSION) || (LDAP_API_VERSION < 2000)
207 #error "Your LDAP version is too old"
208 #endif
209 
210 /* Handle differences between LDAP SDK's constant definitions */
211 #ifndef LDAP_CONST
212 #define LDAP_CONST const
213 #endif
214 #ifndef LDAP_OPT_SUCCESS
215 #define LDAP_OPT_SUCCESS 0
216 #endif
217 
218 /* Utility library. */
219 
220 #include <msg.h>
221 #include <mymalloc.h>
222 #include <vstring.h>
223 #include <dict.h>
224 #include <stringops.h>
225 #include <binhash.h>
226 #include <name_code.h>
227 
228 /* Global library. */
229 
230 #include "cfg_parser.h"
231 #include "db_common.h"
232 #include "mail_conf.h"
233 
234 #if defined(USE_LDAP_SASL) && defined(LDAP_API_FEATURE_X_OPENLDAP)
235 
236  /*
237   * SASL headers, for sasl_interact_t. Either SASL v1 or v2 should be fine.
238   */
239 #include <sasl.h>
240 #endif
241 
242 /* Application-specific. */
243 
244 #include "dict_ldap.h"
245 
246 #define DICT_LDAP_BIND_NONE	0
247 #define DICT_LDAP_BIND_SIMPLE	1
248 #define DICT_LDAP_BIND_SASL	2
249 #define DICT_LDAP_DO_BIND(d)	((d)->bind != DICT_LDAP_BIND_NONE)
250 #define DICT_LDAP_DO_SASL(d)	((d)->bind == DICT_LDAP_BIND_SASL)
251 
252 static const NAME_CODE bindopt_table[] = {
253     CONFIG_BOOL_NO, DICT_LDAP_BIND_NONE,
254     "none", DICT_LDAP_BIND_NONE,
255     CONFIG_BOOL_YES, DICT_LDAP_BIND_SIMPLE,
256     "simple", DICT_LDAP_BIND_SIMPLE,
257 #ifdef LDAP_API_FEATURE_X_OPENLDAP
258 #if defined(USE_LDAP_SASL)
259     "sasl", DICT_LDAP_BIND_SASL,
260 #endif
261 #endif
262     0, -1,
263 };
264 
265 typedef struct {
266     LDAP   *conn_ld;
267     int     conn_refcount;
268 } LDAP_CONN;
269 
270 /*
271  * Structure containing all the configuration parameters for a given
272  * LDAP source, plus its connection handle.
273  */
274 typedef struct {
275     DICT    dict;			/* generic member */
276     CFG_PARSER *parser;			/* common parameter parser */
277     char   *query;			/* db_common_expand() query */
278     char   *result_format;		/* db_common_expand() result_format */
279     void   *ctx;			/* db_common_parse() context */
280     int     dynamic_base;		/* Search base has substitutions? */
281     int     expansion_limit;
282     char   *server_host;
283     int     server_port;
284     int     scope;
285     char   *search_base;
286     ARGV   *result_attributes;
287     int     num_terminal;		/* Number of terminal attributes. */
288     int     num_leaf;			/* Number of leaf attributes */
289     int     num_attributes;		/* Combined # of non-special attrs */
290     int     bind;
291     char   *bind_dn;
292     char   *bind_pw;
293     int     timeout;
294     int     dereference;
295     long    recursion_limit;
296     long    size_limit;
297     int     chase_referrals;
298     int     debuglevel;
299     int     version;
300 #ifdef LDAP_API_FEATURE_X_OPENLDAP
301 #if defined(USE_LDAP_SASL)
302     int     sasl;
303     char   *sasl_mechs;
304     char   *sasl_realm;
305     char   *sasl_authz;
306     int     sasl_minssf;
307 #endif
308     int     ldap_ssl;
309     int     start_tls;
310     int     tls_require_cert;
311     char   *tls_ca_cert_file;
312     char   *tls_ca_cert_dir;
313     char   *tls_cert;
314     char   *tls_key;
315     char   *tls_random_file;
316     char   *tls_cipher_suite;
317 #endif
318     BINHASH_INFO *ht;			/* hash entry for LDAP connection */
319     LDAP   *ld;				/* duplicated from conn->conn_ld */
320 } DICT_LDAP;
321 
322 #define DICT_LDAP_CONN(d) ((LDAP_CONN *)((d)->ht->value))
323 
324 #define DICT_LDAP_UNBIND_RETURN(__ld, __err, __ret) do { \
325 	dict_ldap_unbind(__ld); \
326 	(__ld) = 0; \
327 	dict_ldap->dict.error = (__err); \
328 	return ((__ret)); \
329     } while (0)
330 
331  /*
332   * Bitrot: LDAP_API 3000 and up (OpenLDAP 2.2.x) deprecated ldap_unbind()
333   */
334 #if LDAP_API_VERSION >= 3000
335 #define dict_ldap_unbind(ld)		ldap_unbind_ext((ld), 0, 0)
336 #define dict_ldap_abandon(ld, msg)	ldap_abandon_ext((ld), (msg), 0, 0)
337 #else
338 #define dict_ldap_unbind(ld)		ldap_unbind(ld)
339 #define dict_ldap_abandon(ld, msg)	ldap_abandon((ld), (msg))
340 #endif
341 
dict_ldap_vendor_version(void)342 static int dict_ldap_vendor_version(void)
343 {
344     const char *myname = "dict_ldap_api_info";
345     LDAPAPIInfo api;
346 
347     /*
348      * We tell the library our version, and it tells us its version and/or
349      * may return an error code if the versions are not the same.
350      */
351     api.ldapai_info_version = LDAP_API_INFO_VERSION;
352     if (ldap_get_option(0, LDAP_OPT_API_INFO, &api) != LDAP_SUCCESS
353 	|| api.ldapai_info_version != LDAP_API_INFO_VERSION) {
354 	if (api.ldapai_info_version != LDAP_API_INFO_VERSION)
355 	    msg_fatal("%s: run-time API_INFO version: %d, compiled with: %d",
356 		    myname, api.ldapai_info_version, LDAP_API_INFO_VERSION);
357 	else
358 	    msg_fatal("%s: ldap_get_option(API_INFO) failed", myname);
359     }
360     if (strcmp(api.ldapai_vendor_name, LDAP_VENDOR_NAME) != 0)
361 	msg_fatal("%s: run-time API vendor: %s, compiled with: %s",
362 		  myname, api.ldapai_vendor_name, LDAP_VENDOR_NAME);
363 
364     return (api.ldapai_vendor_version);
365 }
366 
367 /*
368  * Quoting rules.
369  */
370 
371 /* rfc2253_quote - Quote input key for safe inclusion in the search base */
372 
rfc2253_quote(DICT * unused,const char * name,VSTRING * result)373 static void rfc2253_quote(DICT *unused, const char *name, VSTRING *result)
374 {
375     const char *sub = name;
376     size_t  len;
377 
378     /*
379      * The RFC only requires quoting of a leading or trailing space, but it
380      * is harmless to quote whitespace everywhere. Similarly, we quote all
381      * '#' characters, even though only the leading '#' character requires
382      * quoting per the RFC.
383      */
384     while (*sub)
385 	if ((len = strcspn(sub, " \t\"#+,;<>\\")) > 0) {
386 	    vstring_strncat(result, sub, len);
387 	    sub += len;
388 	} else
389 	    vstring_sprintf_append(result, "\\%02X",
390 				   *((const unsigned char *) sub++));
391 }
392 
393 /* rfc2254_quote - Quote input key for safe inclusion in the query filter */
394 
rfc2254_quote(DICT * unused,const char * name,VSTRING * result)395 static void rfc2254_quote(DICT *unused, const char *name, VSTRING *result)
396 {
397     const char *sub = name;
398     size_t  len;
399 
400     /*
401      * If any characters in the supplied address should be escaped per RFC
402      * 2254, do so. Thanks to Keith Stevenson and Wietse. And thanks to
403      * Samuel Tardieu for spotting that wildcard searches were being done in
404      * the first place, which prompted the ill-conceived lookup_wildcards
405      * parameter and then this more comprehensive mechanism.
406      */
407     while (*sub)
408 	if ((len = strcspn(sub, "*()\\")) > 0) {
409 	    vstring_strncat(result, sub, len);
410 	    sub += len;
411 	} else
412 	    vstring_sprintf_append(result, "\\%02X",
413 				   *((const unsigned char *) sub++));
414 }
415 
416 static BINHASH *conn_hash = 0;
417 
418 #if defined(LDAP_API_FEATURE_X_OPENLDAP) || !defined(LDAP_OPT_NETWORK_TIMEOUT)
419 /*
420  * LDAP connection timeout support.
421  */
422 static jmp_buf env;
423 
dict_ldap_timeout(int unused_sig)424 static void dict_ldap_timeout(int unused_sig)
425 {
426     longjmp(env, 1);
427 }
428 
429 #endif
430 
dict_ldap_logprint(LDAP_CONST char * data)431 static void dict_ldap_logprint(LDAP_CONST char *data)
432 {
433     const char *myname = "dict_ldap_debug";
434     char   *buf, *p;
435 
436     buf = mystrdup(data);
437     if (*buf) {
438 	p = buf + strlen(buf) - 1;
439 	while (p - buf >= 0 && ISSPACE(*p))
440 	    *p-- = 0;
441     }
442     msg_info("%s: %s", myname, buf);
443     myfree(buf);
444 }
445 
dict_ldap_get_errno(LDAP * ld)446 static int dict_ldap_get_errno(LDAP *ld)
447 {
448     int     rc;
449 
450     if (ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &rc) != LDAP_OPT_SUCCESS)
451 	rc = LDAP_OTHER;
452     return rc;
453 }
454 
dict_ldap_set_errno(LDAP * ld,int rc)455 static int dict_ldap_set_errno(LDAP *ld, int rc)
456 {
457     (void) ldap_set_option(ld, LDAP_OPT_ERROR_NUMBER, &rc);
458     return rc;
459 }
460 
461 #if defined(USE_LDAP_SASL) && defined(LDAP_API_FEATURE_X_OPENLDAP)
462 
463  /*
464   * Context structure for SASL property callback.
465   */
466 typedef struct bind_props {
467     char   *authcid;
468     char   *passwd;
469     char   *realm;
470     char   *authzid;
471 } bind_props;
472 
ldap_b2_interact(LDAP * ld,unsigned flags,void * props,void * inter)473 static int ldap_b2_interact(LDAP *ld, unsigned flags, void *props, void *inter)
474 {
475 
476     sasl_interact_t *in;
477     bind_props *ctx = (bind_props *) props;
478 
479     for (in = inter; in->id != SASL_CB_LIST_END; in++) {
480 	in->result = NULL;
481 	switch (in->id) {
482 	case SASL_CB_GETREALM:
483 	    in->result = ctx->realm;
484 	    break;
485 	case SASL_CB_AUTHNAME:
486 	    in->result = ctx->authcid;
487 	    break;
488 	case SASL_CB_USER:
489 	    in->result = ctx->authzid;
490 	    break;
491 	case SASL_CB_PASS:
492 	    in->result = ctx->passwd;
493 	    break;
494 	}
495 	if (in->result)
496 	    in->len = strlen(in->result);
497     }
498     return LDAP_SUCCESS;
499 }
500 
501 #endif
502 
503 /* dict_ldap_result - Read and parse LDAP result */
504 
dict_ldap_result(LDAP * ld,int msgid,int timeout,LDAPMessage ** res)505 static int dict_ldap_result(LDAP *ld, int msgid, int timeout, LDAPMessage **res)
506 {
507     struct timeval mytimeval;
508     int     err;
509 
510     mytimeval.tv_sec = timeout;
511     mytimeval.tv_usec = 0;
512 
513 #define GET_ALL 1
514     if (ldap_result(ld, msgid, GET_ALL, &mytimeval, res) == -1)
515 	return (dict_ldap_get_errno(ld));
516 
517     if ((err = dict_ldap_get_errno(ld)) != LDAP_SUCCESS) {
518 	if (err == LDAP_TIMEOUT) {
519 	    (void) dict_ldap_abandon(ld, msgid);
520 	    return (dict_ldap_set_errno(ld, LDAP_TIMEOUT));
521 	}
522 	return err;
523     }
524     return LDAP_SUCCESS;
525 }
526 
527 #if defined(USE_LDAP_SASL) && defined(LDAP_API_FEATURE_X_OPENLDAP)
528 
529 /* Asynchronous SASL auth if SASL is enabled */
530 
dict_ldap_bind_sasl(DICT_LDAP * dict_ldap)531 static int dict_ldap_bind_sasl(DICT_LDAP *dict_ldap)
532 {
533     int     rc;
534     bind_props props;
535     static VSTRING *minssf = 0;
536 
537     if (minssf == 0)
538 	minssf = vstring_alloc(12);
539 
540     vstring_sprintf(minssf, "minssf=%d", dict_ldap->sasl_minssf);
541 
542     if ((rc = ldap_set_option(dict_ldap->ld, LDAP_OPT_X_SASL_SECPROPS,
543 			      (char *) minssf)) != LDAP_OPT_SUCCESS)
544 	return (rc);
545 
546     props.authcid = dict_ldap->bind_dn;
547     props.passwd = dict_ldap->bind_pw;
548     props.realm = dict_ldap->sasl_realm;
549     props.authzid = dict_ldap->sasl_authz;
550 
551     if ((rc = ldap_sasl_interactive_bind_s(dict_ldap->ld, NULL,
552 					   dict_ldap->sasl_mechs, NULL, NULL,
553 					   LDAP_SASL_QUIET, ldap_b2_interact,
554 					   &props)) != LDAP_SUCCESS)
555 	return (rc);
556 
557     return (LDAP_SUCCESS);
558 }
559 
560 #endif
561 
562 /* dict_ldap_bind_st - Synchronous simple auth with timeout */
563 
dict_ldap_bind_st(DICT_LDAP * dict_ldap)564 static int dict_ldap_bind_st(DICT_LDAP *dict_ldap)
565 {
566     int     rc;
567     int     err = LDAP_SUCCESS;
568     int     msgid;
569     LDAPMessage *res;
570     struct berval cred;
571 
572     cred.bv_val = dict_ldap->bind_pw;
573     cred.bv_len = strlen(cred.bv_val);
574     if ((rc = ldap_sasl_bind(dict_ldap->ld, dict_ldap->bind_dn,
575 			     LDAP_SASL_SIMPLE, &cred,
576 			     0, 0, &msgid)) != LDAP_SUCCESS)
577 	return (rc);
578     if ((rc = dict_ldap_result(dict_ldap->ld, msgid, dict_ldap->timeout,
579 			       &res)) != LDAP_SUCCESS)
580 	return (rc);
581 
582 #define FREE_RESULT 1
583     rc = ldap_parse_result(dict_ldap->ld, res, &err, 0, 0, 0, 0, FREE_RESULT);
584     return (rc == LDAP_SUCCESS ? err : rc);
585 }
586 
587 /* search_st - Synchronous search with timeout */
588 
search_st(LDAP * ld,char * base,int scope,char * query,char ** attrs,int timeout,LDAPMessage ** res)589 static int search_st(LDAP *ld, char *base, int scope, char *query,
590 		             char **attrs, int timeout, LDAPMessage **res)
591 {
592     struct timeval mytimeval;
593     int     msgid;
594     int     rc;
595     int     err;
596 
597     mytimeval.tv_sec = timeout;
598     mytimeval.tv_usec = 0;
599 
600 #define WANTVALS 0
601 #define USE_SIZE_LIM_OPT -1			/* Any negative value will do */
602 
603     if ((rc = ldap_search_ext(ld, base, scope, query, attrs, WANTVALS, 0, 0,
604 			      &mytimeval, USE_SIZE_LIM_OPT,
605 			      &msgid)) != LDAP_SUCCESS)
606 	return rc;
607 
608     if ((rc = dict_ldap_result(ld, msgid, timeout, res)) != LDAP_SUCCESS)
609 	return (rc);
610 
611 #define DONT_FREE_RESULT 0
612     rc = ldap_parse_result(ld, *res, &err, 0, 0, 0, 0, DONT_FREE_RESULT);
613     return (err != LDAP_SUCCESS ? err : rc);
614 }
615 
616 #ifdef LDAP_API_FEATURE_X_OPENLDAP
dict_ldap_set_tls_options(DICT_LDAP * dict_ldap)617 static int dict_ldap_set_tls_options(DICT_LDAP *dict_ldap)
618 {
619     const char *myname = "dict_ldap_set_tls_options";
620     int     rc;
621 
622 #ifdef LDAP_OPT_X_TLS_NEWCTX
623     int     am_server = 0;
624     LDAP   *ld = dict_ldap->ld;
625 
626 #else
627     LDAP   *ld = 0;
628 
629 #endif
630 
631     if (dict_ldap->start_tls || dict_ldap->ldap_ssl) {
632 	if (*dict_ldap->tls_random_file) {
633 	    if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_RANDOM_FILE,
634 			     dict_ldap->tls_random_file)) != LDAP_SUCCESS) {
635 		msg_warn("%s: Unable to set tls_random_file to %s: %d: %s",
636 			 myname, dict_ldap->tls_random_file,
637 			 rc, ldap_err2string(rc));
638 		return (-1);
639 	    }
640 	}
641 	if (*dict_ldap->tls_ca_cert_file) {
642 	    if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTFILE,
643 			    dict_ldap->tls_ca_cert_file)) != LDAP_SUCCESS) {
644 		msg_warn("%s: Unable to set tls_ca_cert_file to %s: %d: %s",
645 			 myname, dict_ldap->tls_ca_cert_file,
646 			 rc, ldap_err2string(rc));
647 		return (-1);
648 	    }
649 	}
650 	if (*dict_ldap->tls_ca_cert_dir) {
651 	    if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_CACERTDIR,
652 			     dict_ldap->tls_ca_cert_dir)) != LDAP_SUCCESS) {
653 		msg_warn("%s: Unable to set tls_ca_cert_dir to %s: %d: %s",
654 			 myname, dict_ldap->tls_ca_cert_dir,
655 			 rc, ldap_err2string(rc));
656 		return (-1);
657 	    }
658 	}
659 	if (*dict_ldap->tls_cert) {
660 	    if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_CERTFILE,
661 				    dict_ldap->tls_cert)) != LDAP_SUCCESS) {
662 		msg_warn("%s: Unable to set tls_cert to %s: %d: %s",
663 			 myname, dict_ldap->tls_cert,
664 			 rc, ldap_err2string(rc));
665 		return (-1);
666 	    }
667 	}
668 	if (*dict_ldap->tls_key) {
669 	    if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_KEYFILE,
670 				      dict_ldap->tls_key)) != LDAP_SUCCESS) {
671 		msg_warn("%s: Unable to set tls_key to %s: %d: %s",
672 			 myname, dict_ldap->tls_key,
673 			 rc, ldap_err2string(rc));
674 		return (-1);
675 	    }
676 	}
677 	if (*dict_ldap->tls_cipher_suite) {
678 	    if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_CIPHER_SUITE,
679 			    dict_ldap->tls_cipher_suite)) != LDAP_SUCCESS) {
680 		msg_warn("%s: Unable to set tls_cipher_suite to %s: %d: %s",
681 			 myname, dict_ldap->tls_cipher_suite,
682 			 rc, ldap_err2string(rc));
683 		return (-1);
684 	    }
685 	}
686 	if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT,
687 			 &(dict_ldap->tls_require_cert))) != LDAP_SUCCESS) {
688 	    msg_warn("%s: Unable to set tls_require_cert to %d: %d: %s",
689 		     myname, dict_ldap->tls_require_cert,
690 		     rc, ldap_err2string(rc));
691 	    return (-1);
692 	}
693 #ifdef LDAP_OPT_X_TLS_NEWCTX
694 	if ((rc = ldap_set_option(ld, LDAP_OPT_X_TLS_NEWCTX, &am_server))
695 	    != LDAP_SUCCESS) {
696 	    msg_warn("%s: Unable to allocate new TLS context %d: %s",
697 		     myname, rc, ldap_err2string(rc));
698 	    return (-1);
699 	}
700 #endif
701     }
702     return (0);
703 }
704 
705 #endif
706 
707 /* Establish a connection to the LDAP server. */
dict_ldap_connect(DICT_LDAP * dict_ldap)708 static int dict_ldap_connect(DICT_LDAP *dict_ldap)
709 {
710     const char *myname = "dict_ldap_connect";
711     int     rc = 0;
712 
713 #ifdef LDAP_OPT_NETWORK_TIMEOUT
714     struct timeval mytimeval;
715 
716 #endif
717 
718 #if defined(LDAP_API_FEATURE_X_OPENLDAP) || !defined(LDAP_OPT_NETWORK_TIMEOUT)
719     void    (*saved_alarm) (int);
720 
721 #endif
722 
723 #if defined(LDAP_OPT_DEBUG_LEVEL) && defined(LBER_OPT_LOG_PRINT_FN)
724     if (dict_ldap->debuglevel > 0 &&
725 	ber_set_option(NULL, LBER_OPT_LOG_PRINT_FN,
726 		(LDAP_CONST void *) dict_ldap_logprint) != LBER_OPT_SUCCESS)
727 	msg_warn("%s: Unable to set ber logprint function.", myname);
728 #if defined(LBER_OPT_DEBUG_LEVEL)
729     if (ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL,
730 		       &(dict_ldap->debuglevel)) != LBER_OPT_SUCCESS)
731 	msg_warn("%s: Unable to set BER debug level.", myname);
732 #endif
733     if (ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL,
734 			&(dict_ldap->debuglevel)) != LDAP_OPT_SUCCESS)
735 	msg_warn("%s: Unable to set LDAP debug level.", myname);
736 #endif
737 
738     dict_ldap->dict.error = 0;
739 
740     if (msg_verbose)
741 	msg_info("%s: Connecting to server %s", myname,
742 		 dict_ldap->server_host);
743 
744 #ifdef LDAP_OPT_NETWORK_TIMEOUT
745 #ifdef LDAP_API_FEATURE_X_OPENLDAP
746     ldap_initialize(&(dict_ldap->ld), dict_ldap->server_host);
747 #else
748     dict_ldap->ld = ldap_init(dict_ldap->server_host,
749 			      (int) dict_ldap->server_port);
750 #endif
751     if (dict_ldap->ld == NULL) {
752 	msg_warn("%s: Unable to init LDAP server %s",
753 		 myname, dict_ldap->server_host);
754 	dict_ldap->dict.error = DICT_ERR_RETRY;
755 	return (-1);
756     }
757     mytimeval.tv_sec = dict_ldap->timeout;
758     mytimeval.tv_usec = 0;
759     if (ldap_set_option(dict_ldap->ld, LDAP_OPT_NETWORK_TIMEOUT, &mytimeval) !=
760 	LDAP_OPT_SUCCESS) {
761 	msg_warn("%s: Unable to set network timeout.", myname);
762 	DICT_LDAP_UNBIND_RETURN(dict_ldap->ld, DICT_ERR_RETRY, -1);
763     }
764 #else
765     if ((saved_alarm = signal(SIGALRM, dict_ldap_timeout)) == SIG_ERR) {
766 	msg_warn("%s: Error setting signal handler for open timeout: %m",
767 		 myname);
768 	dict_ldap->dict.error = DICT_ERR_RETRY;
769 	return (-1);
770     }
771     alarm(dict_ldap->timeout);
772     if (setjmp(env) == 0)
773 	dict_ldap->ld = ldap_open(dict_ldap->server_host,
774 				  (int) dict_ldap->server_port);
775     else
776 	dict_ldap->ld = 0;
777     alarm(0);
778 
779     if (signal(SIGALRM, saved_alarm) == SIG_ERR) {
780 	msg_warn("%s: Error resetting signal handler after open: %m",
781 		 myname);
782 	dict_ldap->dict.error = DICT_ERR_RETRY;
783 	return (-1);
784     }
785     if (dict_ldap->ld == NULL) {
786 	msg_warn("%s: Unable to connect to LDAP server %s",
787 		 myname, dict_ldap->server_host);
788 	dict_ldap->dict.error = DICT_ERR_RETRY;
789 	return (-1);
790     }
791 #endif
792 
793     /*
794      * v3 support is needed for referral chasing.  Thanks to Sami Haahtinen
795      * for the patch.
796      */
797 #ifdef LDAP_OPT_PROTOCOL_VERSION
798     if (ldap_set_option(dict_ldap->ld, LDAP_OPT_PROTOCOL_VERSION,
799 			&dict_ldap->version) != LDAP_OPT_SUCCESS) {
800 	msg_warn("%s: Unable to set LDAP protocol version", myname);
801 	DICT_LDAP_UNBIND_RETURN(dict_ldap->ld, DICT_ERR_RETRY, -1);
802     }
803     if (msg_verbose) {
804 	if (ldap_get_option(dict_ldap->ld,
805 			    LDAP_OPT_PROTOCOL_VERSION,
806 			    &dict_ldap->version) != LDAP_OPT_SUCCESS)
807 	    msg_warn("%s: Unable to get LDAP protocol version", myname);
808 	else
809 	    msg_info("%s: Actual Protocol version used is %d.",
810 		     myname, dict_ldap->version);
811     }
812 #endif
813 
814     /*
815      * Limit the number of entries returned by each query.
816      */
817     if (dict_ldap->size_limit) {
818 	if (ldap_set_option(dict_ldap->ld, LDAP_OPT_SIZELIMIT,
819 			    &dict_ldap->size_limit) != LDAP_OPT_SUCCESS) {
820 	    msg_warn("%s: %s: Unable to set query result size limit to %ld.",
821 		     myname, dict_ldap->parser->name, dict_ldap->size_limit);
822 	    DICT_LDAP_UNBIND_RETURN(dict_ldap->ld, DICT_ERR_RETRY, -1);
823 	}
824     }
825 
826     /*
827      * Configure alias dereferencing for this connection. Thanks to Mike
828      * Mattice for this, and to Hery Rakotoarisoa for the v3 update.
829      */
830     if (ldap_set_option(dict_ldap->ld, LDAP_OPT_DEREF,
831 			&(dict_ldap->dereference)) != LDAP_OPT_SUCCESS)
832 	msg_warn("%s: Unable to set dereference option.", myname);
833 
834     /* Chase referrals. */
835 
836 #ifdef LDAP_OPT_REFERRALS
837     if (ldap_set_option(dict_ldap->ld, LDAP_OPT_REFERRALS,
838 		    dict_ldap->chase_referrals ? LDAP_OPT_ON : LDAP_OPT_OFF)
839 	!= LDAP_OPT_SUCCESS) {
840 	msg_warn("%s: Unable to set Referral chasing.", myname);
841 	DICT_LDAP_UNBIND_RETURN(dict_ldap->ld, DICT_ERR_RETRY, -1);
842     }
843 #else
844     if (dict_ldap->chase_referrals) {
845 	msg_warn("%s: Unable to set Referral chasing.", myname);
846     }
847 #endif
848 
849 #ifdef LDAP_API_FEATURE_X_OPENLDAP
850     if (dict_ldap_set_tls_options(dict_ldap) != 0)
851 	DICT_LDAP_UNBIND_RETURN(dict_ldap->ld, DICT_ERR_RETRY, -1);
852     if (dict_ldap->start_tls) {
853 	if ((saved_alarm = signal(SIGALRM, dict_ldap_timeout)) == SIG_ERR) {
854 	    msg_warn("%s: Error setting signal handler for STARTTLS timeout: %m",
855 		     myname);
856 	    DICT_LDAP_UNBIND_RETURN(dict_ldap->ld, DICT_ERR_RETRY, -1);
857 	}
858 	alarm(dict_ldap->timeout);
859 	if (setjmp(env) == 0)
860 	    rc = ldap_start_tls_s(dict_ldap->ld, NULL, NULL);
861 	else {
862 	    rc = LDAP_TIMEOUT;
863 	    dict_ldap->ld = 0;			/* Unknown state after
864 						 * longjmp() */
865 	}
866 	alarm(0);
867 
868 	if (signal(SIGALRM, saved_alarm) == SIG_ERR) {
869 	    msg_warn("%s: Error resetting signal handler after STARTTLS: %m",
870 		     myname);
871 	    dict_ldap->dict.error = DICT_ERR_RETRY;
872 	    return (-1);
873 	}
874 	if (rc != LDAP_SUCCESS) {
875 	    msg_error("%s: Unable to set STARTTLS: %d: %s", myname,
876 		      rc, ldap_err2string(rc));
877 	    dict_ldap->dict.error = DICT_ERR_RETRY;
878 	    return (-1);
879 	}
880     }
881 #endif
882 
883 #define DN_LOG_VAL(dict_ldap) \
884 	((dict_ldap)->bind_dn[0] ? (dict_ldap)->bind_dn : "empty or implicit")
885 
886     /*
887      * If this server requires a bind, do so. Thanks to Sam Tardieu for
888      * noticing that the original bind call was broken.
889      */
890     if (DICT_LDAP_DO_BIND(dict_ldap)) {
891 	if (msg_verbose)
892 	    msg_info("%s: Binding to server %s with dn %s",
893 		     myname, dict_ldap->server_host, DN_LOG_VAL(dict_ldap));
894 
895 #if defined(USE_LDAP_SASL) && defined(LDAP_API_FEATURE_X_OPENLDAP)
896 	if (DICT_LDAP_DO_SASL(dict_ldap)) {
897 	    rc = dict_ldap_bind_sasl(dict_ldap);
898 	} else {
899 	    rc = dict_ldap_bind_st(dict_ldap);
900 	}
901 #else
902 	rc = dict_ldap_bind_st(dict_ldap);
903 #endif
904 
905 	if (rc != LDAP_SUCCESS) {
906 	    msg_warn("%s: Unable to bind to server %s with dn %s: %d (%s)",
907 		     myname, dict_ldap->server_host, DN_LOG_VAL(dict_ldap),
908 		     rc, ldap_err2string(rc));
909 	    DICT_LDAP_UNBIND_RETURN(dict_ldap->ld, DICT_ERR_RETRY, -1);
910 	}
911 	if (msg_verbose)
912 	    msg_info("%s: Successful bind to server %s with dn %s",
913 		     myname, dict_ldap->server_host, DN_LOG_VAL(dict_ldap));
914     }
915     /* Save connection handle in shared container */
916     DICT_LDAP_CONN(dict_ldap)->conn_ld = dict_ldap->ld;
917 
918     if (msg_verbose)
919 	msg_info("%s: Cached connection handle for LDAP source %s",
920 		 myname, dict_ldap->parser->name);
921 
922     return (0);
923 }
924 
925 /*
926  * Locate or allocate connection cache entry.
927  */
dict_ldap_conn_find(DICT_LDAP * dict_ldap)928 static void dict_ldap_conn_find(DICT_LDAP *dict_ldap)
929 {
930     VSTRING *keybuf = vstring_alloc(10);
931     char   *key;
932     int     len;
933 
934 #ifdef LDAP_API_FEATURE_X_OPENLDAP
935     int     sslon = dict_ldap->start_tls || dict_ldap->ldap_ssl;
936 
937 #endif
938     LDAP_CONN *conn;
939 
940     /*
941      * Join key fields with null characters.
942      */
943 #define ADDSTR(vp, s) vstring_memcat((vp), (s), strlen((s))+1)
944 #define ADDINT(vp, i) vstring_sprintf_append((vp), "%lu%c", (unsigned long)(i), 0)
945 
946     ADDSTR(keybuf, dict_ldap->server_host);
947     ADDINT(keybuf, dict_ldap->server_port);
948     ADDINT(keybuf, dict_ldap->bind);
949     ADDSTR(keybuf, DICT_LDAP_DO_BIND(dict_ldap) ? dict_ldap->bind_dn : "");
950     ADDSTR(keybuf, DICT_LDAP_DO_BIND(dict_ldap) ? dict_ldap->bind_pw : "");
951     ADDINT(keybuf, dict_ldap->dereference);
952     ADDINT(keybuf, dict_ldap->chase_referrals);
953     ADDINT(keybuf, dict_ldap->debuglevel);
954     ADDINT(keybuf, dict_ldap->version);
955 #ifdef LDAP_API_FEATURE_X_OPENLDAP
956 #if defined(USE_LDAP_SASL)
957     ADDSTR(keybuf, DICT_LDAP_DO_SASL(dict_ldap) ? dict_ldap->sasl_mechs : "");
958     ADDSTR(keybuf, DICT_LDAP_DO_SASL(dict_ldap) ? dict_ldap->sasl_realm : "");
959     ADDSTR(keybuf, DICT_LDAP_DO_SASL(dict_ldap) ? dict_ldap->sasl_authz : "");
960     ADDINT(keybuf, DICT_LDAP_DO_SASL(dict_ldap) ? dict_ldap->sasl_minssf : 0);
961 #endif
962     ADDINT(keybuf, dict_ldap->ldap_ssl);
963     ADDINT(keybuf, dict_ldap->start_tls);
964     ADDINT(keybuf, sslon ? dict_ldap->tls_require_cert : 0);
965     ADDSTR(keybuf, sslon ? dict_ldap->tls_ca_cert_file : "");
966     ADDSTR(keybuf, sslon ? dict_ldap->tls_ca_cert_dir : "");
967     ADDSTR(keybuf, sslon ? dict_ldap->tls_cert : "");
968     ADDSTR(keybuf, sslon ? dict_ldap->tls_key : "");
969     ADDSTR(keybuf, sslon ? dict_ldap->tls_random_file : "");
970     ADDSTR(keybuf, sslon ? dict_ldap->tls_cipher_suite : "");
971 #endif
972 
973     key = vstring_str(keybuf);
974     len = VSTRING_LEN(keybuf);
975 
976     if (conn_hash == 0)
977 	conn_hash = binhash_create(0);
978 
979     if ((dict_ldap->ht = binhash_locate(conn_hash, key, len)) == 0) {
980 	conn = (LDAP_CONN *) mymalloc(sizeof(LDAP_CONN));
981 	conn->conn_ld = 0;
982 	conn->conn_refcount = 0;
983 	dict_ldap->ht = binhash_enter(conn_hash, key, len, (void *) conn);
984     }
985     ++DICT_LDAP_CONN(dict_ldap)->conn_refcount;
986 
987     vstring_free(keybuf);
988 }
989 
990 /* attr_sub_type - Is one of two attributes a sub-type of another */
991 
attrdesc_subtype(const char * a1,const char * a2)992 static int attrdesc_subtype(const char *a1, const char *a2)
993 {
994 
995     /*
996      * RFC 2251 section 4.1.4: LDAP attribute names are case insensitive
997      */
998     while (*a1 && TOLOWER(*a1) == TOLOWER(*a2))
999 	++a1, ++a2;
1000 
1001     /*
1002      * Names equal to end of a1, is a2 equal or a subtype?
1003      */
1004     if (*a1 == 0 && (*a2 == 0 || *a2 == ';'))
1005 	return (1);
1006 
1007     /*
1008      * Names equal to end of a2, is a1 a subtype?
1009      */
1010     if (*a2 == 0 && *a1 == ';')
1011 	return (-1);
1012 
1013     /*
1014      * Distinct attributes
1015      */
1016     return (0);
1017 }
1018 
1019 /* url_attrs - attributes we want from LDAP URL */
1020 
url_attrs(DICT_LDAP * dict_ldap,LDAPURLDesc * url)1021 static char **url_attrs(DICT_LDAP *dict_ldap, LDAPURLDesc * url)
1022 {
1023     static ARGV *attrs;
1024     char  **a1;
1025     char  **a2;
1026     int     arel;
1027 
1028     /*
1029      * If the LDAP URI specified no attributes, all entry attributes are
1030      * returned, leading to unnecessarily large LDAP results, particularly
1031      * since dynamic groups are most useful for large groups.
1032      *
1033      * Since we only make use of the various mumble_results attributes, we ask
1034      * only for these, thus making large queries much faster.
1035      *
1036      * In one test case, a query returning 75K users took 16 minutes when all
1037      * attributes are returned, and just under 3 minutes with only the
1038      * desired result attribute.
1039      */
1040     if (url->lud_attrs == 0 || *url->lud_attrs == 0)
1041 	return (dict_ldap->result_attributes->argv);
1042 
1043     /*
1044      * When the LDAP URI explicitly specifies a set of attributes, we use the
1045      * interaction of the URI attributes and our result attributes. This way
1046      * LDAP URIs can hide certain attributes that should not be part of the
1047      * query. There is no point in retrieving attributes not listed in our
1048      * result set, we won't make any use of those.
1049      */
1050     if (attrs)
1051 	argv_truncate(attrs, 0);
1052     else
1053 	attrs = argv_alloc(2);
1054 
1055     /*
1056      * Retrieve only those attributes that are of interest to us.
1057      *
1058      * If the URL attribute and the attribute we want differ only in the
1059      * "options" part of the attribute descriptor, select the more specific
1060      * attribute descriptor.
1061      */
1062     for (a1 = url->lud_attrs; *a1; ++a1) {
1063 	for (a2 = dict_ldap->result_attributes->argv; *a2; ++a2) {
1064 	    arel = attrdesc_subtype(*a1, *a2);
1065 	    if (arel > 0)
1066 		argv_add(attrs, *a2, ARGV_END);
1067 	    else if (arel < 0)
1068 		argv_add(attrs, *a1, ARGV_END);
1069 	}
1070     }
1071 
1072     return ((attrs->argc > 0) ? attrs->argv : 0);
1073 }
1074 
1075 /*
1076  * dict_ldap_get_values: for each entry returned by a search, get the values
1077  * of all its attributes. Recurses to resolve any DN or URL values found.
1078  *
1079  * This and the rest of the handling of multiple attributes, DNs and URLs
1080  * are thanks to LaMont Jones.
1081  */
dict_ldap_get_values(DICT_LDAP * dict_ldap,LDAPMessage * res,VSTRING * result,const char * name)1082 static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage *res,
1083 				         VSTRING *result, const char *name)
1084 {
1085     static int recursion = 0;
1086     static int expansion;
1087     long    entries = 0;
1088     long    i = 0;
1089     int     rc = 0;
1090     LDAPMessage *resloop = 0;
1091     LDAPMessage *entry = 0;
1092     BerElement *ber;
1093     char   *attr;
1094     char  **attrs;
1095     struct berval **vals;
1096     int     valcount;
1097     LDAPURLDesc *url;
1098     const char *myname = "dict_ldap_get_values";
1099     int     is_leaf = 1;		/* No recursion via this entry */
1100     int     is_terminal = 0;		/* No expansion via this entry */
1101 
1102     if (++recursion == 1)
1103 	expansion = 0;
1104 
1105     if (msg_verbose)
1106 	msg_info("%s[%d]: Search found %d match(es)", myname, recursion,
1107 		 ldap_count_entries(dict_ldap->ld, res));
1108 
1109     for (entry = ldap_first_entry(dict_ldap->ld, res); entry != NULL;
1110 	 entry = ldap_next_entry(dict_ldap->ld, entry)) {
1111 	ber = NULL;
1112 
1113 	/*
1114 	 * LDAP should not, but may produce more than the requested maximum
1115 	 * number of entries.
1116 	 */
1117 	if (dict_ldap->dict.error == 0
1118 	    && dict_ldap->size_limit
1119 	    && ++entries > dict_ldap->size_limit) {
1120 	    msg_warn("%s[%d]: %s: Query size limit (%ld) exceeded",
1121 		     myname, recursion, dict_ldap->parser->name,
1122 		     dict_ldap->size_limit);
1123 	    dict_ldap->dict.error = DICT_ERR_RETRY;
1124 	}
1125 
1126 	/*
1127 	 * Check for terminal attributes, these preclude expansion of all
1128 	 * other attributes, and DN/URI recursion. Any terminal attributes
1129 	 * are listed first in the attribute array.
1130 	 */
1131 	if (dict_ldap->num_terminal > 0) {
1132 	    for (i = 0; i < dict_ldap->num_terminal; ++i) {
1133 		attr = dict_ldap->result_attributes->argv[i];
1134 		if (!(vals = ldap_get_values_len(dict_ldap->ld, entry, attr)))
1135 		    continue;
1136 		is_terminal = (ldap_count_values_len(vals) > 0);
1137 		ldap_value_free_len(vals);
1138 		if (is_terminal)
1139 		    break;
1140 	    }
1141 	}
1142 
1143 	/*
1144 	 * Check for special attributes, these preclude expansion of
1145 	 * "leaf-only" attributes, and are at the end of the attribute array
1146 	 * after the terminal, leaf and regular attributes.
1147 	 */
1148 	if (is_terminal == 0 && dict_ldap->num_leaf > 0) {
1149 	    for (i = dict_ldap->num_attributes;
1150 		 dict_ldap->result_attributes->argv[i]; ++i) {
1151 		attr = dict_ldap->result_attributes->argv[i];
1152 		if (!(vals = ldap_get_values_len(dict_ldap->ld, entry, attr)))
1153 		    continue;
1154 		is_leaf = (ldap_count_values_len(vals) == 0);
1155 		ldap_value_free_len(vals);
1156 		if (!is_leaf)
1157 		    break;
1158 	    }
1159 	}
1160 	for (attr = ldap_first_attribute(dict_ldap->ld, entry, &ber);
1161 	     attr != NULL; ldap_memfree(attr),
1162 	     attr = ldap_next_attribute(dict_ldap->ld, entry, ber)) {
1163 
1164 	    vals = ldap_get_values_len(dict_ldap->ld, entry, attr);
1165 	    if (vals == NULL) {
1166 		if (msg_verbose)
1167 		    msg_info("%s[%d]: Entry doesn't have any values for %s",
1168 			     myname, recursion, attr);
1169 		continue;
1170 	    }
1171 	    valcount = ldap_count_values_len(vals);
1172 
1173 	    /*
1174 	     * If we previously encountered an error, we still continue
1175 	     * through the loop, to avoid memory leaks, but we don't waste
1176 	     * time accumulating any further results.
1177 	     *
1178 	     * XXX: There may be a more efficient way to exit the loop with no
1179 	     * leaks, but it will likely be more fragile and not worth the
1180 	     * extra code.
1181 	     */
1182 	    if (dict_ldap->dict.error != 0 || valcount == 0) {
1183 		ldap_value_free_len(vals);
1184 		continue;
1185 	    }
1186 
1187 	    /*
1188 	     * The "result_attributes" list enumerates all the requested
1189 	     * attributes, first the ordinary result attributes and then the
1190 	     * special result attributes that hold DN or LDAP URL values.
1191 	     *
1192 	     * The number of ordinary attributes is "num_attributes".
1193 	     *
1194 	     * We compute the attribute type (ordinary or special) from its
1195 	     * index on the "result_attributes" list.
1196 	     */
1197 	    for (i = 0; dict_ldap->result_attributes->argv[i]; i++)
1198 		if (attrdesc_subtype(dict_ldap->result_attributes->argv[i],
1199 				     attr) > 0)
1200 		    break;
1201 
1202 	    /*
1203 	     * Append each returned address to the result list, possibly
1204 	     * recursing (for dn or url attributes of non-terminal entries)
1205 	     */
1206 	    if (i < dict_ldap->num_attributes || is_terminal) {
1207 		if ((is_terminal && i >= dict_ldap->num_terminal)
1208 		    || (!is_leaf &&
1209 			i < dict_ldap->num_terminal + dict_ldap->num_leaf)) {
1210 		    if (msg_verbose)
1211 			msg_info("%s[%d]: skipping %d value(s) of %s "
1212 				 "attribute %s", myname, recursion, valcount,
1213 				 is_terminal ? "non-terminal" : "leaf-only",
1214 				 attr);
1215 		} else {
1216 		    /* Ordinary result attribute */
1217 		    for (i = 0; i < valcount; i++) {
1218 			if (db_common_expand(dict_ldap->ctx,
1219 					     dict_ldap->result_format,
1220 					     vals[i]->bv_val,
1221 					     name, result, 0)
1222 			    && dict_ldap->expansion_limit > 0
1223 			    && ++expansion > dict_ldap->expansion_limit) {
1224 			    msg_warn("%s[%d]: %s: Expansion limit exceeded "
1225 				     "for key: '%s'", myname, recursion,
1226 				     dict_ldap->parser->name, name);
1227 			    dict_ldap->dict.error = DICT_ERR_RETRY;
1228 			    break;
1229 			}
1230 		    }
1231 		    if (dict_ldap->dict.error != 0)
1232 			continue;
1233 		    if (msg_verbose)
1234 			msg_info("%s[%d]: search returned %d value(s) for"
1235 				 " requested result attribute %s",
1236 				 myname, recursion, valcount, attr);
1237 		}
1238 	    } else if (recursion < dict_ldap->recursion_limit
1239 		       && dict_ldap->result_attributes->argv[i]) {
1240 		/* Special result attribute */
1241 		for (i = 0; i < valcount; i++) {
1242 		    if (ldap_is_ldap_url(vals[i]->bv_val)) {
1243 			rc = ldap_url_parse(vals[i]->bv_val, &url);
1244 			if (rc == 0) {
1245 			    if ((attrs = url_attrs(dict_ldap, url)) != 0) {
1246 				if (msg_verbose)
1247 				    msg_info("%s[%d]: looking up URL %s",
1248 					     myname, recursion,
1249 					     vals[i]->bv_val);
1250 				rc = search_st(dict_ldap->ld, url->lud_dn,
1251 					       url->lud_scope,
1252 					       url->lud_filter,
1253 					       attrs, dict_ldap->timeout,
1254 					       &resloop);
1255 			    }
1256 			    ldap_free_urldesc(url);
1257 			    if (attrs == 0) {
1258 				if (msg_verbose)
1259 				    msg_info("%s[%d]: skipping URL %s: no "
1260 					     "pertinent attributes", myname,
1261 					     recursion, vals[i]->bv_val);
1262 				continue;
1263 			    }
1264 			} else {
1265 			    msg_warn("%s[%d]: malformed URL %s: %s(%d)",
1266 				     myname, recursion, vals[i]->bv_val,
1267 				     ldap_err2string(rc), rc);
1268 			    dict_ldap->dict.error = DICT_ERR_RETRY;
1269 			    break;
1270 			}
1271 		    } else {
1272 			if (msg_verbose)
1273 			    msg_info("%s[%d]: looking up DN %s",
1274 				     myname, recursion, vals[i]->bv_val);
1275 			rc = search_st(dict_ldap->ld, vals[i]->bv_val,
1276 				       LDAP_SCOPE_BASE, "objectclass=*",
1277 				       dict_ldap->result_attributes->argv,
1278 				       dict_ldap->timeout, &resloop);
1279 		    }
1280 		    switch (rc) {
1281 		    case LDAP_SUCCESS:
1282 			dict_ldap_get_values(dict_ldap, resloop, result, name);
1283 			break;
1284 		    case LDAP_NO_SUCH_OBJECT:
1285 
1286 			/*
1287 			 * Go ahead and treat this as though the DN existed
1288 			 * and just didn't have any result attributes.
1289 			 */
1290 			msg_warn("%s[%d]: DN %s not found, skipping ", myname,
1291 				 recursion, vals[i]->bv_val);
1292 			break;
1293 		    default:
1294 			msg_warn("%s[%d]: search error %d: %s ", myname,
1295 				 recursion, rc, ldap_err2string(rc));
1296 			dict_ldap->dict.error = DICT_ERR_RETRY;
1297 			break;
1298 		    }
1299 
1300 		    if (resloop != 0)
1301 			ldap_msgfree(resloop);
1302 
1303 		    if (dict_ldap->dict.error != 0)
1304 			break;
1305 		}
1306 		if (msg_verbose && dict_ldap->dict.error == 0)
1307 		    msg_info("%s[%d]: search returned %d value(s) for"
1308 			     " special result attribute %s",
1309 			     myname, recursion, valcount, attr);
1310 	    } else if (recursion >= dict_ldap->recursion_limit
1311 		       && dict_ldap->result_attributes->argv[i]) {
1312 		msg_warn("%s[%d]: %s: Recursion limit exceeded"
1313 			 " for special attribute %s=%s", myname, recursion,
1314 			 dict_ldap->parser->name, attr, vals[0]->bv_val);
1315 		dict_ldap->dict.error = DICT_ERR_RETRY;
1316 	    }
1317 	    ldap_value_free_len(vals);
1318 	}
1319 	if (ber)
1320 	    ber_free(ber, 0);
1321     }
1322 
1323     if (msg_verbose)
1324 	msg_info("%s[%d]: Leaving %s", myname, recursion, myname);
1325     --recursion;
1326 }
1327 
1328 /* dict_ldap_lookup - find database entry */
1329 
dict_ldap_lookup(DICT * dict,const char * name)1330 static const char *dict_ldap_lookup(DICT *dict, const char *name)
1331 {
1332     const char *myname = "dict_ldap_lookup";
1333     DICT_LDAP *dict_ldap = (DICT_LDAP *) dict;
1334     LDAPMessage *res = 0;
1335     static VSTRING *base;
1336     static VSTRING *query;
1337     static VSTRING *result;
1338     int     rc = 0;
1339     int     sizelimit;
1340     int     domain_rc;
1341 
1342     dict_ldap->dict.error = 0;
1343 
1344     if (msg_verbose)
1345 	msg_info("%s: In dict_ldap_lookup", myname);
1346 
1347     /*
1348      * Don't frustrate future attempts to make Postfix UTF-8 transparent.
1349      */
1350     if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0
1351 	&& !valid_utf8_string(name, strlen(name))) {
1352 	if (msg_verbose)
1353 	    msg_info("%s: %s: Skipping lookup of non-UTF-8 key '%s'",
1354 		     myname, dict_ldap->parser->name, name);
1355 	return (0);
1356     }
1357 
1358     /*
1359      * Optionally fold the key.
1360      */
1361     if (dict->flags & DICT_FLAG_FOLD_FIX) {
1362 	if (dict->fold_buf == 0)
1363 	    dict->fold_buf = vstring_alloc(10);
1364 	vstring_strcpy(dict->fold_buf, name);
1365 	name = lowercase(vstring_str(dict->fold_buf));
1366     }
1367 
1368     /*
1369      * If they specified a domain list for this map, then only search for
1370      * addresses in domains on the list. This can significantly reduce the
1371      * load on the LDAP server.
1372      */
1373     if ((domain_rc = db_common_check_domain(dict_ldap->ctx, name)) == 0) {
1374 	if (msg_verbose)
1375 	    msg_info("%s: %s: Skipping lookup of key '%s': domain mismatch",
1376 		     myname, dict_ldap->parser->name, name);
1377 	return (0);
1378     }
1379     if (domain_rc < 0)
1380 	DICT_ERR_VAL_RETURN(dict, domain_rc, (char *) 0);
1381 
1382 #define INIT_VSTR(buf, len) do { \
1383 	if (buf == 0) \
1384 	    buf = vstring_alloc(len); \
1385 	VSTRING_RESET(buf); \
1386 	VSTRING_TERMINATE(buf); \
1387     } while (0)
1388 
1389     INIT_VSTR(base, 10);
1390     INIT_VSTR(query, 10);
1391     INIT_VSTR(result, 10);
1392 
1393     /*
1394      * Because the connection may be shared and invalidated via queries for
1395      * another map, update private copy of "ld" from shared connection
1396      * container.
1397      */
1398     dict_ldap->ld = DICT_LDAP_CONN(dict_ldap)->conn_ld;
1399 
1400     /*
1401      * Connect to the LDAP server, if necessary.
1402      */
1403     if (dict_ldap->ld == NULL) {
1404 	if (msg_verbose)
1405 	    msg_info
1406 		("%s: No existing connection for LDAP source %s, reopening",
1407 		 myname, dict_ldap->parser->name);
1408 
1409 	dict_ldap_connect(dict_ldap);
1410 
1411 	/*
1412 	 * if dict_ldap_connect() set dict_ldap->dict.error, abort.
1413 	 */
1414 	if (dict_ldap->dict.error)
1415 	    return (0);
1416     } else if (msg_verbose)
1417 	msg_info("%s: Using existing connection for LDAP source %s",
1418 		 myname, dict_ldap->parser->name);
1419 
1420     /*
1421      * Connection caching, means that the connection handle may have the
1422      * wrong size limit. Re-adjust before each query. This is cheap, just
1423      * sets a field in the ldap connection handle. We also do this in the
1424      * connect code, because we sometimes reconnect (below) in the middle of
1425      * a query.
1426      */
1427     sizelimit = dict_ldap->size_limit ? dict_ldap->size_limit : LDAP_NO_LIMIT;
1428     if (ldap_set_option(dict_ldap->ld, LDAP_OPT_SIZELIMIT, &sizelimit)
1429 	!= LDAP_OPT_SUCCESS) {
1430 	msg_warn("%s: %s: Unable to set query result size limit to %ld.",
1431 		 myname, dict_ldap->parser->name, dict_ldap->size_limit);
1432 	dict_ldap->dict.error = DICT_ERR_RETRY;
1433 	return (0);
1434     }
1435 
1436     /*
1437      * Expand the search base and query. Skip lookup when the input key lacks
1438      * sufficient domain components to satisfy all the requested
1439      * %-substitutions.
1440      *
1441      * When the search base is not static, LDAP_NO_SUCH_OBJECT is expected and
1442      * is therefore treated as a non-error: the lookup returns no results
1443      * rather than a soft error.
1444      */
1445     if (!db_common_expand(dict_ldap->ctx, dict_ldap->search_base,
1446 			  name, 0, base, rfc2253_quote)) {
1447 	if (msg_verbose > 1)
1448 	    msg_info("%s: %s: Empty expansion for %s", myname,
1449 		     dict_ldap->parser->name, dict_ldap->search_base);
1450 	return (0);
1451     }
1452     if (!db_common_expand(dict_ldap->ctx, dict_ldap->query,
1453 			  name, 0, query, rfc2254_quote)) {
1454 	if (msg_verbose > 1)
1455 	    msg_info("%s: %s: Empty expansion for %s", myname,
1456 		     dict_ldap->parser->name, dict_ldap->query);
1457 	return (0);
1458     }
1459 
1460     /*
1461      * On to the search.
1462      */
1463     if (msg_verbose)
1464 	msg_info("%s: %s: Searching with filter %s", myname,
1465 		 dict_ldap->parser->name, vstring_str(query));
1466 
1467     rc = search_st(dict_ldap->ld, vstring_str(base), dict_ldap->scope,
1468 		   vstring_str(query), dict_ldap->result_attributes->argv,
1469 		   dict_ldap->timeout, &res);
1470 
1471     if (rc == LDAP_SERVER_DOWN) {
1472 	if (msg_verbose)
1473 	    msg_info("%s: Lost connection for LDAP source %s, reopening",
1474 		     myname, dict_ldap->parser->name);
1475 
1476 	dict_ldap_unbind(dict_ldap->ld);
1477 	dict_ldap->ld = DICT_LDAP_CONN(dict_ldap)->conn_ld = 0;
1478 	dict_ldap_connect(dict_ldap);
1479 
1480 	/*
1481 	 * if dict_ldap_connect() set dict_ldap->dict.error, abort.
1482 	 */
1483 	if (dict_ldap->dict.error)
1484 	    return (0);
1485 
1486 	rc = search_st(dict_ldap->ld, vstring_str(base), dict_ldap->scope,
1487 		     vstring_str(query), dict_ldap->result_attributes->argv,
1488 		       dict_ldap->timeout, &res);
1489 
1490     }
1491     switch (rc) {
1492 
1493     case LDAP_SUCCESS:
1494 
1495 	/*
1496 	 * Search worked; extract the requested result_attribute.
1497 	 */
1498 
1499 	dict_ldap_get_values(dict_ldap, res, result, name);
1500 
1501 	/*
1502 	 * OpenLDAP's ldap_next_attribute returns a bogus
1503 	 * LDAP_DECODING_ERROR; I'm ignoring that for now.
1504 	 */
1505 
1506 	rc = dict_ldap_get_errno(dict_ldap->ld);
1507 	if (rc != LDAP_SUCCESS && rc != LDAP_DECODING_ERROR)
1508 	    msg_warn
1509 		("%s: Had some trouble with entries returned by search: %s",
1510 		 myname, ldap_err2string(rc));
1511 
1512 	if (msg_verbose)
1513 	    msg_info("%s: Search returned %s", myname,
1514 		     VSTRING_LEN(result) >
1515 		     0 ? vstring_str(result) : "nothing");
1516 	break;
1517 
1518     case LDAP_NO_SUCH_OBJECT:
1519 
1520 	/*
1521 	 * If the search base is input key dependent, then not finding it, is
1522 	 * equivalent to not finding the input key. Sadly, we cannot detect
1523 	 * misconfiguration in this case.
1524 	 */
1525 	if (dict_ldap->dynamic_base)
1526 	    break;
1527 
1528 	msg_warn("%s: %s: Search base '%s' not found: %d: %s",
1529 		 myname, dict_ldap->parser->name,
1530 		 vstring_str(base), rc, ldap_err2string(rc));
1531 	dict_ldap->dict.error = DICT_ERR_RETRY;
1532 	break;
1533 
1534     default:
1535 
1536 	/*
1537 	 * Rats. The search didn't work.
1538 	 */
1539 	msg_warn("%s: Search error %d: %s ", myname, rc,
1540 		 ldap_err2string(rc));
1541 
1542 	/*
1543 	 * Tear down the connection so it gets set up from scratch on the
1544 	 * next lookup.
1545 	 */
1546 	dict_ldap_unbind(dict_ldap->ld);
1547 	dict_ldap->ld = DICT_LDAP_CONN(dict_ldap)->conn_ld = 0;
1548 
1549 	/*
1550 	 * And tell the caller to try again later.
1551 	 */
1552 	dict_ldap->dict.error = DICT_ERR_RETRY;
1553 	break;
1554     }
1555 
1556     /*
1557      * Cleanup.
1558      */
1559     if (res != 0)
1560 	ldap_msgfree(res);
1561 
1562     /*
1563      * If we had an error, return nothing, Otherwise, return the result, if
1564      * any.
1565      */
1566     return (VSTRING_LEN(result) > 0 && !dict_ldap->dict.error ? vstring_str(result) : 0);
1567 }
1568 
1569 /* dict_ldap_close - disassociate from data base */
1570 
dict_ldap_close(DICT * dict)1571 static void dict_ldap_close(DICT *dict)
1572 {
1573     const char *myname = "dict_ldap_close";
1574     DICT_LDAP *dict_ldap = (DICT_LDAP *) dict;
1575     LDAP_CONN *conn = DICT_LDAP_CONN(dict_ldap);
1576     BINHASH_INFO *ht = dict_ldap->ht;
1577 
1578     if (--conn->conn_refcount == 0) {
1579 	if (conn->conn_ld) {
1580 	    if (msg_verbose)
1581 		msg_info("%s: Closed connection handle for LDAP source %s",
1582 			 myname, dict_ldap->parser->name);
1583 	    dict_ldap_unbind(conn->conn_ld);
1584 	}
1585 	binhash_delete(conn_hash, ht->key, ht->key_len, myfree);
1586     }
1587     cfg_parser_free(dict_ldap->parser);
1588     myfree(dict_ldap->server_host);
1589     myfree(dict_ldap->search_base);
1590     myfree(dict_ldap->query);
1591     if (dict_ldap->result_format)
1592 	myfree(dict_ldap->result_format);
1593     argv_free(dict_ldap->result_attributes);
1594     myfree(dict_ldap->bind_dn);
1595     myfree(dict_ldap->bind_pw);
1596     if (dict_ldap->ctx)
1597 	db_common_free_ctx(dict_ldap->ctx);
1598 #ifdef LDAP_API_FEATURE_X_OPENLDAP
1599 #if defined(USE_LDAP_SASL)
1600     if (DICT_LDAP_DO_SASL(dict_ldap)) {
1601 	myfree(dict_ldap->sasl_mechs);
1602 	myfree(dict_ldap->sasl_realm);
1603 	myfree(dict_ldap->sasl_authz);
1604     }
1605 #endif
1606     myfree(dict_ldap->tls_ca_cert_file);
1607     myfree(dict_ldap->tls_ca_cert_dir);
1608     myfree(dict_ldap->tls_cert);
1609     myfree(dict_ldap->tls_key);
1610     myfree(dict_ldap->tls_random_file);
1611     myfree(dict_ldap->tls_cipher_suite);
1612 #endif
1613     if (dict->fold_buf)
1614 	vstring_free(dict->fold_buf);
1615     dict_free(dict);
1616 }
1617 
1618 /* dict_ldap_open - create association with data base */
1619 
dict_ldap_open(const char * ldapsource,int open_flags,int dict_flags)1620 DICT   *dict_ldap_open(const char *ldapsource, int open_flags, int dict_flags)
1621 {
1622     const char *myname = "dict_ldap_open";
1623     DICT_LDAP *dict_ldap;
1624     VSTRING *url_list;
1625     char   *s;
1626     char   *h;
1627     char   *server_host;
1628     char   *scope;
1629     char   *attr;
1630     char   *bindopt;
1631     int     tmp;
1632     int     vendor_version = dict_ldap_vendor_version();
1633     CFG_PARSER *parser;
1634 
1635     if (msg_verbose)
1636 	msg_info("%s: Using LDAP source %s", myname, ldapsource);
1637 
1638     /*
1639      * Sanity check.
1640      */
1641     if (open_flags != O_RDONLY)
1642 	return (dict_surrogate(DICT_TYPE_LDAP, ldapsource, open_flags, dict_flags,
1643 			       "%s:%s map requires O_RDONLY access mode",
1644 			       DICT_TYPE_LDAP, ldapsource));
1645 
1646     /*
1647      * Open the configuration file.
1648      */
1649     if ((parser = cfg_parser_alloc(ldapsource)) == 0)
1650 	return (dict_surrogate(DICT_TYPE_LDAP, ldapsource, open_flags, dict_flags,
1651 			       "open %s: %m", ldapsource));
1652 
1653     dict_ldap = (DICT_LDAP *) dict_alloc(DICT_TYPE_LDAP, ldapsource,
1654 					 sizeof(*dict_ldap));
1655     dict_ldap->dict.lookup = dict_ldap_lookup;
1656     dict_ldap->dict.close = dict_ldap_close;
1657     dict_ldap->dict.flags = dict_flags;
1658 
1659     dict_ldap->ld = NULL;
1660     dict_ldap->parser = parser;
1661 
1662     server_host = cfg_get_str(dict_ldap->parser, "server_host",
1663 			      "localhost", 1, 0);
1664 
1665     /*
1666      * get configured value of "server_port"; default to LDAP_PORT (389)
1667      */
1668     dict_ldap->server_port =
1669 	cfg_get_int(dict_ldap->parser, "server_port", LDAP_PORT, 0, 0);
1670 
1671     /*
1672      * Define LDAP Protocol Version.
1673      */
1674     dict_ldap->version = cfg_get_int(dict_ldap->parser, "version", 2, 2, 0);
1675     switch (dict_ldap->version) {
1676     case 2:
1677 	dict_ldap->version = LDAP_VERSION2;
1678 	break;
1679     case 3:
1680 	dict_ldap->version = LDAP_VERSION3;
1681 	break;
1682     default:
1683 	msg_warn("%s: %s Unknown version %d, using 2.", myname, ldapsource,
1684 		 dict_ldap->version);
1685 	dict_ldap->version = LDAP_VERSION2;
1686     }
1687 
1688 #if defined(LDAP_API_FEATURE_X_OPENLDAP)
1689     dict_ldap->ldap_ssl = 0;
1690 #endif
1691 
1692     url_list = vstring_alloc(32);
1693     s = server_host;
1694     while ((h = mystrtok(&s, CHARS_COMMA_SP)) != NULL) {
1695 #if defined(LDAP_API_FEATURE_X_OPENLDAP)
1696 
1697 	/*
1698 	 * Convert (host, port) pairs to LDAP URLs
1699 	 */
1700 	if (ldap_is_ldap_url(h)) {
1701 	    LDAPURLDesc *url_desc;
1702 	    int     rc;
1703 
1704 	    if ((rc = ldap_url_parse(h, &url_desc)) != 0) {
1705 		msg_error("%s: error parsing URL %s: %d: %s; skipping", myname,
1706 			  h, rc, ldap_err2string(rc));
1707 		continue;
1708 	    }
1709 	    if (strcasecmp(url_desc->lud_scheme, "ldap") != 0 &&
1710 		dict_ldap->version != LDAP_VERSION3) {
1711 		msg_warn("%s: URL scheme %s requires protocol version 3", myname,
1712 			 url_desc->lud_scheme);
1713 		dict_ldap->version = LDAP_VERSION3;
1714 	    }
1715 	    if (strcasecmp(url_desc->lud_scheme, "ldaps") == 0)
1716 		dict_ldap->ldap_ssl = 1;
1717 	    ldap_free_urldesc(url_desc);
1718 	    if (VSTRING_LEN(url_list) > 0)
1719 		VSTRING_ADDCH(url_list, ' ');
1720 	    vstring_strcat(url_list, h);
1721 	} else {
1722 	    if (VSTRING_LEN(url_list) > 0)
1723 		VSTRING_ADDCH(url_list, ' ');
1724 	    if (strrchr(h, ':'))
1725 		vstring_sprintf_append(url_list, "ldap://%s", h);
1726 	    else
1727 		vstring_sprintf_append(url_list, "ldap://%s:%d", h,
1728 				       dict_ldap->server_port);
1729 	}
1730 #else
1731 	if (VSTRING_LEN(url_list) > 0)
1732 	    VSTRING_ADDCH(url_list, ' ');
1733 	vstring_strcat(url_list, h);
1734 #endif
1735     }
1736     VSTRING_TERMINATE(url_list);
1737     dict_ldap->server_host = vstring_export(url_list);
1738 
1739 #if defined(LDAP_API_FEATURE_X_OPENLDAP)
1740 
1741     /*
1742      * With URL scheme, clear port to normalize connection cache key
1743      */
1744     dict_ldap->server_port = LDAP_PORT;
1745     if (msg_verbose)
1746 	msg_info("%s: %s server_host URL is %s", myname, ldapsource,
1747 		 dict_ldap->server_host);
1748 #endif
1749     myfree(server_host);
1750 
1751     /*
1752      * Scope handling thanks to Carsten Hoeger of SuSE.
1753      */
1754     scope = cfg_get_str(dict_ldap->parser, "scope", "sub", 1, 0);
1755 
1756     if (strcasecmp(scope, "one") == 0) {
1757 	dict_ldap->scope = LDAP_SCOPE_ONELEVEL;
1758     } else if (strcasecmp(scope, "base") == 0) {
1759 	dict_ldap->scope = LDAP_SCOPE_BASE;
1760     } else if (strcasecmp(scope, "sub") == 0) {
1761 	dict_ldap->scope = LDAP_SCOPE_SUBTREE;
1762     } else {
1763 	msg_warn("%s: %s: Unrecognized value %s specified for scope; using sub",
1764 		 myname, ldapsource, scope);
1765 	dict_ldap->scope = LDAP_SCOPE_SUBTREE;
1766     }
1767 
1768     myfree(scope);
1769 
1770     dict_ldap->search_base = cfg_get_str(dict_ldap->parser, "search_base",
1771 					 "", 0, 0);
1772 
1773     /*
1774      * get configured value of "timeout"; default to 10 seconds
1775      *
1776      * Thanks to Manuel Guesdon for spotting that this wasn't really getting
1777      * set.
1778      */
1779     dict_ldap->timeout = cfg_get_int(dict_ldap->parser, "timeout", 10, 0, 0);
1780     dict_ldap->query =
1781 	cfg_get_str(dict_ldap->parser, "query_filter",
1782 		    "(mailacceptinggeneralid=%s)", 0, 0);
1783     if ((dict_ldap->result_format =
1784 	 cfg_get_str(dict_ldap->parser, "result_format", 0, 0, 0)) == 0)
1785 	dict_ldap->result_format =
1786 	    cfg_get_str(dict_ldap->parser, "result_filter", "%s", 1, 0);
1787 
1788     /*
1789      * Must parse all templates before we can use db_common_expand() If data
1790      * dependent substitutions are found in the search base, treat
1791      * NO_SUCH_OBJECT search errors as a non-matching key, rather than a
1792      * fatal run-time error.
1793      */
1794     dict_ldap->ctx = 0;
1795     dict_ldap->dynamic_base =
1796 	db_common_parse(&dict_ldap->dict, &dict_ldap->ctx,
1797 			dict_ldap->search_base, 1);
1798     if (!db_common_parse(0, &dict_ldap->ctx, dict_ldap->query, 1)) {
1799 	msg_warn("%s: %s: Fixed query_filter %s is probably useless",
1800 		 myname, ldapsource, dict_ldap->query);
1801     }
1802     (void) db_common_parse(0, &dict_ldap->ctx, dict_ldap->result_format, 0);
1803     db_common_parse_domain(dict_ldap->parser, dict_ldap->ctx);
1804 
1805     /*
1806      * Maps that use substring keys should only be used with the full input
1807      * key.
1808      */
1809     if (db_common_dict_partial(dict_ldap->ctx))
1810 	dict_ldap->dict.flags |= DICT_FLAG_PATTERN;
1811     else
1812 	dict_ldap->dict.flags |= DICT_FLAG_FIXED;
1813     if (dict_flags & DICT_FLAG_FOLD_FIX)
1814 	dict_ldap->dict.fold_buf = vstring_alloc(10);
1815 
1816     /* Order matters, first the terminal attributes: */
1817     attr = cfg_get_str(dict_ldap->parser, "terminal_result_attribute", "", 0, 0);
1818     dict_ldap->result_attributes = argv_split(attr, CHARS_COMMA_SP);
1819     dict_ldap->num_terminal = dict_ldap->result_attributes->argc;
1820     myfree(attr);
1821 
1822     /* Order matters, next the leaf-only attributes: */
1823     attr = cfg_get_str(dict_ldap->parser, "leaf_result_attribute", "", 0, 0);
1824     if (*attr)
1825 	argv_split_append(dict_ldap->result_attributes, attr, CHARS_COMMA_SP);
1826     dict_ldap->num_leaf =
1827 	dict_ldap->result_attributes->argc - dict_ldap->num_terminal;
1828     myfree(attr);
1829 
1830     /* Order matters, next the regular attributes: */
1831     attr = cfg_get_str(dict_ldap->parser, "result_attribute", "maildrop", 0, 0);
1832     if (*attr)
1833 	argv_split_append(dict_ldap->result_attributes, attr, CHARS_COMMA_SP);
1834     dict_ldap->num_attributes = dict_ldap->result_attributes->argc;
1835     myfree(attr);
1836 
1837     /* Order matters, finally the special attributes: */
1838     attr = cfg_get_str(dict_ldap->parser, "special_result_attribute", "", 0, 0);
1839     if (*attr)
1840 	argv_split_append(dict_ldap->result_attributes, attr, CHARS_COMMA_SP);
1841     myfree(attr);
1842 
1843     /*
1844      * get configured value of "bind"; default to simple bind
1845      */
1846     bindopt = cfg_get_str(dict_ldap->parser, "bind", CONFIG_BOOL_YES, 1, 0);
1847     dict_ldap->bind = name_code(bindopt_table, NAME_CODE_FLAG_NONE, bindopt);
1848     if (dict_ldap->bind < 0)
1849 	msg_fatal("%s: unsupported parameter value: %s = %s",
1850 		  dict_ldap->parser->name, "bind", bindopt);
1851     myfree(bindopt);
1852 
1853     /*
1854      * get configured value of "bind_dn"; default to ""
1855      */
1856     dict_ldap->bind_dn = cfg_get_str(dict_ldap->parser, "bind_dn", "", 0, 0);
1857 
1858     /*
1859      * get configured value of "bind_pw"; default to ""
1860      */
1861     dict_ldap->bind_pw = cfg_get_str(dict_ldap->parser, "bind_pw", "", 0, 0);
1862 
1863     /*
1864      * LDAP message caching never worked and is no longer supported.
1865      */
1866     tmp = cfg_get_bool(dict_ldap->parser, "cache", 0);
1867     if (tmp)
1868 	msg_warn("%s: %s ignoring cache", myname, ldapsource);
1869 
1870     tmp = cfg_get_int(dict_ldap->parser, "cache_expiry", -1, 0, 0);
1871     if (tmp >= 0)
1872 	msg_warn("%s: %s ignoring cache_expiry", myname, ldapsource);
1873 
1874     tmp = cfg_get_int(dict_ldap->parser, "cache_size", -1, 0, 0);
1875     if (tmp >= 0)
1876 	msg_warn("%s: %s ignoring cache_size", myname, ldapsource);
1877 
1878     dict_ldap->recursion_limit = cfg_get_int(dict_ldap->parser,
1879 					     "recursion_limit", 1000, 1, 0);
1880 
1881     /*
1882      * XXX: The default should be non-zero for safety, but that is not
1883      * backwards compatible.
1884      */
1885     dict_ldap->expansion_limit = cfg_get_int(dict_ldap->parser,
1886 					     "expansion_limit", 0, 0, 0);
1887 
1888     dict_ldap->size_limit = cfg_get_int(dict_ldap->parser, "size_limit",
1889 					dict_ldap->expansion_limit, 0, 0);
1890 
1891     /*
1892      * Alias dereferencing suggested by Mike Mattice.
1893      */
1894     dict_ldap->dereference = cfg_get_int(dict_ldap->parser, "dereference",
1895 					 0, 0, 0);
1896     if (dict_ldap->dereference < 0 || dict_ldap->dereference > 3) {
1897 	msg_warn("%s: %s Unrecognized value %d specified for dereference; using 0",
1898 		 myname, ldapsource, dict_ldap->dereference);
1899 	dict_ldap->dereference = 0;
1900     }
1901     /* Referral chasing */
1902     dict_ldap->chase_referrals = cfg_get_bool(dict_ldap->parser,
1903 					      "chase_referrals", 0);
1904 
1905 #ifdef LDAP_API_FEATURE_X_OPENLDAP
1906 #if defined(USE_LDAP_SASL)
1907 
1908     /*
1909      * SASL options
1910      */
1911     if (DICT_LDAP_DO_SASL(dict_ldap)) {
1912 	dict_ldap->sasl_mechs =
1913 	    cfg_get_str(dict_ldap->parser, "sasl_mechs", "", 0, 0);
1914 	dict_ldap->sasl_realm =
1915 	    cfg_get_str(dict_ldap->parser, "sasl_realm", "", 0, 0);
1916 	dict_ldap->sasl_authz =
1917 	    cfg_get_str(dict_ldap->parser, "sasl_authz_id", "", 0, 0);
1918 	dict_ldap->sasl_minssf =
1919 	    cfg_get_int(dict_ldap->parser, "sasl_minssf", 0, 0, 4096);
1920     } else {
1921 	dict_ldap->sasl_mechs = 0;
1922 	dict_ldap->sasl_realm = 0;
1923 	dict_ldap->sasl_authz = 0;
1924     }
1925 #endif
1926 
1927     /*
1928      * TLS options
1929      */
1930     /* get configured value of "start_tls"; default to no */
1931     dict_ldap->start_tls = cfg_get_bool(dict_ldap->parser, "start_tls", 0);
1932     if (dict_ldap->start_tls) {
1933 	if (dict_ldap->version < LDAP_VERSION3) {
1934 	    msg_warn("%s: %s start_tls requires protocol version 3",
1935 		     myname, ldapsource);
1936 	    dict_ldap->version = LDAP_VERSION3;
1937 	}
1938 	/* Binary incompatibility in the OpenLDAP API from 2.0.11 to 2.0.12 */
1939 	if (((LDAP_VENDOR_VERSION <= 20011) && !(vendor_version <= 20011))
1940 	  || (!(LDAP_VENDOR_VERSION <= 20011) && (vendor_version <= 20011)))
1941 	    msg_fatal("%s: incompatible TLS support: "
1942 		      "compile-time OpenLDAP version %d, "
1943 		      "run-time OpenLDAP version %d",
1944 		      myname, LDAP_VENDOR_VERSION, vendor_version);
1945     }
1946     /* get configured value of "tls_require_cert"; default to no */
1947     dict_ldap->tls_require_cert =
1948 	cfg_get_bool(dict_ldap->parser, "tls_require_cert", 0) ?
1949 	LDAP_OPT_X_TLS_DEMAND : LDAP_OPT_X_TLS_NEVER;
1950 
1951     /* get configured value of "tls_ca_cert_file"; default "" */
1952     dict_ldap->tls_ca_cert_file = cfg_get_str(dict_ldap->parser,
1953 					      "tls_ca_cert_file", "", 0, 0);
1954 
1955     /* get configured value of "tls_ca_cert_dir"; default "" */
1956     dict_ldap->tls_ca_cert_dir = cfg_get_str(dict_ldap->parser,
1957 					     "tls_ca_cert_dir", "", 0, 0);
1958 
1959     /* get configured value of "tls_cert"; default "" */
1960     dict_ldap->tls_cert = cfg_get_str(dict_ldap->parser, "tls_cert",
1961 				      "", 0, 0);
1962 
1963     /* get configured value of "tls_key"; default "" */
1964     dict_ldap->tls_key = cfg_get_str(dict_ldap->parser, "tls_key",
1965 				     "", 0, 0);
1966 
1967     /* get configured value of "tls_random_file"; default "" */
1968     dict_ldap->tls_random_file = cfg_get_str(dict_ldap->parser,
1969 					     "tls_random_file", "", 0, 0);
1970 
1971     /* get configured value of "tls_cipher_suite"; default "" */
1972     dict_ldap->tls_cipher_suite = cfg_get_str(dict_ldap->parser,
1973 					      "tls_cipher_suite", "", 0, 0);
1974 #endif
1975 
1976     /*
1977      * Debug level.
1978      */
1979 #if defined(LDAP_OPT_DEBUG_LEVEL) && defined(LBER_OPT_LOG_PRINT_FN)
1980     dict_ldap->debuglevel = cfg_get_int(dict_ldap->parser, "debuglevel",
1981 					0, 0, 0);
1982 #endif
1983 
1984     /*
1985      * Find or allocate shared LDAP connection container.
1986      */
1987     dict_ldap_conn_find(dict_ldap);
1988 
1989     /*
1990      * Return the new dict_ldap structure.
1991      */
1992     dict_ldap->dict.owner = cfg_get_owner(dict_ldap->parser);
1993     return (DICT_DEBUG (&dict_ldap->dict));
1994 }
1995 
1996 #endif
1997