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