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