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