+++ /dev/null
---- 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;
-+ if (msg_verbose)
-+ 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));
- }
+++ /dev/null
-This is an unofficial patch for Postfix stable release 2.0 that
-back-ports a new feature from the after-2.0 snapshot releases.
-
-This feature can be used to block mail from so-called spammer
-havens, from sender addresses that resolve to Verisign's wild-card
-mail responder, or from domains that claim to have mail servers in
-reserved networks such as 127.0.0.1.
-
-This patch is a revised version; the first implementation of the
-check_{helo,sender,recipient}_ns_access feature did not correctly
-look up name server records and caused mail to be deferred with a
-450 status code; the second revision no longer defers mail when
-some NS or MX host lookup fails.
-
------------------
-
-The check_{helo,sender,recipient}_{ns,mx}_access maptype:mapname
-restriction applies the specified access table to the NS or MX
-hosts of the host/domain given in HELO, EHLO, MAIL FROM or RCPT TO
-commands.
-
- /etc/postfix/main.cf:
- smtpd_mumble_restrictions =
- ...
- reject_unknown_sender_domain
- check_sender_mx_access hash:/etc/postfix/mx_access
- ...
-
- /etc/postfix/mx_access:
- spammer.haven.tld reject spammer mx host
- 64.94.110.11 reject mail server in verisign wild-card domain
- 127 reject mail server in loopback network
- 0 reject mail server in broadcast network
-
-The following entries block mail from domains that claim to have
-have mail servers or domain servers in reserved networks.
-
- 10 reject mail server in RFC 1918 private network
- 169.254 reject mail server in link local network
- 172.16 reject mail server in RFC 1918 private network
- ...similar entries for 172.17...172.31
- 192.0.2 reject mail server in TEST-NET network
- 192.168 reject mail server in RFC 1918 private network
- 224 reject mail server in class D multicast network
- ...similar entries for 225...239
- 240 reject mail server in class E reserved network
- ...similar entries for 241...247
- 248 reject mail server in reserved network
- ...similar entries for 249...255
-
-diff -cr /tmp/postfix-2.0.16/conf/sample-smtpd.cf ./conf/sample-smtpd.cf
-*** /tmp/postfix-2.0.16/conf/sample-smtpd.cf Tue Aug 12 12:28:46 2003
---- ./conf/sample-smtpd.cf Fri Sep 19 11:17:34 2003
-***************
-*** 311,316 ****
---- 311,321 ----
- # check_helo_access maptype:mapname
- # look up HELO hostname or parent domains.
- # see access(5) for possible lookup results.
-+ # check_helo_mx_access maptype:mapname
-+ # check_helo_ns_access maptype:mapname
-+ # look up the helo hostname MX hosts (or name servers) and apply the
-+ # specified access table to those hosts.
-+ # Note: the OK result is not allowed here for security reasons.
- # reject: reject the request. Place this at the end of a restriction.
- # permit: permit the request. Place this at the end of a restriction.
- # warn_if_reject: next restriction logs a warning instead of rejecting.
-***************
-*** 343,348 ****
---- 348,358 ----
- # check_sender_access maptype:mapname
- # look up sender address, parent domain, or localpart@.
- # see access(5) for possible lookup results.
-+ # check_sender_mx_access maptype:mapname
-+ # check_sender_ns_access maptype:mapname
-+ # look up sender address MX hosts (or name servers) and apply the
-+ # specified access table to those hosts.
-+ # Note: the OK result is not allowed here for security reasons.
- # reject_sender_login_mismatch: reject if $smtpd_sender_login_maps specifies
- # a MAIL FROM address owner, but the client is not (SASL) logged in as
- # that MAIL FROM address owner; or if the client is (SASL) logged in, but
-***************
-*** 409,414 ****
---- 419,429 ----
- # check_recipient_access maptype:mapname
- # look up recipient address, parent domain, or localpart@.
- # see access(5) for possible lookup results.
-+ # check_recipient_mx_access maptype:mapname
-+ # check_recipient_ns_access maptype:mapname
-+ # look up the recipient address MX hosts (or name servers) and apply the
-+ # specified access table to those hosts.
-+ # Note: the OK result is not allowed here for security reasons.
- # reject_non_fqdn_recipient: reject recipient address that is not in FQDN form
- # reject: reject the request. Place this at the end of a restriction.
- # permit: permit the request. Place this at the end of a restriction.
-diff -cr /tmp/postfix-2.0.16/html/uce.html ./html/uce.html
-*** /tmp/postfix-2.0.16/html/uce.html Mon Aug 25 09:54:11 2003
---- ./html/uce.html Fri Sep 19 11:17:34 2003
-***************
-*** 567,572 ****
---- 567,588 ----
-
- <p>
-
-+ <a name="check_helo_ns_access">
-+
-+ <dt> <b>check_helo_ns_access</b> <i>maptype</i>:<i>mapname</i>
-+
-+ <a name="check_helo_mx_access">
-+
-+ <dt> <b>check_helo_mx_access</b> <i>maptype</i>:<i>mapname</i>
-+
-+ <dd> Apply the specified <a href="access.5.html">access database</a>
-+ to the DNS (or MX) servers for the host or domain name given with
-+ the HELO (or EHLO) command.
-+
-+ <dd> Note: an OK result is not allowed for safety reasons.
-+
-+ <p>
-+
- <dt> <b><a href="#permit">permit</a></b>
-
- <dt> <b><a href="#defer">defer</a></b>
-***************
-*** 714,719 ****
---- 730,751 ----
-
- <p>
-
-+ <a name="check_sender_ns_access">
-+
-+ <dt> <b>check_sender_ns_access</b> <i>maptype</i>:<i>mapname</i>
-+
-+ <a name="check_sender_mx_access">
-+
-+ <dt> <b>check_sender_mx_access</b> <i>maptype</i>:<i>mapname</i>
-+
-+ <dd> Apply the specified <a href="access.5.html">access database</a>
-+ to the DNS (or MX) servers for the host or domain name given with
-+ the MAIL FROM command.
-+
-+ <dd> Note: an OK result is not allowed for safety reasons.
-+
-+ <p>
-+
- <a name="reject_non_fqdn_sender">
-
- <dt> <b>reject_non_fqdn_sender</b> <dd> Reject the request when
-***************
-*** 921,926 ****
---- 953,974 ----
- <dt> <i>maptype</i>:<i>mapname</i> <dd> Search the named <a
- href="access.5.html">access database</a> for the resolved destination
- address, recipient domain or parent domain, or <i>localpart</i>@.
-+
-+ <p>
-+
-+ <a name="check_recipient_ns_access">
-+
-+ <dt> <b>check_recipient_ns_access</b> <i>maptype</i>:<i>mapname</i>
-+
-+ <a name="check_recipient_mx_access">
-+
-+ <dt> <b>check_recipient_mx_access</b> <i>maptype</i>:<i>mapname</i>
-+
-+ <dd> Apply the specified <a href="access.5.html">access database</a>
-+ to the DNS servers (or MX hosts) for the host or domain name given
-+ with the RCPT TO command.
-+
-+ <dd> Note: an OK result is not allowed for safety reasons.
-
- <p>
-
-diff -cr /tmp/postfix-2.0.16/src/dns/dns_lookup.c ./src/dns/dns_lookup.c
-*** /tmp/postfix-2.0.16/src/dns/dns_lookup.c Sun Dec 8 09:09:11 2002
---- ./src/dns/dns_lookup.c Fri Sep 19 11:17:34 2003
-***************
-*** 509,514 ****
---- 509,515 ----
- vstring_sprintf(why,
- "Name service error for %s: invalid host or domain name",
- name);
-+ h_errno = HOST_NOT_FOUND;
- return (DNS_NOTFOUND);
- }
-
-***************
-*** 520,525 ****
---- 521,527 ----
- vstring_sprintf(why,
- "Name service error for %s: invalid host or domain name",
- name);
-+ h_errno = HOST_NOT_FOUND;
- return (DNS_NOTFOUND);
- }
-
-diff -cr /tmp/postfix-2.0.16/src/global/mail_params.h ./src/global/mail_params.h
-*** /tmp/postfix-2.0.16/src/global/mail_params.h Mon Mar 3 17:07:03 2003
---- ./src/global/mail_params.h Fri Sep 19 11:17:35 2003
-***************
-*** 1249,1254 ****
---- 1249,1261 ----
- #define CHECK_RECIP_ACL "check_recipient_access"
- #define CHECK_ETRN_ACL "check_etrn_access"
-
-+ #define CHECK_HELO_MX_ACL "check_helo_mx_access"
-+ #define CHECK_SENDER_MX_ACL "check_sender_mx_access"
-+ #define CHECK_RECIP_MX_ACL "check_recipient_mx_access"
-+ #define CHECK_HELO_NS_ACL "check_helo_ns_access"
-+ #define CHECK_SENDER_NS_ACL "check_sender_ns_access"
-+ #define CHECK_RECIP_NS_ACL "check_recipient_ns_access"
-+
- #define WARN_IF_REJECT "warn_if_reject"
-
- #define REJECT_RBL "reject_rbl" /* LaMont compatibility */
-diff -cr /tmp/postfix-2.0.16/src/smtpd/smtpd_check.c ./src/smtpd/smtpd_check.c
-*** /tmp/postfix-2.0.16/src/smtpd/smtpd_check.c Tue Aug 12 10:53:25 2003
---- ./src/smtpd/smtpd_check.c Fri Sep 19 11:17:52 2003
-***************
-*** 86,91 ****
---- 86,103 ----
- /* .IP "check_recipient_access maptype:mapname"
- /* Look up the resolved recipient address in the named access table,
- /* any parent domains of the recipient domain, and the localpart@.
-+ /* .IP "check_helo_mx_access maptype:mapname"
-+ /* .IP "check_sender_mx_access maptype:mapname"
-+ /* .IP "check_recipient_mx_access maptype:mapname"
-+ /* Apply the specified access table to the MX server host name and IP
-+ /* addresses for the helo hostname, sender, or recipient, respectively.
-+ /* If no MX record is found the A record is used instead.
-+ /* .IP "check_helo_ns_access maptype:mapname"
-+ /* .IP "check_sender_ns_access maptype:mapname"
-+ /* .IP "check_recipient_ns_access maptype:mapname"
-+ /* Apply the specified access table to the DNS server host name and IP
-+ /* addresses for the helo hostname, sender, or recipient, respectively.
-+ /* If no NS record is found, the parent domain is used instead.
- /* .IP "check_recipient_maps"
- /* Reject recipients not listed as valid local, virtual or relay
- /* recipients.
-***************
-*** 455,460 ****
---- 467,484 ----
- else \
- (void) smtpd_check_reject((state), (class), (fmt), (a1), (a2)); \
- } while (0)
-+ #define DEFER_IF_PERMIT3(state, class, fmt, a1, a2, a3) do { \
-+ if ((state)->warn_if_reject == 0) \
-+ defer_if(&(state)->defer_if_permit, (class), (fmt), (a1), (a2), (a3)); \
-+ else \
-+ (void) smtpd_check_reject((state), (class), (fmt), (a1), (a2), (a3)); \
-+ } while (0)
-+ #define DEFER_IF_PERMIT4(state, class, fmt, a1, a2, a3, a4) do { \
-+ if ((state)->warn_if_reject == 0) \
-+ defer_if(&(state)->defer_if_permit, (class), (fmt), (a1), (a2), (a3), (a4)); \
-+ else \
-+ (void) smtpd_check_reject((state), (class), (fmt), (a1), (a2), (a3), (a4)); \
-+ } while (0)
-
- /*
- * Cached RBL lookup state.
-***************
-*** 2005,2010 ****
---- 2029,2151 ----
- return (SMTPD_CHECK_DUNNO);
- }
-
-+ /* check_server_access - access control by server host name or address */
-+
-+ static int check_server_access(SMTPD_STATE *state, const char *table,
-+ const char *name,
-+ int type,
-+ const char *reply_name,
-+ const char *reply_class,
-+ const char *def_acl)
-+ {
-+ const char *myname = "check_server_access";
-+ const char *domain;
-+ int dns_status;
-+ DNS_RR *server_list;
-+ DNS_RR *server;
-+ int found = 0;
-+ struct in_addr addr;
-+ struct hostent *hp;
-+ char *addr_string;
-+ int status;
-+ char **cpp;
-+ static DNS_FIXED fixed;
-+
-+ /*
-+ * Sanity check.
-+ */
-+ if (type != T_MX && type != T_NS)
-+ msg_panic("%s: unexpected resource type \"%s\" in request",
-+ myname, dns_strtype(type));
-+
-+ if (msg_verbose)
-+ msg_info("%s: %s %s", myname, dns_strtype(type), name);
-+
-+ /*
-+ * Skip over local-part.
-+ */
-+ if ((domain = strrchr(name, '@')) != 0)
-+ domain += 1;
-+ else
-+ domain = name;
-+
-+ /*
-+ * If the domain name does not exist then we apply no restriction.
-+ *
-+ * If the domain name exists but no MX record exists, fabricate an MX record
-+ * that points to the domain name itself.
-+ *
-+ * If the domain name exists but no NS record exists, look up parent domain
-+ * NS records.
-+ */
-+ dns_status = dns_lookup(domain, type, 0, &server_list,
-+ (VSTRING *) 0, (VSTRING *) 0);
-+ if (dns_status == DNS_NOTFOUND && h_errno == NO_DATA) {
-+ if (type == T_MX) {
-+ server_list = dns_rr_create(domain, &fixed, 0,
-+ domain, strlen(domain) + 1);
-+ dns_status = DNS_OK;
-+ } else if (type == T_NS) {
-+ while ((domain = strchr(domain, '.')) != 0 && domain[1]) {
-+ domain += 1;
-+ dns_status = dns_lookup(domain, type, 0, &server_list,
-+ (VSTRING *) 0, (VSTRING *) 0);
-+ if (dns_status != DNS_NOTFOUND || h_errno != NO_DATA)
-+ break;
-+ }
-+ }
-+ }
-+ if (dns_status != DNS_OK) {
-+ msg_warn("Unable to look up %s host for %s", dns_strtype(type),
-+ domain && domain[1] ? domain : reply_name);
-+ return (SMTPD_CHECK_DUNNO);
-+ }
-+
-+ /*
-+ * No bare returns after this point or we have a memory leak.
-+ */
-+ #define CHECK_SERVER_RETURN(x) { dns_rr_free(server_list); return(x); }
-+
-+ /*
-+ * Check the hostnames first, then the addresses.
-+ */
-+ for (server = server_list; server != 0; server = server->next) {
-+ if ((hp = gethostbyname((char *) server->data)) == 0) {
-+ msg_warn("Unable to look up %s host %s for %s %s",
-+ dns_strtype(type), (char *) server->data,
-+ reply_class, reply_name);
-+ continue;
-+ }
-+ if (hp->h_addrtype != AF_INET || hp->h_length != sizeof(addr)) {
-+ if (msg_verbose)
-+ msg_warn("address type %d length %d for %s",
-+ hp->h_addrtype, hp->h_length, (char *) server->data);
-+ continue; /* XXX */
-+ }
-+ if (msg_verbose)
-+ msg_info("%s: %s hostname check: %s",
-+ myname, dns_strtype(type), (char *) server->data);
-+ if ((status = check_domain_access(state, table, (char *) server->data,
-+ FULL, &found, reply_name, reply_class,
-+ def_acl)) != 0 || found)
-+ CHECK_SERVER_RETURN(status);
-+ if (msg_verbose)
-+ msg_info("%s: %s host address check: %s",
-+ myname, dns_strtype(type), (char *) server->data);
-+ for (cpp = hp->h_addr_list; *cpp; cpp++) {
-+ memcpy((char *) &addr, *cpp, sizeof(addr));
-+ addr_string = mystrdup(inet_ntoa(addr));
-+ status = check_addr_access(state, table, addr_string, FULL,
-+ &found, reply_name, reply_class,
-+ def_acl);
-+ myfree(addr_string);
-+ if (status != 0 || found)
-+ CHECK_SERVER_RETURN(status);
-+ }
-+ }
-+ CHECK_SERVER_RETURN(SMTPD_CHECK_DUNNO);
-+ }
-+
- /* check_mail_access - OK/FAIL based on mail address lookup */
-
- static int check_mail_access(SMTPD_STATE *state, const char *table,
-***************
-*** 2568,2573 ****
---- 2709,2728 ----
- }
- }
-
-+ /* forbid_whitelist - disallow whitelisting */
-+
-+ static void forbid_whitelist(SMTPD_STATE *state, const char *name,
-+ int status, const char *target)
-+ {
-+ if (status == SMTPD_CHECK_OK) {
-+ msg_warn("restriction %s returns OK for %s", name, target);
-+ msg_warn("this is not allowed for security reasons");
-+ msg_warn("use DUNNO instead of OK if you want to make an exception");
-+ longjmp(smtpd_check_buf, smtpd_check_reject(state, MAIL_ERROR_SOFTWARE,
-+ "451 Server configuration error"));
-+ }
-+ }
-+
- /* generic_checks - generic restrictions */
-
- static int generic_checks(SMTPD_STATE *state, ARGV *restrictions,
-***************
-*** 2722,2727 ****
---- 2877,2896 ----
- state->helo_name, SMTPD_NAME_HELO)) == 0)
- status = SMTPD_CHECK_OK;
- }
-+ } else if (is_map_command(state, name, CHECK_HELO_NS_ACL, &cpp)) {
-+ if (state->helo_name) {
-+ status = check_server_access(state, *cpp, state->helo_name,
-+ T_NS, state->helo_name,
-+ SMTPD_NAME_HELO, def_acl);
-+ forbid_whitelist(state, name, status, state->helo_name);
-+ }
-+ } else if (is_map_command(state, name, CHECK_HELO_MX_ACL, &cpp)) {
-+ if (state->helo_name) {
-+ status = check_server_access(state, *cpp, state->helo_name,
-+ T_MX, state->helo_name,
-+ SMTPD_NAME_HELO, def_acl);
-+ forbid_whitelist(state, name, status, state->helo_name);
-+ }
- } else if (strcasecmp(name, REJECT_NON_FQDN_HOSTNAME) == 0) {
- if (state->helo_name) {
- if (*state->helo_name != '[')
-***************
-*** 2760,2765 ****
---- 2929,2948 ----
- } else if (strcasecmp(name, REJECT_SENDER_LOGIN_MISMATCH) == 0) {
- if (state->sender && *state->sender)
- status = reject_sender_login_mismatch(state, state->sender);
-+ } else if (is_map_command(state, name, CHECK_SENDER_NS_ACL, &cpp)) {
-+ if (state->sender && *state->sender) {
-+ status = check_server_access(state, *cpp, state->sender,
-+ T_NS, state->sender,
-+ SMTPD_NAME_SENDER, def_acl);
-+ forbid_whitelist(state, name, status, state->sender);
-+ }
-+ } else if (is_map_command(state, name, CHECK_SENDER_MX_ACL, &cpp)) {
-+ if (state->sender && *state->sender) {
-+ status = check_server_access(state, *cpp, state->sender,
-+ T_MX, state->sender,
-+ SMTPD_NAME_SENDER, def_acl);
-+ forbid_whitelist(state, name, status, state->sender);
-+ }
- } else if (strcasecmp(name, REJECT_RHSBL_SENDER) == 0) {
- if (cpp[1] == 0)
- msg_warn("restriction %s requires domain name argument", name);
-***************
-*** 2812,2817 ****
---- 2995,3014 ----
- if (state->recipient)
- status = reject_non_fqdn_address(state, state->recipient,
- state->recipient, SMTPD_NAME_RECIPIENT);
-+ } else if (is_map_command(state, name, CHECK_RECIP_NS_ACL, &cpp)) {
-+ if (state->recipient && *state->recipient) {
-+ status = check_server_access(state, *cpp, state->recipient,
-+ T_NS, state->recipient,
-+ SMTPD_NAME_RECIPIENT, def_acl);
-+ forbid_whitelist(state, name, status, state->recipient);
-+ }
-+ } else if (is_map_command(state, name, CHECK_RECIP_MX_ACL, &cpp)) {
-+ if (state->recipient && *state->recipient) {
-+ status = check_server_access(state, *cpp, state->recipient,
-+ T_MX, state->recipient,
-+ SMTPD_NAME_RECIPIENT, def_acl);
-+ forbid_whitelist(state, name, status, state->recipient);
-+ }
- } else if (strcasecmp(name, REJECT_RHSBL_RECIPIENT) == 0) {
- if (cpp[1] == 0)
- msg_warn("restriction %s requires domain name argument", name);
+++ /dev/null
---- postfix-2.0.16.orig/PGSQL_README 1970-01-01 01:00:00.000000000 +0100
-+++ postfix-2.0.16/PGSQL_README 2003-11-08 20:11:44.000000000 +0100
-@@ -0,0 +1,88 @@
-+[Code contributed by Mathieu Arnold]
-+
-+We've written code to add a pgsql map type. It utilizes the pgsql
-+client library, which can be obtained from:
-+
-+ http://www.postgresql.org/
-+
-+In order to build postfix with pgsql map support, you will need to add
-+-DHAS_PGSQL and -I for the directory containing the postgres headers, and
-+the libpq library (and libcrypt) to AUXLIBS, for example:
-+
-+make -f Makefile.init makefiles \
-+ 'CCARGS=-DHAS_PGSQL -I/some/where/include/postgresql' \
-+ 'AUXLIBS=/some/where/lib/postgres/libpq.a -lcrypt'
-+
-+then, just run 'make'.
-+
-+Postfix installations which may benefit from using pgsql map types
-+include sites that have a need for instantaneous updates of
-+forwarding, and sites that may benefit from having mail exchangers
-+reference a networked database, possibly working in conjunction with a
-+customer database of sorts.
-+
-+Once postfix is built with pgsql support, you can specify a map type
-+in main.cf like this:
-+
-+alias_maps = pgsql:/etc/postfix/pgsql-aliases.cf
-+
-+The file /etc/postfix/pgsql-aliases.cf specifies lots of information
-+telling postfix how to reference the postgresql database. An example
-+postgresql map config file follows:
-+
-+#
-+# postgresql config file for alias lookups on postfix
-+# comments are ok.
-+#
-+
-+# the user name and password to log into the pgsql server
-+user = someone
-+password = some_passwordd
-+
-+# the database name on the servers
-+dbname = customer_database
-+
-+# the table name
-+table = mxaliases
-+
-+#
-+select_field = forw_addr
-+where_field = alias
-+
-+# you may specify additional_conditions here
-+additional_conditions = and status = 'paid'
-+
-+# the above variables will result in a query of
-+# the form:
-+# select forw_addr from mxaliases where alias = '$lookup' and status = 'paid'
-+# ($lookup is escaped so if it contains single quotes or other odd
-+# characters, it will not cause a parse error in the sql).
-+#
-+# the hosts that postfix will try to connect to
-+# and query from (in the order listed)
-+hosts = host1.some.domain host2.some.domain
-+
-+# end postgresql config file
-+
-+Some notes:
-+
-+This configuration interface setup allows for multiple postgresql
-+databases: you can use one for a virtual table, one for an access
-+table, and one for an aliases table if you want.
-+
-+Since sites that have a need for multiple mail exchangers may enjoy
-+the convenience of using a networked mailer database, but do not want
-+to introduce a single point of failure to their system, we've included
-+the ability to have postfix reference multiple hosts for access to a
-+single pgsql map. This will work if sites set up mirrored pgsql
-+databases on two or more hosts. Whenever queries fail with an error
-+at one host, the rest of the hosts will be tried in order. Each host
-+that is in an error state will undergo a reconnection attempt every so
-+often, and if no pgsql server hosts are reachable, then mail will be
-+deferred until atleast one of those hosts is reachable.
-+
-+Performance of postfix with pgsql has not been thoroughly tested,
-+however, we have found it to be stable. Busy mail servers using pgsql
-+maps will generate lots of concurrent pgsql clients, so the pgsql
-+server(s) should be run with this fact in mind. Any further
-+performance information, in addition to any feedback is most welcome.
---- postfix-2.0.16.orig/src/util/dict_open.c 2003-11-08 20:10:08.000000000 +0100
-+++ postfix-2.0.16/src/util/dict_open.c 2003-11-08 20:11:44.000000000 +0100
-@@ -178,6 +178,7 @@
- #include <dict_ni.h>
- #include <dict_ldap.h>
- #include <dict_mysql.h>
-+#include <dict_pgsql.h>
- #include <dict_pcre.h>
- #include <dict_regexp.h>
- #include <dict_static.h>
-@@ -230,6 +231,9 @@
- #ifdef HAS_MYSQL
- DICT_TYPE_MYSQL, dict_mysql_open,
- #endif
-+#ifdef HAS_PGSQL
-+ DICT_TYPE_PGSQL, dict_pgsql_open,
-+#endif
- #ifndef MAX_DYNAMIC_MAPS
- #ifdef HAS_PCRE
- DICT_TYPE_PCRE, dict_pcre_open,
---- postfix-2.0.16.orig/src/util/dict_pgsql.c 1970-01-01 01:00:00.000000000 +0100
-+++ postfix-2.0.16/src/util/dict_pgsql.c 2003-11-08 20:11:44.000000000 +0100
-@@ -0,0 +1,675 @@
-+
-+/*++
-+/* NAME
-+/* dict_pgsql 3
-+/* SUMMARY
-+/* dictionary manager interface to db files
-+/* SYNOPSIS
-+/* #include <dict.h>
-+/* #include <dict_pgsql.h>
-+/*
-+/* DICT *dict_pgsql_open(name, dummy, unused_dict_flags)
-+/* const char *name;
-+/* int dummy;
-+/* int unused_dict_flags;
-+/* DESCRIPTION
-+/* dict_pgsql_open() creates a dictionary of type 'pg'. This
-+/* dictionary is an interface for the postfix key->value mappings
-+/* to pgsql. The result is a pointer to the installed dictionary,
-+/* or a null pointer in case of problems.
-+/*
-+/* The pgsql dictionary can manage multiple connections to different
-+/* sql servers on different hosts. It assumes that the underlying data
-+/* on each host is identical (mirrored) and maintains one connection
-+/* at any given time. If any connection fails, any other available
-+/* ones will be opened and used. The intent of this feature is to eliminate
-+/* a single point of failure for mail systems that would otherwise rely
-+/* on a single pgsql server.
-+/*
-+/* Arguments:
-+/* .IP name
-+/* The path of the PostgreSQL configuration file. The file encodes a number of
-+/* pieces of information: username, password, databasename, table,
-+/* select_field, where_field, and hosts. For example, if you want the map to
-+/* reference databases of the name "your_db" and execute a query like this:
-+/* select forw_addr from aliases where alias like '<some username>' against
-+/* any database called "vmailer_info" located on hosts host1.some.domain and
-+/* host2.some.domain, logging in as user "vmailer" and password "passwd" then
-+/* the configuration file should read:
-+/*
-+/* user = vmailer
-+/* password = passwd
-+/* DBname = vmailer_info
-+/* table = aliases
-+/* select_field = forw_addr
-+/* where_field = alias
-+/* hosts = host1.some.domain host2.some.domain
-+/*
-+/* .IP other_name
-+/* reference for outside use.
-+/* .IP unusued_flags
-+/* unused flags
-+/* SEE ALSO
-+/* dict(3) generic dictionary manager
-+/* AUTHOR(S)
-+/* Mathieu Arnold
-+/* Absolight
-+/* mat@absolight.com
-+/*
-+/* based on dict_mysql by
-+/*
-+/* Scott Cotton
-+/* IC Group, Inc.
-+/* scott@icgroup.com
-+/*
-+/* Joshua Marcus
-+/* IC Group, Inc.
-+/* josh@icgroup.com
-+/*--*/
-+
-+/* System library. */
-+#include "sys_defs.h"
-+
-+#ifdef HAS_PGSQL
-+#include <sys/socket.h>
-+#include <netinet/in.h>
-+#include <arpa/inet.h>
-+#include <netdb.h>
-+#include <stdio.h>
-+#include <string.h>
-+#include <stdlib.h>
-+#include <syslog.h>
-+#include <time.h>
-+#include <libpq-fe.h>
-+
-+/* Utility library. */
-+#include "dict.h"
-+#include "msg.h"
-+#include "mymalloc.h"
-+#include "dict_pgsql.h"
-+#include "argv.h"
-+#include "vstring.h"
-+#include "split_at.h"
-+#include "find_inet.h"
-+
-+/* need some structs to help organize things */
-+typedef struct
-+{
-+ PGconn *db;
-+ char *hostname;
-+ int stat; /* STATUNTRIED | STATFAIL | STATCUR */
-+ time_t ts; /* used for attempting reconnection
-+ * every so often if a host is down */
-+} HOST;
-+
-+typedef struct
-+{
-+ int len_hosts; /* number of hosts */
-+ HOST *db_hosts; /* the hosts on which the databases
-+ * reside */
-+} PLPGSQL;
-+
-+typedef struct
-+{
-+ char *username;
-+ char *password;
-+ char *dbname;
-+ char *table;
-+ char *select_field;
-+ char *where_field;
-+ char *additional_conditions;
-+ char **hostnames;
-+ int len_hosts;
-+} PGSQL_NAME;
-+
-+typedef struct
-+{
-+ DICT dict;
-+ PLPGSQL *pldb;
-+ PGSQL_NAME *name;
-+} DICT_PGSQL;
-+
-+#define STATACTIVE 0
-+#define STATFAIL 1
-+#define STATUNTRIED 2
-+#define RETRY_CONN_INTV 60 /* 1 minute */
-+
-+/* internal function declarations */
-+static PLPGSQL *plpgsql_init (char *hostnames[], int);
-+static PGresult *plpgsql_query (PLPGSQL *, const char *, char *, char *,
-+
-+ char *);
-+static void plpgsql_dealloc (PLPGSQL *);
-+static void plpgsql_down_host (HOST *);
-+static void plpgsql_connect_single (HOST *, char *, char *, char *);
-+static int plpgsql_ready_reconn (HOST *);
-+static const char *dict_pgsql_lookup (DICT *, const char *);
-+DICT *dict_pgsql_open (const char *, int, int);
-+static void dict_pgsql_close (DICT *);
-+static PGSQL_NAME *pgsqlname_parse (const char *);
-+static HOST host_init (char *);
-+void pgsql_escape_string (char *escaped, const char *name, int len);
-+
-+
-+
-+/**********************************************************************
-+ * public interface dict_pgsql_lookup
-+ * find database entry return 0 if no alias found, set dict_errno
-+ * on errors to DICT_ERRBO_RETRY and set dict_errno to 0 on success
-+ *********************************************************************/
-+static const char *dict_pgsql_lookup (DICT *dict, const char *name)
-+{
-+ PGresult *query_res;
-+ char *field;
-+ DICT_PGSQL *dict_pgsql;
-+ PLPGSQL *pldb;
-+ static VSTRING *result;
-+ static VSTRING *query = 0;
-+ int i,
-+ j,
-+ numrows;
-+ char *name_escaped = 0;
-+
-+ dict_pgsql = (DICT_PGSQL *) dict;
-+ pldb = dict_pgsql->pldb;
-+ /* initialization for query */
-+ query = vstring_alloc (24);
-+ vstring_strcpy (query, "");
-+ if (
-+ (name_escaped =
-+ (char *) mymalloc ((sizeof (char) * (strlen (name) * 2) + 1))) ==
-+ NULL)
-+ {
-+ msg_fatal ("dict_pgsql_lookup: out of memory.");
-+ }
-+ /* prepare the query */
-+ pgsql_escape_string (name_escaped, name, (unsigned int) strlen (name));
-+ vstring_sprintf (query, "select %s from %s where %s = '%s' %s",
-+ dict_pgsql->name->select_field, dict_pgsql->name->table,
-+ dict_pgsql->name->where_field, name_escaped,
-+ dict_pgsql->name->additional_conditions);
-+ if (msg_verbose)
-+ msg_info ("dict_pgsql_lookup using sql query: %s",
-+ vstring_str (query));
-+ /* free mem associated with preparing the query */
-+ myfree (name_escaped);
-+ /* do the query - set dict_errno & cleanup if there's an error */
-+ if ((query_res = plpgsql_query (pldb,
-+ vstring_str (query),
-+ dict_pgsql->name->dbname,
-+ dict_pgsql->name->username,
-+ dict_pgsql->name->password)) == 0)
-+ {
-+ dict_errno = DICT_ERR_RETRY;
-+ vstring_free (query);
-+ return 0;
-+ }
-+ dict_errno = 0;
-+ /* free the vstring query */
-+ vstring_free (query);
-+ numrows = PQntuples (query_res);
-+ if (msg_verbose)
-+ msg_info ("dict_pgsql_lookup: retrieved %d rows", numrows);
-+ if (numrows == 0)
-+ {
-+ PQclear (query_res);
-+ return 0;
-+ }
-+ if (result == 0)
-+ result = vstring_alloc (10);
-+ vstring_strcpy (result, "");
-+ for (i = 0; i < numrows; i++)
-+ {
-+ if (i > 0)
-+ vstring_strcat (result, ",");
-+ for (j = 0; j < PQnfields (query_res); j++)
-+ {
-+ if (j > 0)
-+ vstring_strcat (result, ",");
-+ field = PQgetvalue (query_res, i, j);
-+ vstring_strcat (result, field);
-+ if (msg_verbose > 1)
-+ msg_info ("dict_pgsql_lookup: retrieved field: %d: %s", j,
-+ field);
-+ }
-+ }
-+ PQclear (query_res);
-+ return vstring_str (result);
-+}
-+
-+/*
-+ * plpgsql_query - process a PGSQL query. Return PGresult* on success.
-+ * On failure, log failure and try other db instances.
-+ * on failure of all db instances, return 0;
-+ * close unnecessary active connections
-+ */
-+
-+static PGresult *plpgsql_query (PLPGSQL * PLDB,
-+ const char *query,
-+ char *dbname, char *username, char *password)
-+{
-+ int i;
-+ HOST *host;
-+ PGresult *res = 0;
-+ ExecStatusType status;
-+
-+ for (i = 0; i < PLDB->len_hosts; i++)
-+ {
-+ /* can't deal with typing or reading PLDB->db_hosts[i] over & over */
-+ host = &(PLDB->db_hosts[i]);
-+ if (msg_verbose > 1)
-+ msg_info ("dict_pgsql: trying host %s stat %d, last res %p",
-+ host->hostname, host->stat, res);
-+
-+ /* answer already found */
-+ if (res != 0 && host->stat == STATACTIVE)
-+ {
-+ if (msg_verbose)
-+ msg_info
-+ ("dict_pgsql: closing unnessary connection to %s",
-+ host->hostname);
-+ plpgsql_down_host (host);
-+ }
-+ /* try to connect for the first time if we don't have a result yet */
-+ if (res == 0 && host->stat == STATUNTRIED)
-+ {
-+ if (msg_verbose)
-+ msg_info ("dict_pgsql: attempting to connect to host %s",
-+ host->hostname);
-+ plpgsql_connect_single (host, dbname, username, password);
-+ }
-+
-+ /*
-+ * try to reconnect if we don't have an answer and the host had a
-+ * prob in the past and it's time for it to reconnect
-+ */
-+ if (res == 0 && host->stat == STATFAIL
-+ && host->ts < time ((time_t *) 0))
-+ {
-+ if (msg_verbose)
-+ msg_info
-+ ("dict_pgsql: attempting to reconnect to host %s",
-+ host->hostname);
-+ plpgsql_connect_single (host, dbname, username, password);
-+ }
-+
-+ /*
-+ * if we don't have a result and the current host is marked active,
-+ * try the query. If the query fails, mark the host STATFAIL
-+ */
-+ if (res == 0 && host->stat == STATACTIVE)
-+ {
-+ res = PQexec (host->db, query);
-+ status = PQresultStatus (res);
-+ if (res
-+ && (status = PGRES_COMMAND_OK
-+ || status == PGRES_TUPLES_OK))
-+ {
-+ if (msg_verbose)
-+ msg_info
-+ ("dict_pgsql: successful query from host %s",
-+ host->hostname);
-+ }
-+ else
-+ {
-+ msg_warn ("%s", PQerrorMessage (host->db));
-+ plpgsql_down_host (host);
-+ }
-+ }
-+ }
-+ return res;
-+}
-+
-+/*
-+ * plpgsql_connect_single -
-+ * used to reconnect to a single database when one is down or none is
-+ * connected yet. Log all errors and set the stat field of host accordingly
-+ */
-+static void
-+plpgsql_connect_single (HOST *host, char *dbname, char *username,
-+ char *password)
-+{
-+ char *destination = host->hostname;
-+ char *hostname = 0;
-+ char *service;
-+ VSTRING *conninfo = vstring_alloc (100);
-+ char *conn;
-+ unsigned port = 0;
-+
-+ /*
-+ * Ad-hoc parsing code. Expect "unix:pathname" or "inet:host:port", where
-+ * both "inet:" and ":port" are optional.
-+ */
-+ if (strncmp (destination, "unix:", 5) == 0)
-+ {
-+ vstring_sprintf_append (conninfo, "host=%s ", destination + 5);
-+ }
-+ else
-+ {
-+ if (strncmp (destination, "inet:", 5) == 0)
-+ destination += 5;
-+ hostname = mystrdup (destination);
-+ if ((service = split_at (hostname, ':')) != 0)
-+ {
-+ port = ntohs (find_inet_port (service, "tcp"));
-+ vstring_sprintf_append (conninfo, "host='%s' port='%d'",
-+ hostname, port);
-+ }
-+ else
-+ {
-+ vstring_sprintf_append (conninfo, "host='%s'", hostname);
-+ }
-+ }
-+
-+ vstring_sprintf_append (conninfo, " dbname='%s' user='%s' password='%s'", dbname,
-+ username, password);
-+ conn = vstring_export (conninfo);
-+
-+ host->db = PQconnectdb (conn);
-+
-+ if ((host->db != NULL) && (PQstatus (host->db) == CONNECTION_OK))
-+ {
-+ if (msg_verbose)
-+ msg_info ("dict_pgsql: successful connection to host %s",
-+ host->hostname);
-+ host->stat = STATACTIVE;
-+ }
-+ else
-+ {
-+ msg_warn ("%s", PQerrorMessage (host->db));
-+ plpgsql_down_host (host);
-+ }
-+ if (hostname)
-+ myfree (hostname);
-+ myfree (conn);
-+}
-+
-+/*
-+ * plpgsql_down_host - mark a HOST down update ts if marked down
-+ * for the first time so that we'll know when to retry the connection
-+ */
-+static void plpgsql_down_host (HOST *host)
-+{
-+ if (host->stat != STATFAIL)
-+ {
-+ host->ts = time ((time_t *) 0) + RETRY_CONN_INTV;
-+ host->stat = STATFAIL;
-+ }
-+ PQfinish (host->db);
-+ host->db = 0;
-+}
-+
-+/**********************************************************************
-+ * public interface dict_pgsql_open
-+ * create association with database with appropriate values
-+ * parse the map's config file
-+ * allocate memory
-+ **********************************************************************/
-+DICT *dict_pgsql_open (const char *name, int unused_open_flags,
-+ int dict_flags)
-+{
-+ DICT_PGSQL *dict_pgsql;
-+ int connections;
-+
-+ dict_pgsql = (DICT_PGSQL *) dict_alloc (DICT_TYPE_PGSQL, name,
-+ sizeof (DICT_PGSQL));
-+ dict_pgsql->dict.lookup = dict_pgsql_lookup;
-+ dict_pgsql->dict.close = dict_pgsql_close;
-+ dict_pgsql->dict.flags = dict_flags | DICT_FLAG_FIXED;
-+ dict_pgsql->name = pgsqlname_parse (name);
-+ dict_pgsql->pldb = plpgsql_init (dict_pgsql->name->hostnames,
-+ dict_pgsql->name->len_hosts);
-+ if (dict_pgsql->pldb == NULL)
-+ msg_fatal ("couldn't intialize pldb!\n");
-+ dict_register (name, (DICT *) dict_pgsql);
-+ return (DICT_DEBUG (&dict_pgsql->dict));
-+}
-+
-+/* pgsqlname_parse - parse pgsql configuration file */
-+static PGSQL_NAME *pgsqlname_parse (const char *pgsqlcf_path)
-+{
-+ int i;
-+ char *nameval;
-+ char *hosts;
-+ PGSQL_NAME *name = (PGSQL_NAME *) mymalloc (sizeof (PGSQL_NAME));
-+ ARGV *hosts_argv;
-+ VSTRING *opt_dict_name;
-+
-+ /*
-+ * setup a dict containing info in the pgsql cf file. the dict has a
-+ * name, and a path. The name must be distinct from the path, or the
-+ * dict interface gets confused. The name must be distinct for two
-+ * different paths, or the configuration info will cache across different
-+ * pgsql maps, which can be confusing.
-+ */
-+ opt_dict_name = vstring_alloc (64);
-+ vstring_sprintf (opt_dict_name, "pgsql opt dict %s", pgsqlcf_path);
-+ dict_load_file (vstring_str (opt_dict_name), pgsqlcf_path);
-+ /* pgsql username lookup */
-+ if (
-+ (nameval =
-+ (char *) dict_lookup (vstring_str (opt_dict_name), "user")) == NULL)
-+ name->username = mystrdup ("");
-+ else
-+ name->username = mystrdup (nameval);
-+ if (msg_verbose)
-+ msg_info ("pgsqlname_parse(): set username to '%s'", name->username);
-+ /* password lookup */
-+ if (
-+ (nameval =
-+ (char *) dict_lookup (vstring_str (opt_dict_name),
-+ "password")) == NULL)
-+ name->password = mystrdup ("");
-+ else
-+ name->password = mystrdup (nameval);
-+ if (msg_verbose)
-+ msg_info ("pgsqlname_parse(): set password to '%s'", name->password);
-+
-+ /* database name lookup */
-+ if (
-+ (nameval =
-+ (char *) dict_lookup (vstring_str (opt_dict_name),
-+ "dbname")) == NULL)
-+
-+
-+
-+ msg_fatal ("%s: pgsql options file does not include database name",
-+ pgsqlcf_path);
-+ else
-+ name->dbname = mystrdup (nameval);
-+ if (msg_verbose)
-+ msg_info ("pgsqlname_parse(): set database name to '%s'",
-+ name->dbname);
-+
-+ /* table lookup */
-+ if (
-+ (nameval =
-+ (char *) dict_lookup (vstring_str (opt_dict_name), "table")) == NULL)
-+ msg_fatal ("%s: pgsql options file does not include table name",
-+ pgsqlcf_path);
-+ else
-+ name->table = mystrdup (nameval);
-+ if (msg_verbose)
-+ msg_info ("pgsqlname_parse(): set table name to '%s'", name->table);
-+
-+ /* select field lookup */
-+ if (
-+ (nameval =
-+ (char *) dict_lookup (vstring_str (opt_dict_name),
-+ "select_field")) == NULL)
-+
-+
-+
-+ msg_fatal ("%s: pgsql options file does not include select field",
-+ pgsqlcf_path);
-+ else
-+ name->select_field = mystrdup (nameval);
-+ if (msg_verbose)
-+ msg_info ("pgsqlname_parse(): set select_field to '%s'",
-+ name->select_field);
-+
-+ /* where field lookup */
-+ if (
-+ (nameval =
-+ (char *) dict_lookup (vstring_str (opt_dict_name),
-+ "where_field")) == NULL)
-+ msg_fatal ("%s: pgsql options file does not include where field",
-+ pgsqlcf_path);
-+ else
-+ name->where_field = mystrdup (nameval);
-+ if (msg_verbose)
-+ msg_info ("pgsqlname_parse(): set where_field to '%s'",
-+ name->where_field);
-+
-+ /* additional conditions */
-+ if (
-+ (nameval =
-+ (char *) dict_lookup (vstring_str (opt_dict_name),
-+ "additional_conditions")) == NULL)
-+ name->additional_conditions = mystrdup ("");
-+ else
-+ name->additional_conditions = mystrdup (nameval);
-+ if (msg_verbose)
-+ msg_info ("pgsqlname_parse(): set additional_conditions to '%s'",
-+ name->additional_conditions);
-+
-+ /* pgsql server hosts */
-+ if (
-+ (nameval =
-+ (char *) dict_lookup (vstring_str (opt_dict_name), "hosts")) == NULL)
-+ hosts = mystrdup ("");
-+ else
-+ hosts = mystrdup (nameval);
-+ /* coo argv interface */
-+ hosts_argv = argv_split (hosts, " ,\t\r\n");
-+
-+ if (hosts_argv->argc == 0)
-+ { /* no hosts specified,
-+ * default to 'localhost' */
-+ if (msg_verbose)
-+ msg_info
-+ ("pgsqlname_parse(): no hostnames specified, defaulting to 'localhost'");
-+ argv_add (hosts_argv, "localhost", ARGV_END);
-+ argv_terminate (hosts_argv);
-+ }
-+ name->len_hosts = hosts_argv->argc;
-+ name->hostnames =
-+
-+ (char **) mymalloc ((sizeof (char *)) * name->len_hosts);
-+ i = 0;
-+ for (i = 0; hosts_argv->argv[i] != NULL; i++)
-+ {
-+ name->hostnames[i] = mystrdup (hosts_argv->argv[i]);
-+ if (msg_verbose)
-+ msg_info
-+ ("pgsqlname_parse(): adding host '%s' to list of pgsql server hosts",
-+ name->hostnames[i]);
-+ }
-+ myfree (hosts);
-+ vstring_free (opt_dict_name);
-+ argv_free (hosts_argv);
-+ return name;
-+}
-+
-+
-+/*
-+ * plpgsql_init - initalize a PGSQL database.
-+ * Return NULL on failure, or a PLPGSQL * on success.
-+ */
-+static PLPGSQL *plpgsql_init (char *hostnames[], int len_hosts)
-+{
-+ PLPGSQL *PLDB;
-+ int i;
-+ HOST host;
-+
-+ if ((PLDB = (PLPGSQL *) mymalloc (sizeof (PLPGSQL))) == NULL)
-+ {
-+ msg_fatal ("mymalloc of pldb failed");
-+ }
-+ PLDB->len_hosts = len_hosts;
-+ if ((PLDB->db_hosts = (HOST *) mymalloc (sizeof (HOST) * len_hosts))
-+ == NULL)
-+ return NULL;
-+
-+ for (i = 0; i < len_hosts; i++)
-+ {
-+ PLDB->db_hosts[i] = host_init (hostnames[i]);
-+ }
-+ return PLDB;
-+}
-+
-+
-+/* host_init - initialize HOST structure */
-+static HOST host_init (char *hostname)
-+{
-+ HOST host;
-+
-+ host.stat = STATUNTRIED;
-+ host.hostname = mystrdup (hostname);
-+ host.db = 0;
-+ host.ts = 0;
-+ return host;
-+}
-+
-+/**********************************************************************
-+ * public interface dict_pgsql_close
-+ * unregister, disassociate from database, freeing appropriate memory
-+ **********************************************************************/
-+static void dict_pgsql_close (DICT *dict)
-+{
-+ int i;
-+ DICT_PGSQL *dict_pgsql = (DICT_PGSQL *) dict;
-+
-+ plpgsql_dealloc (dict_pgsql->pldb);
-+ myfree (dict_pgsql->name->username);
-+ myfree (dict_pgsql->name->password);
-+ myfree (dict_pgsql->name->dbname);
-+ myfree (dict_pgsql->name->table);
-+ myfree (dict_pgsql->name->select_field);
-+ myfree (dict_pgsql->name->where_field);
-+ myfree (dict_pgsql->name->additional_conditions);
-+ for (i = 0; i < dict_pgsql->name->len_hosts; i++)
-+ {
-+ myfree (dict_pgsql->name->hostnames[i]);
-+ }
-+ myfree ((char *) dict_pgsql->name->hostnames);
-+ myfree ((char *) dict_pgsql->name);
-+ dict_free (dict);
-+}
-+
-+/* plpgsql_dealloc - free memory associated with PLPGSQL close databases */
-+static void plpgsql_dealloc (PLPGSQL * PLDB)
-+{
-+ int i;
-+
-+ for (i = 0; i < PLDB->len_hosts; i++)
-+ {
-+ if (PLDB->db_hosts[i].db)
-+ PQfinish (PLDB->db_hosts[i].db);
-+ myfree (PLDB->db_hosts[i].hostname);
-+ }
-+ myfree ((char *) PLDB->db_hosts);
-+ myfree ((char *) (PLDB));
-+}
-+
-+/* pgsql_escape_string - replace mysql_escape_string */
-+void pgsql_escape_string (char *escaped, const char *name, int len)
-+{
-+ int i,
-+ j;
-+
-+ for (i = 0, j = 0; i <= len; i++, j++)
-+ {
-+ if ((name[i] == '\'') || (name[i] == '\\'))
-+ {
-+ escaped[j] = '\\';
-+ j++;
-+ }
-+ escaped[j] = name[i];
-+ }
-+}
-+
-+
-+
-+#endif
-+
---- postfix-2.0.16.orig/src/util/dict_pgsql.h 1970-01-01 01:00:00.000000000 +0100
-+++ postfix-2.0.16/src/util/dict_pgsql.h 2003-11-08 20:11:44.000000000 +0100
-@@ -0,0 +1,41 @@
-+#ifndef _DICT_PGSQL_H_INCLUDED_
-+#define _DICT_PGSQL_H_INCLUDED_
-+
-+/*++
-+/* NAME
-+/* dict_pgsql 3h
-+/* SUMMARY
-+/* dictionary manager interface to pgsql databases
-+/* SYNOPSIS
-+/* #include <dict_pgsql.h>
-+/* DESCRIPTION
-+/* .nf
-+
-+ /*
-+ * Utility library.
-+ */
-+#include <dict.h>
-+
-+ /*
-+ * External interface.
-+ */
-+#define DICT_TYPE_PGSQL "pgsql"
-+
-+extern DICT *dict_pgsql_open (const char *, int, int);
-+
-+/* LICENSE
-+/* .ad
-+/* .fi
-+/* The Secure Mailer license must be distributed with this software.
-+/* AUTHOR(S)
-+/* Scott Cotton
-+/* IC Group, Inc.
-+/* scott@icgroup.com
-+/*
-+/* Joshua Marcus
-+/* IC Group, Inc.
-+/* josh@icgroup.com
-+/*--*/
-+
-+#endif
-+
---- postfix-2.0.16.orig/src/util/Makefile.in 2003-11-08 20:10:08.000000000 +0100
-+++ postfix-2.0.16/src/util/Makefile.in 2003-11-08 20:12:29.000000000 +0100
-@@ -4,7 +4,7 @@
- chroot_uid.c clean_env.c close_on_exec.c concatenate.c ctable.c \
- dict.c dict_alloc.c dict_db.c dict_dbm.c dict_debug.c dict_env.c \
- dict_ht.c dict_mysql.c dict_ni.c dict_nis.c \
-- dict_nisplus.c dict_open.c dict_pcre.c dict_regexp.c dict_static.c \
-+ dict_nisplus.c dict_open.c dict_pcre.c dict_pgsql.c dict_regexp.c dict_static.c \
- dict_tcp.c dict_unix.c dir_forest.c doze.c duplex_pipe.c \
- environ.c events.c exec_command.c fifo_listen.c fifo_trigger.c \
- file_limit.c find_inet.c fsspace.c fullname.c get_domainname.c \
-@@ -60,7 +60,7 @@
- HDRS = argv.h attr.h base64_code.h binhash.h chroot_uid.h clean_env.h \
- connect.h ctable.h dict.h dict_db.h dict_dbm.h dict_env.h \
- dict_ht.h dict_ldap.h dict_mysql.h dict_ni.h dict_nis.h \
-- dict_nisplus.h dict_pcre.h dict_regexp.h dict_static.h dict_tcp.h \
-+ dict_nisplus.h dict_pcre.h dict_pgsql.h dict_regexp.h dict_static.h dict_tcp.h \
- dict_unix.h dir_forest.h events.h exec_command.h find_inet.h \
- fsspace.h fullname.h get_domainname.h get_hostname.h hex_quote.h \
- htable.h inet_addr_host.h inet_addr_list.h inet_addr_local.h \
-@@ -86,6 +86,7 @@
- INCL =
- PCRESO = dict_pcre.so
- MYSQLSO = dict_mysql.so
-+PGSQLSO = dict_pgsql.so
- LIB = libutil.a
- TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \
- fifo_rdonly_bug fifo_rdwr_bug fifo_trigger fsspace fullname \
-@@ -98,7 +99,7 @@
-
- LIB_DIR = ../../lib
- INC_DIR = ../../include
--LIBS = $(LIB_DIR)/$(LIB) $(LIB_DIR)/$(PCRESO) $(LIB_DIR)/$(MYSQLSO)
-+LIBS = $(LIB_DIR)/$(LIB) $(LIB_DIR)/$(PCRESO) $(LIB_DIR)/$(MYSQLSO) $(LIB_DIR)/$(PGSQLSO)
-
- .c.o:; $(CC) -fPIC $(CFLAGS) -c $*.c
-
-@@ -115,6 +116,9 @@
- $(MYSQLSO): dict_mysql.o
- gcc -shared -Wl,-soname,dict_mysql.so -o $@ $? -lmysqlclient -L. -lutil
-
-+$(PGSQLSO): dict_pgsql.o
-+ gcc -shared -Wl,-soname,dict_pgsql.so -o $@ $? -lpq -lcrypt -L. -lutil
-+
- $(LIB): $(OBJS)
- gcc -shared -Wl,-soname,libpostfix-util.so.1 -o $(LIB) $(OBJS) -ldl
-
-@@ -127,6 +131,9 @@
- $(LIB_DIR)/$(MYSQLSO): $(MYSQLSO)
- cp $(MYSQLSO) $(LIB_DIR)
-
-+$(LIB_DIR)/$(PGSQLSO): $(PGSQLSO)
-+ cp $(PGSQLSO) $(LIB_DIR)
-+
- update: $(LIBS) $(HDRS)
- -for i in $(HDRS); \
- do \
-@@ -149,7 +156,7 @@
- lint $(SRCS)
-
- clean:
-- rm -f *.o $(LIB) $(PCRESO) $(MYSQLSO) *core $(TESTPROG) \
-+ rm -f *.o $(LIB) $(PCRESO) $(MYSQLSO) $(PGSQLSO) *core $(TESTPROG) \
- junk $(MAKES) *.tmp
- rm -rf printfck
-
-@@ -591,6 +598,8 @@
- dict_ldap.o: sys_defs.h
- dict_mysql.o: dict_mysql.c
- dict_mysql.o: sys_defs.h
-+dict_pgsql.o: dict_pgsql.c
-+dict_pgsql.o: sys_defs.h
- dict_ni.o: dict_ni.c
- dict_ni.o: sys_defs.h
- dict_nis.o: dict_nis.c
-@@ -632,6 +641,7 @@
- dict_open.o: dict_ni.h
- dict_open.o: dict_ldap.h
- dict_open.o: dict_mysql.h
-+dict_open.o: dict_pgsql.h
- dict_open.o: dict_pcre.h
- dict_open.o: dict_regexp.h
- dict_open.o: dict_static.h