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