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