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