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