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