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