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