diff options
-rw-r--r-- | postfix-dict_ldap.patch | 1162 |
1 files changed, 1162 insertions, 0 deletions
diff --git a/postfix-dict_ldap.patch b/postfix-dict_ldap.patch new file mode 100644 index 0000000..8ed5876 --- /dev/null +++ b/postfix-dict_ldap.patch @@ -0,0 +1,1162 @@ +--- postfix-2.0.19.old/src/util/dict_ldap.c 2002-10-17 02:26:41.000000000 +0200 ++++ postfix-2.0.19.new/src/util/dict_ldap.c 2004-04-10 21:08:37.000000000 +0200 +@@ -26,7 +26,9 @@ + /* .PP + /* Configuration parameters: + /* .IP \fIldapsource_\fRserver_host +-/* The host at which all LDAP queries are directed. ++/* List of hosts at which all LDAP queries are directed. ++/* The host names can also be LDAP URLs if the LDAP client library used ++/* is OpenLDAP. + /* .IP \fIldapsource_\fRserver_port + /* The port the LDAP server listens on. + /* .IP \fIldapsource_\fRsearch_base +@@ -57,14 +59,56 @@ + /* If you must bind to the server, do it with this distinguished name ... + /* .IP \fIldapsource_\fRbind_pw + /* \&... and this password. +-/* .IP \fIldapsource_\fRcache ++/* .IP \fIldapsource_\fRcache (no longer supported) + /* Whether or not to turn on client-side caching. +-/* .IP \fIldapsource_\fRcache_expiry ++/* .IP \fIldapsource_\fRcache_expiry (no longer supported) + /* If you do cache results, expire them after this many seconds. +-/* .IP \fIldapsource_\fRcache_size ++/* .IP \fIldapsource_\fRcache_size (no longer supported) + /* The cache size in bytes. Does nothing if the cache is off, of course. ++/* .IP \fIldapsource_\fRrecursion_limit ++/* Maximum recursion depth when expanding DN or URL references. ++/* Queries which exceed the recursion limit fail with ++/* dict_errno = DICT_ERR_RETRY. ++/* .IP \fIldapsource_\fRexpansion_limit ++/* Limit (if any) on the total number of lookup result values. Lookups which ++/* exceed the limit fail with dict_errno=DICT_ERR_RETRY. Note that ++/* each value of a multivalued result attribute counts as one result. ++/* .IP \fIldapsource_\fRsize_limit ++/* Limit on the number of entries returned by individual LDAP queries. ++/* Queries which exceed the limit fail with dict_errno=DICT_ERR_RETRY. ++/* This is an *entry* count, for any single query performed during the ++/* possibly recursive lookup. ++/* .IP \fIldapsource_\fRchase_referrals ++/* Controls whether LDAP referrals are obeyed. + /* .IP \fIldapsource_\fRdereference + /* How to handle LDAP aliases. See ldap.h or ldap_open(3) man page. ++/* .IP \fIldapsource_\fRversion ++/* Specifies the LDAP protocol version to use. Default is version ++/* \fI2\fR. ++/* .IP \fIldapsource_\fRstart_tls ++/* Whether or not to issue STARTTLS upon connection to the server. ++/* At this time, STARTTLS and LDAP SSL are only available if the ++/* LDAP client library used is OpenLDAP. Default is \fIno\fR. ++/* .IP \fIldapsource_\fRtls_ca_cert_file ++/* File containing certificates for all of the X509 Certificate ++/* Authorities the client will recognize. Takes precedence over ++/* tls_ca_cert_dir. ++/* .IP \fIldapsource_\fRtls_ca_cert_dir ++/* Directory containing X509 Certificate Authority certificates ++/* in separate individual files. ++/* .IP \fIldapsource_\fRtls_cert ++/* File containing client's X509 certificate. ++/* .IP \fIldapsource_\fRtls_key ++/* File containing the private key corresponding to ++/* tls_cert. ++/* .IP \fIldapsource_\fRtls_require_cert ++/* Whether or not to request server's X509 certificate and check its ++/* validity. ++/* .IP \fIldapsource_\fRtls_random_file ++/* Path of a file to obtain random bits from when /dev/[u]random is ++/* not available. Generally set to the name of the EGD/PRNGD socket. ++/* .IP \fIldapsource_\fRtls_cipher_suite ++/* Cipher suite to use in SSL/TLS negotiations. + /* .IP \fIldapsource_\fRdebuglevel + /* Debug level. See 'loglevel' option in slapd.conf(5) man page. + /* Currently only in openldap libraries (and derivatives). +@@ -102,6 +146,8 @@ + #include <lber.h> + #include <ldap.h> + #include <string.h> ++#include <ctype.h> ++#include <unistd.h> + + /* + * Older APIs have weird memory freeing behavior. +@@ -126,12 +172,22 @@ + #include "mymalloc.h" + #include "vstring.h" + #include "dict.h" ++#include "stringops.h" ++#include "binhash.h" ++ + #include "dict_ldap.h" + + /* AAARGH!! */ + + #include "../global/mail_conf.h" + ++#include "dict_ldap.h" ++ ++typedef struct { ++ LDAP *conn_ld; ++ int conn_refcount; ++} LDAP_CONN; ++ + /* + * Structure containing all the configuration parameters for a given + * LDAP source, plus its connection handle. +@@ -152,17 +208,33 @@ + char *bind_dn; + char *bind_pw; + int timeout; +- int cache; +- long cache_expiry; +- long cache_size; + int dereference; ++ long recursion_limit; ++ long expansion_limit; ++ long size_limit; + int chase_referrals; + int debuglevel; + int version; ++#ifdef LDAP_API_FEATURE_X_OPENLDAP ++ int ldap_ssl; ++ int start_tls; ++ int tls_require_cert; ++ char *tls_ca_cert_file; ++ char *tls_ca_cert_dir; ++ char *tls_cert; ++ char *tls_key; ++ char *tls_random_file; ++ char *tls_cipher_suite; ++#endif ++ BINHASH_INFO *ht; /* hash entry for LDAP connection */ + LDAP *ld; + } DICT_LDAP; + +-#ifndef LDAP_OPT_NETWORK_TIMEOUT ++#define DICT_LDAP_CONN(d) ((LDAP_CONN *)((d)->ht->value)) ++ ++static BINHASH *conn_hash = 0; ++ ++#if defined(LDAP_API_FEATURE_X_OPENLDAP) || !defined(LDAP_OPT_NETWORK_TIMEOUT) + /* + * LDAP connection timeout support. + */ +@@ -178,8 +250,17 @@ + static void dict_ldap_logprint(LDAP_CONST char *data) + { + char *myname = "dict_ldap_debug"; ++ char *buf, ++ *p; + +- msg_info("%s: %s", myname, data); ++ buf = mystrdup(data); ++ if (*buf) { ++ p = buf + strlen(buf) - 1; ++ while (p - buf >= 0 && ISSPACE(*p)) ++ *p-- = 0; ++ } ++ msg_info("%s: %s", myname, buf); ++ myfree(buf); + } + + +@@ -225,24 +306,97 @@ + return (ldap_result2error(dict_ldap->ld, res, 1)); + } + ++#ifdef LDAP_API_FEATURE_X_OPENLDAP ++static void dict_ldap_set_tls_options(DICT_LDAP *dict_ldap) ++{ ++ char *myname = "dict_ldap_set_tls_options"; ++ int rc; ++ ++ if (dict_ldap->start_tls || dict_ldap->ldap_ssl) { ++ if (*dict_ldap->tls_random_file) { ++ if ((rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_RANDOM_FILE, ++ dict_ldap->tls_random_file)) != LDAP_SUCCESS) ++ msg_warn("%s: Unable to set tls_random_file to %s: %d: %s", ++ myname, dict_ldap->tls_random_file, ++ rc, ldap_err2string(rc)); ++ } ++ if (*dict_ldap->tls_ca_cert_file) { ++ if ((rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, ++ dict_ldap->tls_ca_cert_file)) != LDAP_SUCCESS) ++ msg_warn("%s: Unable to set tls_ca_cert_file to %s: %d: %s", ++ myname, dict_ldap->tls_ca_cert_file, ++ rc, ldap_err2string(rc)); ++ } ++ if (*dict_ldap->tls_ca_cert_dir) { ++ if ((rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTDIR, ++ dict_ldap->tls_ca_cert_dir)) != LDAP_SUCCESS) ++ msg_warn("%s: Unable to set tls_ca_cert_dir to %s: %d: %s", ++ myname, dict_ldap->tls_ca_cert_dir, ++ rc, ldap_err2string(rc)); ++ } ++ if (*dict_ldap->tls_cert) { ++ if ((rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CERTFILE, ++ dict_ldap->tls_cert)) != LDAP_SUCCESS) ++ msg_warn("%s: Unable to set tls_cert to %s: %d: %s", ++ myname, dict_ldap->tls_cert, ++ rc, ldap_err2string(rc)); ++ } ++ if (*dict_ldap->tls_key) { ++ if ((rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_KEYFILE, ++ dict_ldap->tls_key)) != LDAP_SUCCESS) ++ msg_warn("%s: Unable to set tls_key to %s: %d: %s", ++ myname, dict_ldap->tls_key, ++ rc, ldap_err2string(rc)); ++ } ++ if (*dict_ldap->tls_cipher_suite) { ++ if ((rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_CIPHER_SUITE, ++ dict_ldap->tls_cipher_suite)) != LDAP_SUCCESS) ++ msg_warn("%s: Unable to set tls_cipher_suite to %s: %d: %s", ++ myname, dict_ldap->tls_cipher_suite, ++ rc, ldap_err2string(rc)); ++ } ++ if (dict_ldap->tls_require_cert) { ++ if ((rc = ldap_set_option(NULL, LDAP_OPT_X_TLS_REQUIRE_CERT, ++ &(dict_ldap->tls_require_cert))) != LDAP_SUCCESS) ++ msg_warn("%s: Unable to set tls_require_cert to %d: %d: %s", ++ myname, dict_ldap->tls_require_cert, ++ rc, ldap_err2string(rc)); ++ } ++ } ++} ++ ++#endif ++ ++ + /* Establish a connection to the LDAP server. */ + static int dict_ldap_connect(DICT_LDAP *dict_ldap) + { + char *myname = "dict_ldap_connect"; + int rc = 0; + +-#ifdef LDAP_API_FEATURE_X_MEMCACHE +- LDAPMemCache *dircache; +- +-#endif +- + #ifdef LDAP_OPT_NETWORK_TIMEOUT + struct timeval mytimeval; + +-#else ++#endif ++ ++#if defined(LDAP_API_FEATURE_X_OPENLDAP) || !defined(LDAP_OPT_NETWORK_TIMEOUT) + void (*saved_alarm) (int); + + #endif ++#if defined(LDAP_OPT_DEBUG_LEVEL) && defined(LBER_OPT_LOG_PRINT_FN) ++ if (dict_ldap->debuglevel > 0 && ++ ber_set_option(NULL, LBER_OPT_LOG_PRINT_FN, ++ (LDAP_CONST *) dict_ldap_logprint) != LBER_OPT_SUCCESS) ++ msg_warn("%s: Unable to set ber logprint function.", myname); ++#if defined(LBER_OPT_DEBUG_LEVEL) ++ if (ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL, ++ &(dict_ldap->debuglevel)) != LBER_OPT_SUCCESS) ++ msg_warn("%s: Unable to set BER debug level.", myname); ++#endif ++ if (ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, ++ &(dict_ldap->debuglevel)) != LDAP_OPT_SUCCESS) ++ msg_warn("%s: Unable to set LDAP debug level.", myname); ++#endif + + dict_errno = 0; + +@@ -251,8 +405,13 @@ + dict_ldap->server_host); + + #ifdef LDAP_OPT_NETWORK_TIMEOUT ++#ifdef LDAP_API_FEATURE_X_OPENLDAP ++ dict_ldap_set_tls_options(dict_ldap); ++ ldap_initialize(&(dict_ldap->ld), dict_ldap->server_host); ++#else + dict_ldap->ld = ldap_init(dict_ldap->server_host, + (int) dict_ldap->server_port); ++#endif + if (dict_ldap->ld == NULL) { + msg_warn("%s: Unable to init LDAP server %s", + myname, dict_ldap->server_host); +@@ -308,12 +467,22 @@ + &dict_ldap->version) != LDAP_OPT_SUCCESS) + msg_warn("%s: Unable to get LDAP protocol version", myname); + else +- msg_warn("%s: Actual Protocol version used is %d.", ++ msg_info("%s: Actual Protocol version used is %d.", + myname, dict_ldap->version); + } + #endif + + /* ++ * Limit the number of entries returned by each query. ++ */ ++ if (dict_ldap->size_limit) { ++ if (ldap_set_option(dict_ldap->ld, LDAP_OPT_SIZELIMIT, ++ &dict_ldap->size_limit) != LDAP_OPT_SUCCESS) ++ msg_warn("%s: %s: Unable to set query result size limit to %ld.", ++ myname, dict_ldap->ldapsource, dict_ldap->size_limit); ++ } ++ ++ /* + * Configure alias dereferencing for this connection. Thanks to Mike + * Mattice for this, and to Hery Rakotoarisoa for the v3 update. + */ +@@ -321,16 +490,6 @@ + &(dict_ldap->dereference)) != LDAP_OPT_SUCCESS) + msg_warn("%s: Unable to set dereference option.", myname); + +-#if defined(LDAP_OPT_DEBUG_LEVEL) && defined(LBER_OPT_LOG_PRINT_FN) +- if (dict_ldap->debuglevel > 0 && +- ber_set_option(NULL, LBER_OPT_LOG_PRINT_FN, +- (LDAP_CONST *) dict_ldap_logprint) != LBER_OPT_SUCCESS) +- msg_warn("%s: Unable to set ber logprint function.", myname); +- if (ldap_set_option(dict_ldap->ld, LDAP_OPT_DEBUG_LEVEL, +- &(dict_ldap->debuglevel)) != LDAP_OPT_SUCCESS) +- msg_warn("%s: Unable to set LDAP debug level.", myname); +-#endif +- + /* Chase referrals. */ + + /* +@@ -348,6 +507,36 @@ + } + #endif + ++#ifdef LDAP_API_FEATURE_X_OPENLDAP ++ if (dict_ldap->start_tls) { ++ if ((saved_alarm = signal(SIGALRM, dict_ldap_timeout)) == SIG_ERR) { ++ msg_warn("%s: Error setting signal handler for STARTTLS timeout: %m", ++ myname); ++ dict_errno = DICT_ERR_RETRY; ++ return (-1); ++ } ++ alarm(dict_ldap->timeout); ++ if (setjmp(env) == 0) ++ rc = ldap_start_tls_s(dict_ldap->ld, NULL, NULL); ++ else ++ rc = LDAP_TIMEOUT; ++ alarm(0); ++ ++ if (signal(SIGALRM, saved_alarm) == SIG_ERR) { ++ msg_warn("%s: Error resetting signal handler after STARTTLS: %m", ++ myname); ++ dict_errno = DICT_ERR_RETRY; ++ return (-1); ++ } ++ if (rc != LDAP_SUCCESS) { ++ msg_error("%s: Unable to set STARTTLS: %d: %s", myname, ++ rc, ldap_err2string(rc)); ++ dict_errno = DICT_ERR_RETRY; ++ return (-1); ++ } ++ } ++#endif ++ + /* + * If this server requires a bind, do so. Thanks to Sam Tardieu for + * noticing that the original bind call was broken. +@@ -370,52 +559,9 @@ + msg_info("%s: Successful bind to server %s as %s ", + myname, dict_ldap->server_host, dict_ldap->bind_dn); + } ++ /* Save connection handle in shared container */ ++ DICT_LDAP_CONN(dict_ldap)->conn_ld = dict_ldap->ld; + +- /* +- * Set up client-side caching if it's configured. +- */ +- if (dict_ldap->cache) { +- if (msg_verbose) +- msg_info +- ("%s: Enabling %ld-byte cache for %s with %ld-second expiry", +- myname, dict_ldap->cache_size, dict_ldap->ldapsource, +- dict_ldap->cache_expiry); +- +-#ifdef LDAP_API_FEATURE_X_MEMCACHE +- rc = ldap_memcache_init(dict_ldap->cache_expiry, dict_ldap->cache_size, +- NULL, NULL, &dircache); +- if (rc != LDAP_SUCCESS) { +- msg_warn +- ("%s: Unable to configure cache for %s: %d (%s) -- continuing", +- myname, dict_ldap->ldapsource, rc, ldap_err2string(rc)); +- } else { +- rc = ldap_memcache_set(dict_ldap->ld, dircache); +- if (rc != LDAP_SUCCESS) { +- msg_warn +- ("%s: Unable to configure cache for %s: %d (%s) -- continuing", +- myname, dict_ldap->ldapsource, rc, ldap_err2string(rc)); +- } else { +- if (msg_verbose) +- msg_info("%s: Caching enabled for %s", +- myname, dict_ldap->ldapsource); +- } +- } +-#else +- +- rc = ldap_enable_cache(dict_ldap->ld, dict_ldap->cache_expiry, +- dict_ldap->cache_size); +- if (rc != LDAP_SUCCESS) { +- msg_warn +- ("%s: Unable to configure cache for %s: %d (%s) -- continuing", +- myname, dict_ldap->ldapsource, rc, ldap_err2string(rc)); +- } else { +- if (msg_verbose) +- msg_info("%s: Caching enabled for %s", +- myname, dict_ldap->ldapsource); +- } +- +-#endif +- } + if (msg_verbose) + msg_info("%s: Cached connection handle for LDAP source %s", + myname, dict_ldap->ldapsource); +@@ -424,9 +570,65 @@ + } + + /* ++ * Locate or allocate connection cache entry. ++ */ ++static void dict_ldap_conn_find(DICT_LDAP *dict_ldap) ++{ ++ VSTRING *keybuf = vstring_alloc(10); ++ char *key; ++ int len; ++ ++#ifdef LDAP_API_FEATURE_X_OPENLDAP ++ int sslon = dict_ldap->start_tls || dict_ldap->ldap_ssl; ++ ++#endif ++ LDAP_CONN *conn; ++ ++#define ADDSTR(vp, s) vstring_memcat((vp), (s), strlen((s))+1) ++#define ADDINT(vp, i) vstring_sprintf_append((vp), "%lu", (unsigned long)(i)) ++ ++ ADDSTR(keybuf, dict_ldap->server_host); ++ ADDINT(keybuf, dict_ldap->server_port); ++ ADDINT(keybuf, dict_ldap->bind); ++ ADDSTR(keybuf, dict_ldap->bind ? dict_ldap->bind_dn : ""); ++ ADDSTR(keybuf, dict_ldap->bind ? dict_ldap->bind_pw : ""); ++ ADDINT(keybuf, dict_ldap->dereference); ++ ADDINT(keybuf, dict_ldap->chase_referrals); ++ ADDINT(keybuf, dict_ldap->debuglevel); ++ ADDINT(keybuf, dict_ldap->version); ++#ifdef LDAP_API_FEATURE_X_OPENLDAP ++ ADDINT(keybuf, dict_ldap->ldap_ssl); ++ ADDINT(keybuf, dict_ldap->start_tls); ++ ADDINT(keybuf, sslon ? dict_ldap->tls_require_cert : 0); ++ ADDSTR(keybuf, sslon ? dict_ldap->tls_ca_cert_file : ""); ++ ADDSTR(keybuf, sslon ? dict_ldap->tls_ca_cert_dir : ""); ++ ADDSTR(keybuf, sslon ? dict_ldap->tls_cert : ""); ++ ADDSTR(keybuf, sslon ? dict_ldap->tls_key : ""); ++ ADDSTR(keybuf, sslon ? dict_ldap->tls_random_file : ""); ++ ADDSTR(keybuf, sslon ? dict_ldap->tls_cipher_suite : ""); ++#endif ++ ++ key = vstring_str(keybuf); ++ len = VSTRING_LEN(keybuf); ++ ++ if (conn_hash == 0) ++ conn_hash = binhash_create(0); ++ ++ if ((dict_ldap->ht = binhash_locate(conn_hash, key, len)) == 0) { ++ conn = (LDAP_CONN *) mymalloc(sizeof(LDAP_CONN)); ++ conn->conn_ld = 0; ++ conn->conn_refcount = 0; ++ dict_ldap->ht = binhash_enter(conn_hash, key, len, (char *) conn); ++ } ++ ++DICT_LDAP_CONN(dict_ldap)->conn_refcount; ++ ++ vstring_free(keybuf); ++} ++ ++/* + * expand a filter (lookup or result) + */ +-static void dict_ldap_expand_filter(char *filter, char *value, VSTRING *out) ++static void dict_ldap_expand_filter(char *ldapsource, char *filter, char *value, VSTRING *out) + { + char *myname = "dict_ldap_expand_filter"; + char *sub, +@@ -462,8 +664,8 @@ + break; + default: + msg_warn +- ("%s: Invalid filter substitution format '%%%c'!", +- myname, *(sub + 1)); ++ ("%s: %s: Invalid filter substitution format '%%%c'!", ++ myname, ldapsource, *(sub + 1)); + /* fall through */ + case 's': + vstring_strcat(out, u); +@@ -486,6 +688,9 @@ + static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage * res, + VSTRING *result) + { ++ static int recursion = 0; ++ static int expansion; ++ long entries = 0; + long i = 0; + int rc = 0; + LDAPMessage *resloop = 0; +@@ -500,13 +705,27 @@ + tv.tv_sec = dict_ldap->timeout; + tv.tv_usec = 0; + ++ if (++recursion == 1) ++ expansion = 0; ++ + if (msg_verbose) +- msg_info("%s: Search found %d match(es)", myname, ++ msg_info("%s[%d]: Search found %d match(es)", myname, recursion, + ldap_count_entries(dict_ldap->ld, res)); + + for (entry = ldap_first_entry(dict_ldap->ld, res); entry != NULL; + entry = ldap_next_entry(dict_ldap->ld, entry)) { + ber = NULL; ++ ++ /* ++ * LDAP should not, but may produce more than the requested maximum ++ * number of entries. ++ */ ++ if (dict_errno == 0 && ++entries > dict_ldap->size_limit ++ && dict_ldap->size_limit) { ++ msg_warn("%s[%d]: %s: Query size limit (%ld) exceeded", myname, ++ recursion, dict_ldap->ldapsource, dict_ldap->size_limit); ++ dict_errno = DICT_ERR_RETRY; ++ } + for (attr = ldap_first_attribute(dict_ldap->ld, entry, &ber); + attr != NULL; + ldap_memfree(attr), attr = ldap_next_attribute(dict_ldap->ld, +@@ -514,17 +733,38 @@ + vals = ldap_get_values(dict_ldap->ld, entry, attr); + if (vals == NULL) { + if (msg_verbose) +- msg_info("%s: Entry doesn't have any values for %s", +- myname, attr); ++ msg_info("%s[%d]: Entry doesn't have any values for %s", ++ myname, recursion, attr); + continue; + } ++ ++ /* ++ * If we previously encountered an error, we still continue ++ * through the loop, to avoid memory leaks, but we don't waste ++ * time accumulating any further results. ++ * ++ * XXX: There may be a more efficient way to exit the loop with no ++ * leaks, but it will likely be more fragile and not worth the ++ * extra code. ++ */ ++ if (dict_errno != 0 || vals[0] == 0) { ++ ldap_value_free(vals); ++ continue; ++ } ++ ++ /* ++ * The "result_attributes" list enumerates all the requested ++ * attributes, first the ordinary result attribtutes and then the ++ * special result attributes that hold DN or LDAP URL values. ++ * ++ * The number of ordinary attributes is "num_attributes". ++ * ++ * We compute the attribute type (ordinary or special) from its ++ * index on the "result_attributes" list. ++ */ + for (i = 0; dict_ldap->result_attributes->argv[i]; i++) { +- if (strcasecmp(dict_ldap->result_attributes->argv[i], +- attr) == 0) { +- if (msg_verbose) +- msg_info("%s: search returned %ld value(s) for requested result attribute %s", myname, i, attr); ++ if (strcasecmp(dict_ldap->result_attributes->argv[i], attr) == 0) + break; +- } + } + + /* +@@ -532,20 +772,38 @@ + * recursing (for dn or url attributes). + */ + if (i < dict_ldap->num_attributes) { ++ /* Ordinary result attribute */ + for (i = 0; vals[i] != NULL; i++) { ++ if (++expansion > dict_ldap->expansion_limit && ++ dict_ldap->expansion_limit) { ++ msg_warn("%s[%d]: %s: Expansion limit exceeded at" ++ " result attribute %s=%s", myname, recursion, ++ dict_ldap->ldapsource, attr, vals[i]); ++ dict_errno = DICT_ERR_RETRY; ++ break; ++ } + if (VSTRING_LEN(result) > 0) + vstring_strcat(result, ","); + if (dict_ldap->result_filter == NULL) + vstring_strcat(result, vals[i]); + else +- dict_ldap_expand_filter(dict_ldap->result_filter, ++ dict_ldap_expand_filter(dict_ldap->ldapsource, ++ dict_ldap->result_filter, + vals[i], result); + } +- } else if (dict_ldap->result_attributes->argv[i]) { ++ if (dict_errno != 0) ++ continue; ++ if (msg_verbose) ++ msg_info("%s[%d]: search returned %ld value(s) for" ++ " requested result attribute %s", ++ myname, recursion, i, attr); ++ } else if (recursion < dict_ldap->recursion_limit ++ && dict_ldap->result_attributes->argv[i]) { ++ /* Special result attribute */ + for (i = 0; vals[i] != NULL; i++) { + if (ldap_is_ldap_url(vals[i])) { + if (msg_verbose) +- msg_info("%s: looking up URL %s", myname, ++ msg_info("%s[%d]: looking up URL %s", myname, recursion, + vals[i]); + rc = ldap_url_parse(vals[i], &url); + if (rc == 0) { +@@ -557,7 +815,7 @@ + } + } else { + if (msg_verbose) +- msg_info("%s: looking up DN %s", myname, vals[i]); ++ msg_info("%s[%d]: looking up DN %s", myname, recursion, vals[i]); + rc = ldap_search_st(dict_ldap->ld, vals[i], + LDAP_SCOPE_BASE, "objectclass=*", + dict_ldap->result_attributes->argv, +@@ -573,11 +831,11 @@ + * Go ahead and treat this as though the DN existed + * and just didn't have any result attributes. + */ +- msg_warn("%s: DN %s not found, skipping ", myname, +- vals[i]); ++ msg_warn("%s[%d]: DN %s not found, skipping ", myname, ++ recursion, vals[i]); + break; + default: +- msg_warn("%s: search error %d: %s ", myname, rc, ++ msg_warn("%s[%d]: search error %d: %s ", myname, recursion, rc, + ldap_err2string(rc)); + dict_errno = DICT_ERR_RETRY; + break; +@@ -585,7 +843,21 @@ + + if (resloop != 0) + ldap_msgfree(resloop); ++ if (dict_errno != 0) ++ break; + } ++ if (dict_errno != 0) ++ continue; ++ if (msg_verbose) ++ msg_info("%s[%d]: search returned %ld value(s) for" ++ " special result attribute %s", ++ myname, recursion, i, attr); ++ } else if (recursion >= dict_ldap->recursion_limit ++ && dict_ldap->result_attributes->argv[i]) { ++ msg_warn("%s[%d]: %s: Recursion limit exceeded" ++ " for special attribute %s=%s", ++ myname, recursion, dict_ldap->ldapsource, attr, vals[0]); ++ dict_errno = DICT_ERR_RETRY; + } + ldap_value_free(vals); + } +@@ -593,7 +865,8 @@ + ber_free(ber, 0); + } + if (msg_verbose) +- msg_info("%s: Leaving %s", myname, myname); ++ msg_info("%s[%d]: Leaving %s", myname, recursion, myname); ++ --recursion; + } + + /* dict_ldap_lookup - find database entry */ +@@ -608,6 +881,7 @@ + VSTRING *escaped_name = 0, + *filter_buf = 0; + int rc = 0; ++ int sizelimit; + char *sub, + *end; + +@@ -624,11 +898,8 @@ + if (dict_ldap->domain) { + const char *p = strrchr(name, '@'); + +- if (p != 0) +- p = p + 1; +- else +- p = name; +- if (match_list_match(dict_ldap->domain, p) == 0) { ++ if (p == 0 || p == name || ++ match_list_match(dict_ldap->domain, ++p) == 0) { + if (msg_verbose) + msg_info("%s: domain of %s not found in domain list", myname, + name); +@@ -644,6 +915,13 @@ + vstring_strcpy(result, ""); + + /* ++ * Because the connection may be shared and invalidated via queries for ++ * another map, update private copy of "ld" from shared connection ++ * container. ++ */ ++ dict_ldap->ld = DICT_LDAP_CONN(dict_ldap)->conn_ld; ++ ++ /* + * Connect to the LDAP server, if necessary. + */ + if (dict_ldap->ld == NULL) { +@@ -663,6 +941,18 @@ + msg_info("%s: Using existing connection for LDAP source %s", + myname, dict_ldap->ldapsource); + ++ /* ++ * Connection caching, means that the connection handle may have the ++ * wrong size limit. Re-adjust before each query. This is cheap, just ++ * sets a field in the ldap connection handle. We also do this in the ++ * connect code, because we sometimes reconnect (below) in the middle of ++ * a query. ++ */ ++ sizelimit = dict_ldap->size_limit ? dict_ldap->size_limit : LDAP_NO_LIMIT; ++ if (ldap_set_option(dict_ldap->ld, LDAP_OPT_SIZELIMIT, &sizelimit) ++ != LDAP_OPT_SUCCESS) ++ msg_warn("%s: %s: Unable to set query result size limit to %ld.", ++ myname, dict_ldap->ldapsource, dict_ldap->size_limit); + + /* + * Prepare the query. +@@ -720,11 +1010,11 @@ + /* + * No, log the fact and continue. + */ +- msg_warn("%s: Fixed query_filter %s is probably useless", myname, +- dict_ldap->query_filter); ++ msg_warn("%s: %s: Fixed query_filter %s is probably useless", myname, ++ dict_ldap->ldapsource, dict_ldap->query_filter); + vstring_strcpy(filter_buf, dict_ldap->query_filter); + } else { +- dict_ldap_expand_filter(dict_ldap->query_filter, ++ dict_ldap_expand_filter(dict_ldap->ldapsource, dict_ldap->query_filter, + vstring_str(escaped_name), filter_buf); + } + +@@ -747,7 +1037,7 @@ + myname, dict_ldap->ldapsource); + + ldap_unbind(dict_ldap->ld); +- dict_ldap->ld = NULL; ++ dict_ldap->ld = DICT_LDAP_CONN(dict_ldap)->conn_ld = 0; + dict_ldap_connect(dict_ldap); + + /* +@@ -799,7 +1089,7 @@ + * next lookup. + */ + ldap_unbind(dict_ldap->ld); +- dict_ldap->ld = NULL; ++ dict_ldap->ld = DICT_LDAP_CONN(dict_ldap)->conn_ld = 0; + + /* + * And tell the caller to try again later. +@@ -830,9 +1120,18 @@ + { + char *myname = "dict_ldap_close"; + DICT_LDAP *dict_ldap = (DICT_LDAP *) dict; ++ LDAP_CONN *conn = DICT_LDAP_CONN(dict_ldap); ++ BINHASH_INFO *ht = dict_ldap->ht; + +- if (dict_ldap->ld) +- ldap_unbind(dict_ldap->ld); ++ if (--conn->conn_refcount == 0) { ++ if (conn->conn_ld) { ++ if (msg_verbose) ++ msg_info("%s: Closed connection handle for LDAP source %s", ++ myname, dict_ldap->ldapsource); ++ ldap_unbind(conn->conn_ld); ++ } ++ binhash_delete(conn_hash, ht->key, ht->key_len, myfree); ++ } + + myfree(dict_ldap->ldapsource); + myfree(dict_ldap->server_host); +@@ -845,19 +1144,32 @@ + argv_free(dict_ldap->result_attributes); + myfree(dict_ldap->bind_dn); + myfree(dict_ldap->bind_pw); ++#ifdef LDAP_API_FEATURE_X_OPENLDAP ++ myfree(dict_ldap->tls_ca_cert_file); ++ myfree(dict_ldap->tls_ca_cert_dir); ++ myfree(dict_ldap->tls_cert); ++ myfree(dict_ldap->tls_key); ++ myfree(dict_ldap->tls_random_file); ++ myfree(dict_ldap->tls_cipher_suite); ++#endif + dict_free(dict); + } + + /* dict_ldap_open - create association with data base */ + +-DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) ++DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) + { + char *myname = "dict_ldap_open"; + DICT_LDAP *dict_ldap; + VSTRING *config_param; ++ VSTRING *url_list; ++ char *s; ++ char *h; + char *domainlist; ++ char *server_host; + char *scope; + char *attr; ++ int tmp; + + if (msg_verbose) + msg_info("%s: Using LDAP source %s", myname, ldapsource); +@@ -870,15 +1182,14 @@ + + dict_ldap->ldapsource = mystrdup(ldapsource); + ++ dict_ldap->ld = NULL; ++ + config_param = vstring_alloc(15); + vstring_sprintf(config_param, "%s_server_host", ldapsource); + +- dict_ldap->server_host = ++ server_host = + mystrdup((char *) get_mail_conf_str(vstring_str(config_param), +- "localhost", 0, 0)); +- if (msg_verbose) +- msg_info("%s: %s is %s", myname, vstring_str(config_param), +- dict_ldap->server_host); ++ "localhost", 1, 0)); + + /* + * get configured value of "ldapsource_server_port"; default to LDAP_PORT +@@ -892,11 +1203,87 @@ + dict_ldap->server_port); + + /* ++ * Define LDAP Version. ++ */ ++ vstring_sprintf(config_param, "%s_version", ldapsource); ++ dict_ldap->version = get_mail_conf_int(vstring_str(config_param), 2, 2, 0); ++ switch (dict_ldap->version) { ++ case 2: ++ dict_ldap->version = LDAP_VERSION2; ++ break; ++ case 3: ++ dict_ldap->version = LDAP_VERSION3; ++ break; ++ default: ++ msg_warn("%s: Unknown version %d.", myname, dict_ldap->version); ++ dict_ldap->version = LDAP_VERSION2; ++ } ++ ++#if defined(LDAP_API_FEATURE_X_OPENLDAP) ++ dict_ldap->ldap_ssl = 0; ++#endif ++ ++ url_list = vstring_alloc(32); ++ s = server_host; ++ while ((h = mystrtok(&s, " \t\n\r,")) != NULL) { ++#if defined(LDAP_API_FEATURE_X_OPENLDAP) ++ ++ /* ++ * Convert (host, port) pairs to LDAP URLs ++ */ ++ if (ldap_is_ldap_url(h)) { ++ LDAPURLDesc *url_desc; ++ int rc; ++ ++ if ((rc = ldap_url_parse(h, &url_desc)) != 0) { ++ msg_error("%s: error parsing URL %s: %d: %s; skipping", myname, ++ h, rc, ldap_err2string(rc)); ++ continue; ++ } ++ if (strcasecmp(url_desc->lud_scheme, "ldap") != 0 && ++ dict_ldap->version != LDAP_VERSION3) { ++ msg_warn("%s: URL scheme %s requires protocol version 3", myname, ++ url_desc->lud_scheme); ++ dict_ldap->version = LDAP_VERSION3; ++ } ++ if (strcasecmp(url_desc->lud_scheme, "ldaps") == 0) ++ dict_ldap->ldap_ssl = 1; ++ ldap_free_urldesc(url_desc); ++ vstring_sprintf_append(url_list, " %s", h); ++ } else { ++ if (strrchr(h, ':')) ++ vstring_sprintf_append(url_list, " ldap://%s", h); ++ else ++ vstring_sprintf_append(url_list, " ldap://%s:%d", h, ++ dict_ldap->server_port); ++ } ++#else ++ vstring_sprintf_append(url_list, " %s", h); ++#endif ++ } ++ ++ dict_ldap->server_host = ++ mystrdup(VSTRING_LEN(url_list) > 0 ? vstring_str(url_list) + 1 : ""); ++ ++#if defined(LDAP_API_FEATURE_X_OPENLDAP) ++ /* ++ * With URL scheme, clear port to normalize connection cache key ++ */ ++ ++ dict_ldap->server_port = LDAP_PORT; ++ ++ msg_info("%s: %s server_host URL is %s", myname, ldapsource, ++ dict_ldap->server_host); ++#endif ++ myfree(server_host); ++ vstring_free(url_list); ++ ++ /* + * Scope handling thanks to Carsten Hoeger of SuSE. + */ + vstring_sprintf(config_param, "%s_scope", ldapsource); + scope = +- (char *) get_mail_conf_str(vstring_str(config_param), "sub", 0, 0); ++ (char *) get_mail_conf_str(vstring_str(config_param), "sub", 1, 0); + + if (strcasecmp(scope, "one") == 0) { + dict_ldap->scope = LDAP_SCOPE_ONELEVEL; +@@ -910,12 +1297,15 @@ + msg_info("%s: %s is LDAP_SCOPE_BASE", myname, + vstring_str(config_param)); + +- } else { ++ } else if (strcasecmp(scope, "sub") == 0) { + dict_ldap->scope = LDAP_SCOPE_SUBTREE; + if (msg_verbose) + msg_info("%s: %s is LDAP_SCOPE_SUBTREE", myname, + vstring_str(config_param)); +- ++ } else { ++ dict_ldap->scope = LDAP_SCOPE_SUBTREE; ++ msg_warn("%s: %s: Unrecognized value %s specified for scope; using sub", ++ myname, vstring_str(config_param), scope); + } + + myfree(scope); +@@ -992,6 +1382,8 @@ + msg_info("%s: %s is %s", myname, vstring_str(config_param), attr);; + dict_ldap->result_attributes = argv_split(attr, " ,\t\r\n"); + dict_ldap->num_attributes = dict_ldap->result_attributes->argc; ++ ++ myfree(attr); + + vstring_sprintf(config_param, "%s_special_result_attribute", ldapsource); + attr = mystrdup((char *) get_mail_conf_str(vstring_str(config_param), +@@ -1003,6 +1395,8 @@ + argv_split_append(dict_ldap->result_attributes, attr, " ,\t\r\n"); + } + ++ myfree(attr); ++ + /* + * get configured value of "ldapsource_bind"; default to true + */ +@@ -1040,31 +1434,60 @@ + * get configured value of "ldapsource_cache"; default to false + */ + vstring_sprintf(config_param, "%s_cache", ldapsource); +- dict_ldap->cache = get_mail_conf_bool(vstring_str(config_param), 0); +- if (msg_verbose) +- msg_info("%s: %s is %d", myname, vstring_str(config_param), +- dict_ldap->cache); ++ tmp = get_mail_conf_bool(vstring_str(config_param), 0); ++ if (tmp) ++ msg_warn("%s: %s ignoring cache", myname, vstring_str(config_param)); + + /* + * get configured value of "ldapsource_cache_expiry"; default to 30 + * seconds + */ + vstring_sprintf(config_param, "%s_cache_expiry", ldapsource); +- dict_ldap->cache_expiry = get_mail_conf_int(vstring_str(config_param), +- 30, 0, 0); +- if (msg_verbose) +- msg_info("%s: %s is %ld", myname, vstring_str(config_param), +- dict_ldap->cache_expiry); ++ tmp = get_mail_conf_int(vstring_str(config_param), -1, 0, 0); ++ ++ if (tmp >= 0) ++ msg_warn("%s: %s ignoring cache_expiry", myname, vstring_str(config_param)); + + /* + * get configured value of "ldapsource_cache_size"; default to 32k + */ + vstring_sprintf(config_param, "%s_cache_size", ldapsource); +- dict_ldap->cache_size = get_mail_conf_int(vstring_str(config_param), +- 32768, 0, 0); ++ tmp = get_mail_conf_int(vstring_str(config_param), ++ -1, 0, 0); ++ if (tmp >= 0) ++ msg_warn("%s: %s ignoring cache_size", myname, vstring_str(config_param)); ++ ++ /* ++ * get configured value of "recursion_limit"; default to 1000 ++ */ ++ vstring_sprintf(config_param, "%s_recursion_limit", ldapsource); ++ dict_ldap->recursion_limit = get_mail_conf_int(vstring_str(config_param), ++ 1000, 1, 0); ++ ++ if (msg_verbose) ++ msg_info("%s: %s is %ld", myname, vstring_str(config_param), ++ dict_ldap->recursion_limit); ++ ++ /* ++ * Define LDAP Version. ++ * get configured value of "expansion_limit"; default to 0 ++ */ ++ ++ vstring_sprintf(config_param, "%s_expansion_limit", ldapsource); ++ dict_ldap->expansion_limit = get_mail_conf_int(vstring_str(config_param), ++ 0, 0, 0); ++ if (msg_verbose) ++ msg_info("%s: %s is %ld", myname, vstring_str(config_param), ++ dict_ldap->expansion_limit); ++ /* ++ * get configured value of "size_limit"; default to expansion_limit ++ */ ++ vstring_sprintf(config_param, "%s_size_limit", ldapsource); ++ dict_ldap->size_limit = get_mail_conf_int(vstring_str(config_param), ++ dict_ldap->expansion_limit, 0, 0); + if (msg_verbose) +- msg_info("%s: %s is %ld", myname, vstring_str(config_param), +- dict_ldap->cache_size); ++ msg_info("%s: %s is %ld", myname, vstring_str(config_param), ++ dict_ldap->size_limit); + + /* + * Alias dereferencing suggested by Mike Mattice. +@@ -1074,29 +1497,11 @@ + 0); + + /* +- * Define LDAP Version. +- */ +- vstring_sprintf(config_param, "%s_version", ldapsource); +- dict_ldap->version = get_mail_conf_int(vstring_str(config_param), 2, 0, +- 0); +- switch (dict_ldap->version) { +- case 2: +- dict_ldap->version = LDAP_VERSION2; +- break; +- case 3: +- dict_ldap->version = LDAP_VERSION3; +- break; +- default: +- msg_warn("%s: Unknown version %d.", myname, dict_ldap->version); +- dict_ldap->version = LDAP_VERSION2; +- } +- +- /* + * Make sure only valid options for alias dereferencing are used. + */ + if (dict_ldap->dereference < 0 || dict_ldap->dereference > 3) { +- msg_warn("%s: Unrecognized value %d specified for %s; using 0", +- myname, dict_ldap->dereference, vstring_str(config_param)); ++ msg_warn("%s: %s: Unrecognized value %d specified for %s; using 0", ++ myname, ldapsource, dict_ldap->dereference, vstring_str(config_param)); + dict_ldap->dereference = 0; + } + if (msg_verbose) +@@ -1110,6 +1515,68 @@ + msg_info("%s: %s is %d", myname, vstring_str(config_param), + dict_ldap->chase_referrals); + ++#ifdef LDAP_API_FEATURE_X_OPENLDAP ++ ++ /* ++ * TLS options ++ */ ++ /* get configured value of "start_tls"; default to no */ ++ vstring_sprintf(config_param, "%s_start_tls", ldapsource); ++ dict_ldap->start_tls = get_mail_conf_bool(vstring_str(config_param), 0); ++ if (dict_ldap->start_tls && dict_ldap->version < LDAP_VERSION3) { ++ msg_warn("%s: %s start_tls requires protocol version 3", ++ myname, ldapsource); ++ dict_ldap->version = LDAP_VERSION3; ++ } ++ ++ /* get configured value of "tls_require_cert"; default to no */ ++ vstring_sprintf(config_param, "%s_tls_require_cert", ldapsource); ++ dict_ldap->tls_require_cert = get_mail_conf_bool(vstring_str(config_param), 0); ++ ++ /* get configured value of "tls_ca_cert_file"; default "" */ ++ vstring_sprintf(config_param, "%s_tls_ca_cert_file", ldapsource); ++ dict_ldap->tls_ca_cert_file = mystrdup((char *) ++ get_mail_conf_str(vstring_str ++ (config_param), "", 0, ++ 0)); ++ ++ /* get configured value of "tls_ca_cert_dir"; default "" */ ++ vstring_sprintf(config_param, "%s_tls_ca_cert_dir", ldapsource); ++ dict_ldap->tls_ca_cert_dir = mystrdup((char *) ++ get_mail_conf_str(vstring_str ++ (config_param), "", 0, ++ 0)); ++ ++ /* get configured value of "tls_cert"; default "" */ ++ vstring_sprintf(config_param, "%s_tls_cert", ldapsource); ++ dict_ldap->tls_cert = mystrdup((char *) ++ get_mail_conf_str(vstring_str ++ (config_param), "", 0, ++ 0)); ++ ++ /* get configured value of "tls_key"; default "" */ ++ vstring_sprintf(config_param, "%s_tls_key", ldapsource); ++ dict_ldap->tls_key = mystrdup((char *) ++ get_mail_conf_str(vstring_str ++ (config_param), "", 0, ++ 0)); ++ ++ /* get configured value of "tls_random_file"; default "" */ ++ vstring_sprintf(config_param, "%s_tls_random_file", ldapsource); ++ dict_ldap->tls_random_file = mystrdup((char *) ++ get_mail_conf_str(vstring_str ++ (config_param), "", 0, ++ 0)); ++ ++ /* get configured value of "tls_cipher_suite"; default "" */ ++ vstring_sprintf(config_param, "%s_tls_cipher_suite", ldapsource); ++ dict_ldap->tls_cipher_suite = mystrdup((char *) ++ get_mail_conf_str(vstring_str ++ (config_param), "", 0, ++ 0)); ++#endif ++ ++ + /* + * Debug level. + */ +@@ -1122,21 +1589,13 @@ + dict_ldap->debuglevel); + #endif + +- dict_ldap_connect(dict_ldap); +- + /* +- * if dict_ldap_connect() set dict_errno, free dict_ldap and abort. ++ * Find or allocate shared LDAP connection container. + */ +- if (dict_errno) { +- if (dict_ldap->ld) +- ldap_unbind(dict_ldap->ld); +- +- myfree((char *) dict_ldap); +- return (0); +- } ++ dict_ldap_conn_find(dict_ldap); + + /* +- * Otherwise, we're all set. Return the new dict_ldap structure. ++ * Return the new dict_ldap structure. + */ + return (DICT_DEBUG (&dict_ldap->dict)); + } |