summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpius2004-04-10 19:32:46 (GMT)
committercvs2git2012-06-24 12:13:13 (GMT)
commit167b16a4bd9553f666e3df7d91c8295a271b87a4 (patch)
tree96eb84d135c9486c6d375e56328c343b7367a816
parent79569251a1695d85e2e0d696ab4378ad96203d13 (diff)
downloadpostfix-167b16a4bd9553f666e3df7d91c8295a271b87a4.zip
postfix-167b16a4bd9553f666e3df7d91c8295a271b87a4.tar.gz
- backported dict_ldap from 2.1RC1 snapshot so that it could work with
openldap 2.2; it also inroduces ssl/tls and support for multiple servers Changed files: postfix-dict_ldap.patch -> 1.1
-rw-r--r--postfix-dict_ldap.patch1162
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));
+ }