]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
snapshot-19991122
authorWietse Venema <wietse@porcupine.org>
Mon, 22 Nov 1999 05:00:00 +0000 (00:00 -0500)
committerWietse Venema <wietse@porcupine.org>
Thu, 17 Jan 2013 23:09:47 +0000 (18:09 -0500)
12 files changed:
postfix/BEWARE
postfix/HISTORY
postfix/INSTALL.sh
postfix/LDAP_README
postfix/RELEASE_NOTES
postfix/aux/rmail/rmail [deleted file]
postfix/conf/sample-transport.cf
postfix/global/local_transport.c
postfix/global/local_transport.h
postfix/global/mail_version.h
postfix/trivial-rewrite/resolve.c
postfix/util/dict_ldap.c

index cac1f38e980bb85d33fc9a589093d6d7833ab0df..5b0b893aa6771c357130219d5c56e783f73ba648 100644 (file)
@@ -2,7 +2,7 @@ LINUX SYSLOGD PERFORMANCE
 =========================
 
 LINUX syslogd uses synchronous writes by default, which is very
-expensive. For services such a mail it is recommended that you
+expensive. For services such as mail it is recommended that you
 disable synchronous logfile writes by prepending a - to the logfile
 name:
 
index 1c0e4895be7f96a42c901e8bd726eb8f89d1bede..2f6a99da6d3a8edca715918f327b4b88212b125c 100644 (file)
@@ -3280,3 +3280,10 @@ Apologies for any names omitted.
        Robustness: INSTALL.sh no longer uses postmap for sanity checks.
 
        Feature: INSTALL.sh now has an install_root option.
+
+       Bugfix: INSTALL.sh now installs manual pages with proper
+       permissions and ownership.
+
+       Bugfix: the LDAP client did not properly escape special
+       characters in lookup keys (patch by John Hensley).
+       File: util/dict_ldap.c.
index 50f4b83a0ab27c7df24ca088f6784f13a6dc7ea6..4555e4c081eb18b3178b552b3a199045fb457280 100644 (file)
@@ -14,13 +14,13 @@ Make backups if you want to be able to recover.
 In addition to doing a fresh install, this script can change an
 existing installation from using a world-writable maildrop to a
 group-writable one. It cannot be used to change Postfix queue
-ownership.
+file/directory ownership.
 
 Before installing files, this script prompts you for some definitions.
-You can either edit this script ahead of time, or you can specify
-your changes interactively.
+Most definitions will be remembered, so you have to specify them
+only once. All definitions have a reasonable default value.
 
-    install_root - prepend to installed file names (for package building)
+    install_root - prefix for installed file names (for package building)
 
     config_directory - directory with Postfix configuration files.
     daemon_directory - directory with Postfix daemon programs.
@@ -34,12 +34,15 @@ your changes interactively.
     mail_owner - owner of Postfix queue files.
 
     setgid - groupname, e.g., postdrop (default: no). See INSTALL section 12.
-    manpages - path to man tree (default: no). Example: /usr/local/man.
+    manpages - "no" or path to man tree. Example: /usr/local/man.
 
 EOF
 
 # By now, shells must have functions. Ultrix users must use sh5 or lose.
 
+# Apparently, some broken LINUX file utilities won't move symlinks across
+# file systems. Upgrade to a better system. Don't waste my time.
+
 compare_or_replace() {
     cmp $2 $3 >/dev/null 2>&1 || {
        rm -f junk || exit 1
@@ -71,9 +74,9 @@ case `echo -n` in
  *) n=; c='\c';;
 esac
 
-# Default settings, edit to taste or change interactively. Once this
-# script has run it saves settings to $config_directory/install.cf.
+# Default settings. These are clobbered by remembered settings.
 
+install_root=/
 config_directory=/etc/postfix
 daemon_directory=/usr/libexec/postfix
 command_directory=/usr/sbin
@@ -83,19 +86,39 @@ newaliases_path=/usr/bin/newaliases
 mailq_path=/usr/bin/mailq
 mail_owner=postfix
 setgid=no
-manpages=no
+manpages=/usr/local/man
+
+# Find out the location of configuration files.
+
+for name in install_root config_directory
+do
+    while :
+    do
+       eval echo \$n "$name: [\$$name]\  \$c"
+       read ans
+       case $ans in
+       "") break;;
+        *) eval $name=\$ans; break;;
+       esac
+    done
+done
+
+# Sanity checks
 
-while :
+for path in $install_root $config_directory
 do
-    echo $n "install_root: [/] $c"
-    read ans
-    case $ans in
-""|/|no) install_root=; break;;
-     /*) install_root=$ans; break;;
-      *) echo "install_root should be an absolute path name" 1>&2; exit 1;;
-    esac
+   case $path in
+   /*) ;;
+    *) echo "$path should be an absolute path name" 1>&2; exit 1;;
+   esac
 done
 
+# In case some systems special-case pathnames beginning with //.
+
+case $install_root in
+/) install_root=
+esac
+
 # Load defaults from existing installation.
 
 CONFIG_DIRECTORY=$install_root$config_directory
@@ -103,14 +126,16 @@ CONFIG_DIRECTORY=$install_root$config_directory
 test -f $CONFIG_DIRECTORY/main.cf && {
     for name in daemon_directory command_directory queue_directory mail_owner 
     do
-       eval "$name=\"\`bin/postconf -c $CONFIG_DIRECTORY -h $name || kill \$\$\`\""
+       eval $name='"`bin/postconf -c $CONFIG_DIRECTORY -h $name`"' || kill $$
     done
 }
 
 test -f $CONFIG_DIRECTORY/install.cf && . $CONFIG_DIRECTORY/install.cf
 
-for name in config_directory daemon_directory command_directory \
-    queue_directory sendmail_path newaliases_path mailq_path mail_owner \
+# Override default settings.
+
+for name in daemon_directory command_directory \
+    queue_directory sendmail_path newaliases_path mailq_path mail_owner\
     setgid manpages
 do
     while :
@@ -126,10 +151,20 @@ done
 
 # Sanity checks
 
-rm -f foobar-
-touch foobar-
+for path in $daemon_directory $command_directory \
+    $queue_directory $sendmail_path $newaliases_path $mailq_path $manpages
+do
+   case $path in
+   /*) ;;
+   no) ;;
+    *) echo "$path should be an absolute path name" 1>&2; exit 1;;
+   esac
+done
 
-chown $mail_owner foobar- >/dev/null 2>&1 || {
+rm -f junk || exit 1
+touch junk
+
+chown "$mail_owner" junk >/dev/null 2>&1 || {
     echo "Error: $mail_owner needs an entry in the passwd file" 1>&2
     echo "Remember, $mail_owner must have a dedicated user id and group id." 1>&2
     exit 1
@@ -137,28 +172,17 @@ chown $mail_owner foobar- >/dev/null 2>&1 || {
 
 case $setgid in
 no) ;;
- *) chgrp "$setgid" foobar- >/dev/null 2>&1 || {
+ *) chgrp "$setgid" junk >/dev/null 2>&1 || {
         echo "Error: $setgid needs an entry in the group file" 1>&2
         echo "Remember, $setgid must have a dedicated group id." 1>&2
         exit 1
     }
 esac
 
-rm -f foobar-
-
-for path in $config_directory $daemon_directory $command_directory \
-    $queue_directory $sendmail_path $newaliases_path $mailq_path $manpages
-do
-   case $path in
-   /*) ;;
-   no) ;;
-    *) echo "$path should be an absolute path name" 1>&2; exit 1;;
-   esac
-done
+rm -f junk
 
-# Create any missing directories.
+# Avoid clumsiness.
 
-CONFIG_DIRECTORY=$install_root$config_directory
 DAEMON_DIRECTORY=$install_root$daemon_directory
 COMMAND_DIRECTORY=$install_root$command_directory
 QUEUE_DIRECTORY=$install_root$queue_directory
@@ -167,9 +191,7 @@ NEWALIASES_PATH=$install_root$newaliases_path
 MAILQ_PATH=$install_root$mailq_path
 MANPAGES=$install_root$manpages
 
-case $install_root in
- /?*) test -d $install_root || mkdir -p $install_root || exit 1
-esac
+# Create any missing directories.
 
 test -d $CONFIG_DIRECTORY || mkdir -p $CONFIG_DIRECTORY || exit 1
 test -d $DAEMON_DIRECTORY || mkdir -p $DAEMON_DIRECTORY || exit 1
@@ -182,7 +204,7 @@ done
 
 # Install files. Be careful to not copy over running programs.
 
-for file in `ls libexec`
+for file in `ls libexec | grep -v '^\.'`
 do
     compare_or_replace a+x,go-w libexec/$file $DAEMON_DIRECTORY/$file || exit 1
 done
@@ -204,12 +226,14 @@ test -f $CONFIG_DIRECTORY/main.cf || {
     cp conf/* $CONFIG_DIRECTORY || exit 1
     chmod a+r,go-w $CONFIG_DIRECTORY/* || exit 1
 
-    echo "Warning: you still need to edit myorigin/mydestination in" 1>&2
-    echo "$CONFIG_DIRECTORY/main.cf. See also html/faq.html for dialup" 1>&2
-    echo "sites or for sites inside a firewalled network." 1>&2
-    echo "" 1>&2
-    echo "BTW, Edit your alias database and be sure to set up aliases" 1>&2
-    echo "for root and postmaster, then run the newaliases command." 1>&2
+    test -z "$install_root" && {
+       echo "Warning: you still need to edit myorigin/mydestination in" 1>&2
+       echo "$CONFIG_DIRECTORY/main.cf. See also html/faq.html for dialup" 1>&2
+       echo "sites or for sites inside a firewalled network." 1>&2
+       echo "" 1>&2
+       echo "BTW: Edit your alias database and be sure to set up aliases" 1>&2
+       echo "for root and postmaster, then run $NEWALIASES_PATH." 1>&2
+    }
 }
 
 # Save settings.
@@ -222,8 +246,7 @@ postconf -e \
 || exit 1
 
 (echo "# This file was generated by $0"
-for name in config_directory sendmail_path newaliases_path mailq_path \
-    setgid manpages
+for name in sendmail_path newaliases_path mailq_path setgid manpages
 do
     eval echo $name=\$$name
 done) >junk || exit 1
@@ -255,7 +278,7 @@ esac
 compare_or_replace a+x,go-w $postfix_script $CONFIG_DIRECTORY/postfix-script ||
     exit 1
 
-# Install manual pages (optional). We just clobber whatever is there.
+# Install manual pages (optional).
 
 case $manpages in
 no) ;;
@@ -266,9 +289,11 @@ no) ;;
      done
      for file in man?/*
      do
-        rm -f $MANPAGES/$file
-        cp $file $MANPAGES/$file || exit 1
-        chmod 644 $MANPAGES/$file || exit 1
+        cmp -s $file $MANPAGES/$file || {
+            rm -f $MANPAGES/$file
+            cp $file $MANPAGES/$file || exit 1
+            chmod 644 $MANPAGES/$file || exit 1
+        }
      done
     )
 esac
index 77a0989d133ac80d82ec12d99b9cee1f8b87e707..76d26eb0550b45c11a29d19aca239e53ebb985d6 100644 (file)
@@ -67,13 +67,6 @@ Defaults are given in parentheses:
        substitute for the address Postfix is trying to resolve, e.g.
                ldapsource_query_filter = (&(mail=%s)(paid_up=true))
 
-    lookup_wildcards (no)
-       Whether to search for addresses containing '*'. This has huge
-       potential for spammers, so by default, any address containing
-       '*' will cause the lookup to return nothing. Unless another
-       dictionary returns a valid lookup for it, the mail will bounce
-       with an 'unknown user' message.
-
     result_attribute (maildrop)
        The attribute Postfix will read from any directory entries
        returned by the lookup, to be resolved to an email address.
index 533cb6197902079d66cc289372417685722e7290..322c09f6094e94bbdbb8a012fbd341e938caa59c 100644 (file)
@@ -1,4 +1,4 @@
-Incompatible changes with snapshot 19991120
+Incompatible changes with snapshot 19991122
 ===========================================
 
 - In an SMTPD access map, an all-numeric right-hand side now means
@@ -13,43 +13,51 @@ main.cf.
 SMTPD access control tables. Use the permit_recipient_map feature
 instead. The loss is compensated for (see below).
 
-Major changes with snapshot 19991120
+Major changes with snapshot 19991122
 ====================================
 
+- It is now relatively safe to configure 550 status codes for the
+main.cf unknown_address_reject_code or unknown_client_reject_code
+parameters.  The SMTP server now always sends a 450 (try again)
+reply code when an UCE restriction fails due to a soft DNS error,
+regardless of what main.cf specifies.
+
+- The RBL checks now show the content of TXT records (Simon J Mudd).
+
 - The Postfix SMTP server now understands a wider range of illegal
-address formats in MAIL FROM and RCPT TO commands. In order to
-disable those forms, specify "strict_rfc821_envelopes = yes".
+address forms in MAIL FROM and RCPT TO commands. In order to disable
+those forms, specify "strict_rfc821_envelopes = yes".
 
 - Per-client/helo/sender/recipient UCE restrictions (fully-recursive
 UCE restriction parser). See the RESTRICTION_CLASS file for details.
 
-- Block mail for non-existent users at the SMTP port. On a non-relay
-host, use the following to reject mail for non-existent users and
-for non-local destinations.
+- Block mail for most non-existent users at the SMTP port. Example:
+a non-relaying host could use the following to reject mail for
+non-existent local users and for all non-local destinations.
 
     smtpd_recipient_restrictions =
-       permit_recipient_map unix:passwd .byname
+       reject_unknown_sender
+       permit_recipient_map unix:passwd.byname
        permit_recipient_map hash:/etc/postfix/canonical
        permit_recipient_map hash:/etc/postfix/virtual
        permit_recipient_map hash:/etc/aliases
        reject
 
-- "postconf -e name=value..." edits the main.cf file.  This is
-easier and safer than editing the main.cf file by hand. The edits
-are done on a temporary copy that is renamed into place.
+I haven't figured out yet how to use this easily on hosts that must
+relay mail for other systems.
 
-- "postconf -m" displays all supported lookup table types (Scott
-Cotton).
+- Use "postmap -q key" or "postalias -q key" for testing Postfix
+lookup tables or alias files.
 
-- It is now relatively safe to configure 550 status codes for the
-main.cf unknown_address_reject_code or unknown_client_reject_code
-parameters.  The SMTP server now always sends a 450 (try again)
-reply code when an UCE restriction fails due to a soft DNS error.
+- Use "postconf -e name=value..." edits the main.cf file.  This is
+easier and safer than editing the main.cf file by hand. The edits
+are done on a temporary copy that is renamed into place.
 
-- The RBL checks now show the content of TXT records (Simon J Mudd).
+- Use "postconf -m" to display all supported lookup table types
+(Scott Cotton).
 
 - New "permit_auth_destination" UCE restriction for finer-grained
-control (Jesper Skriver).
+access control (Jesper Skriver).
 
 Incompatible changes with postfix-19990906
 ==========================================
diff --git a/postfix/aux/rmail/rmail b/postfix/aux/rmail/rmail
deleted file mode 100755 (executable)
index 44c999e..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/sh
-
-# Dummy UUCP rmail command for postfix/qmail systems
-
-SENDMAIL="/usr/sbin/sendmail"
-IFS=" " read junk from junk
-
-exec $SENDMAIL -f "$from" -- "$@"
index 8ecd2b56bc2206d735e0f130e73a7a3528cb7113..ce582d29487bf960f0f2601faecd9af31b3d3fec 100644 (file)
 # transport_maps = hash:/etc/postfix/transport, nis:transport
 # transport_maps = hash:/etc/postfix/transport, netinfo:/transport
 transport_maps = 
+
+# The local_transports parameter defines the name of the default
+# transport for local mail delivery, plus zero or more names of
+# additional transports that are known to deliver locally. The SMTP
+# server's UCE restrictions use this list to decide if an address
+# would be forwarded or not.
+#
+local_transports = local
index 76783dd82a7bcdca29029c3e0e8cf512a4a5714a..cb109f7be57c3ff51af60ca71626778d013d606e 100644 (file)
@@ -6,7 +6,7 @@
 /* SYNOPSIS
 /*     #include <local_transport.h>
 /*
-/*     const char *def_local_transport()
+/*     const char *get_def_local_transport()
 /*
 /*     int     match_def_local_transport(transport)
 /*     const char *transport;
@@ -19,7 +19,7 @@
 /*     local transport, followed by the names of zero or more other
 /*     transports that deliver locally.
 /*
-/*     def_local_transport() returns the name of the default local
+/*     get_def_local_transport() returns the name of the default local
 /*     transport, that is, the first transport name specified with
 /*     the "local_transports" configuration parameter.
 /*
@@ -29,7 +29,7 @@
 /*     match_any_local_transport() determines if the named transport is
 /*     listed in the "local_transports" configuration parameter.
 /* SEE ALSO
-/*     resolve_local(3), see if address resolves locally.
+/*     resolve_local(3), see if address resolves locally
 /* LICENSE
 /* .ad
 /* .fi
@@ -84,13 +84,14 @@ static void local_transport_init(void)
     /*
      * Sanity check.
      */
-    if (!match_any_local_transport(local_transport_name))
+    if (!match_any_local_transport(local_transport_name)
+       || !match_def_local_transport(local_transport_name))
        msg_panic("%s: unable to intialize", myname);
 }
 
-/* def_local_transport - determine default local transport */
+/* get_def_local_transport - determine default local transport */
 
-const char *def_local_transport(void)
+const char *get_def_local_transport(void)
 {
 
     /*
index a1070c0921fefaf2d74af587154718c871f68da7..de8512d6984e2416f725ba4aab503311f7ffd1d7 100644 (file)
@@ -14,7 +14,7 @@
  /*
   * External interface.
   */
-extern const char *def_local_transport(void);
+extern const char *get_def_local_transport(void);
 extern int match_def_local_transport(const char *);
 extern int match_any_local_transport(const char *);
 
index a0d5395500238069c8317e030e73d7259fe979c7..c6f7979fc0418d0aa86a71fe36300006c0a229b1 100644 (file)
@@ -15,7 +15,7 @@
   * Version of this program.
   */
 #define VAR_MAIL_VERSION       "mail_version"
-#define DEF_MAIL_VERSION       "Snapshot-19991120"
+#define DEF_MAIL_VERSION       "Snapshot-19991122"
 extern char *var_mail_version;
 
 /* LICENSE
index 4bf5ec695ce7386fbc23a0950c60cec7d0e7b859..db7df1942f1476a571c13903e70105162b86c2ae 100644 (file)
@@ -197,7 +197,7 @@ void    resolve_addr(char *addr, VSTRING *channel, VSTRING *nexthop,
      * next-hop hostname (myself).
      */
     else {
-       vstring_strcpy(channel, def_local_transport());
+       vstring_strcpy(channel, get_def_local_transport());
        vstring_strcpy(nexthop, var_myhostname);
     }
 
index 8ebdeb0969d4f2a11ecf6fa5304b08147661e061..67f716cfdb30bd7c1a763327ed2ea73fdd3c75a7 100644 (file)
 /* .IP \fIldapsource_\fRquery_filter
 /*     The filter used to search for directory entries, for example
 /*     \fI(mailacceptinggeneralid=%s)\fR.
-/* .IP \fIldapsource_\fRlookup_wildcards
-/*     Whether to allow '*' in addresses to be looked up.
 /* .IP \fIldapsource_\fRresult_attribute
-/*     The attribute returned by the search, in which we expect to find
+/*     The attribute returned by the search, in which to find
 /*     RFC822 addresses, for example \fImaildrop\fR.
 /* .IP \fIldapsource_\fRbind
 /*     Whether or not to bind to the server -- LDAP v3 implementations don't
@@ -49,7 +47,7 @@
 /* .IP \fIldapsource_\fRbind_pw
 /*     \&... and this password.
 /* BUGS
-/*     Of course not! :)
+/*     Thrice a year, needed or not.
 /* SEE ALSO
 /*     dict(3) generic dictionary manager
 /* DIAGNOSTICS
@@ -65,8 +63,7 @@
 /*     Yorktown Heights, NY 10532, USA
 /*
 /*     John Hensley
-/*     Merit Network, Inc.
-/*     hensley@merit.edu
+/*     stormroll@yahoo.com
 /*
 /*--*/
 
@@ -78,6 +75,8 @@
 
 #include <sys/time.h>
 #include <stdio.h>
+#include <unistd.h>
+#include <string.h>
 #include <signal.h>
 #include <setjmp.h>
 #include <stdlib.h>
 #include "dict.h"
 #include "dict_ldap.h"
 
- /*
-  * Grr.. this module should sit in the global library, because it interacts
-  * with application-specific configuration parameters. I will have to
-  * generalize the manner in which new dictionary types can register
-  * themselves, including their configuration file parameters.
-  */
+/* Global library. */
+
+#include "../global/mail_conf.h"       /* XXX Fixme. */
 
 /*
  * structure containing all the configuration parameters for a given
@@ -110,7 +106,6 @@ typedef struct {
     int     server_port;
     char   *search_base;
     char   *query_filter;
-    int     lookup_wildcards;
     char   *result_attribute;
     int     bind;
     char   *bind_dn;
@@ -139,7 +134,8 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name)
     LDAPMessage *res = 0;
     LDAPMessage *entry = 0;
     struct timeval tv;
-    VSTRING *filter_buf = 0;
+    VSTRING *escaped_name = 0,
+           *filter_buf = 0;
     char  **attr_values;
     long    i = 0;
     int     rc = 0;
@@ -149,17 +145,6 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name)
 
     dict_errno = 0;
 
-    /*
-     * Unless configured to allow them, refuse to search for a name
-     * containing wildcards.
-     */
-    if (!dict_ldap->lookup_wildcards) {
-       if (strstr(name, "*") != NULL) {
-           msg_warn("%s: Address (%s) contains a wildcard; refusing to search. See the lookup_wildcards attribute in LDAP_README for more information.", myname, name);
-           return (0);
-       }
-    }
-
     /*
      * Initialize.
      */
@@ -171,35 +156,43 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name)
     if (msg_verbose)
        msg_info("%s: In dict_ldap_lookup", myname);
 
-    if (dict_ldap->ld == 0) {
-       msg_warn("%s: no existing connection for ldapsource %s, reopening",
-                myname, dict_ldap->ldapsource);
+    if (dict_ldap->ld == NULL) {
+       if (msg_verbose)
+           msg_info("%s: no existing connection for ldapsource %s, reopening",
+                    myname, dict_ldap->ldapsource);
+
+       if ((saved_alarm = signal(SIGALRM, dict_ldap_timeout)) == SIG_ERR) {
+           msg_warn("%s: error setting signal handler for open timeout: %m", myname);
+           dict_errno = DICT_ERR_RETRY;
+           return (0);
+       }
        if (msg_verbose)
            msg_info("%s: connecting to server %s", myname,
                     dict_ldap->server_host);
 
-       if ((saved_alarm = signal(SIGALRM, dict_ldap_timeout)) == SIG_ERR)
-           msg_fatal("%s: signal: %m", myname);
-
        alarm(dict_ldap->timeout);
        if (setjmp(env) == 0)
            dict_ldap->ld = ldap_open(dict_ldap->server_host,
                                      (int) dict_ldap->server_port);
        alarm(0);
 
-       if (signal(SIGALRM, saved_alarm) == SIG_ERR)
-           msg_fatal("%s: signal: %m", myname);
-
+       if (signal(SIGALRM, saved_alarm) == SIG_ERR) {
+           msg_warn("%s: error resetting signal handler after open: %m", myname);
+           dict_errno = DICT_ERR_RETRY;
+           return (0);
+       }
        if (msg_verbose)
            msg_info("%s: after ldap_open", myname);
 
-       if (dict_ldap->ld == 0) {
-           msg_fatal("%s: Unable to contact LDAP server %s",
-                     myname, dict_ldap->server_host);
+       if (dict_ldap->ld == NULL) {
+           msg_warn("%s: Unable to contact LDAP server %s",
+                    myname, dict_ldap->server_host);
+           dict_errno = DICT_ERR_RETRY;
+           return (0);
        } else {
 
            /*
-            * If this server requires us to bind, do so.
+            * If this server requires a bind, do so.
             */
            if (dict_ldap->bind) {
                if (msg_verbose)
@@ -209,7 +202,9 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name)
                rc = ldap_bind_s(dict_ldap->ld, dict_ldap->bind_dn,
                                 dict_ldap->bind_pw, LDAP_AUTH_SIMPLE);
                if (rc != LDAP_SUCCESS) {
-                   msg_fatal("%s: Unable to bind to server %s as %s (%d -- %s): ", myname, dict_ldap->server_host, dict_ldap->bind_dn, rc, ldap_err2string(rc));
+                   msg_warn("%s: Unable to bind to server %s as %s (%d -- %s): ", myname, dict_ldap->server_host, dict_ldap->bind_dn, rc, ldap_err2string(rc));
+                   dict_errno = DICT_ERR_RETRY;
+                   return (0);
                } else {
                    if (msg_verbose)
                        msg_info("%s: Successful bind to server %s as %s (%d -- %s): ", myname, dict_ldap->server_host, dict_ldap->bind_dn, rc, ldap_err2string(rc));
@@ -220,37 +215,52 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name)
                         myname, dict_ldap->ldapsource);
        }
     }
-
     /*
-     * Look for entries matching query_filter.
+     * Prepare the query.
      */
     tv.tv_sec = dict_ldap->timeout;
     tv.tv_usec = 0;
+    escaped_name = vstring_alloc(20);
     filter_buf = vstring_alloc(30);
 
+    /* Any wildcards and escapes in the supplied address should be escaped. */
+    if (strchr(name, '*') || strchr(name, '\\')) {
+       if (msg_verbose)
+           msg_info("%s: found wildcard in %s", myname, name);
+       for (sub = (char *) name; *sub != '\0'; sub++) {
+           if (*sub == '*' || *sub == '\\') {
+               vstring_strncat(escaped_name, "\\", 1);
+               vstring_strncat(escaped_name, sub, 1);
+           } else {
+               vstring_strncat(escaped_name, sub, 1);
+           }
+       }
+       if (msg_verbose)
+           msg_info("%s: with wildcards escaped, it's %s", myname, vstring_str(escaped_name));
+    } else
+       vstring_strcpy(escaped_name, (char *) name);
+
     /* Does the supplied query_filter even include a substitution? */
     if (strstr(dict_ldap->query_filter, "%s") == NULL) {
+       /* No, log the fact and continue. */
        msg_warn("%s: fixed query_filter %s is probably useless", myname,
                 dict_ldap->query_filter);
        vstring_strcpy(filter_buf, dict_ldap->query_filter);
     } else {
 
-       /*
-        * OK, let's replace all the instances of %s with the address to look
-        * up.
-        */
+       /* Yes, replace all instances of %s with the address to look up. */
        sub = dict_ldap->query_filter;
        end = sub + strlen(dict_ldap->query_filter);
        while (sub < end) {
 
            /*
             * Make sure it's %s and not something else, though it wouldn't
-            * really matter; we could skip any single character.
+            * really matter; the token could be any single character.
             */
            if (*(sub) == '%') {
                if ((sub + 1) != end && *(sub + 1) != 's')
-                   msg_fatal("%s: invalid lookup substitution format '%%%c'!", myname, *(sub + 1));
-               vstring_strcat(filter_buf, name);
+                   msg_warn("%s: invalid lookup substitution format '%%%c'!", myname, *(sub + 1));
+               vstring_strcat(filter_buf, vstring_str(escaped_name));
                sub++;
            } else
                vstring_strncat(filter_buf, sub, 1);
@@ -258,6 +268,7 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name)
        }
     }
 
+    /* On to the search. */
     if (msg_verbose)
        msg_info("%s: searching with filter %s", myname,
                 vstring_str(filter_buf));
@@ -265,33 +276,24 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name)
     if ((rc = ldap_search_st(dict_ldap->ld, dict_ldap->search_base,
                             LDAP_SCOPE_SUBTREE,
                             vstring_str(filter_buf),
-                            0, 0, &tv, &res)) != LDAP_SUCCESS) {
-
-       ldap_unbind(dict_ldap->ld);
-       dict_ldap->ld = 0;
-       if (msg_verbose)
-           msg_info("%s: freed connection handle for LDAP source %s", myname, dict_ldap->ldapsource);
-       msg_fatal("%s: Unable to search base %s at server %s (%d -- %s): ",
-                 myname, dict_ldap->search_base, dict_ldap->server_host, rc,
-                 ldap_err2string(rc));
-
-    } else {
-
+                            0, 0, &tv, &res)) == LDAP_SUCCESS) {
        /*
-        * Extract the requested result_attribute.
+        * Search worked; extract the requested result_attribute.
         */
        if (msg_verbose)
-           msg_info("%s: search found %d", myname,
+           msg_info("%s: search found %d matches", myname,
                     ldap_count_entries(dict_ldap->ld, res));
 
+       /* There could have been lots of hits. */
        for (entry = ldap_first_entry(dict_ldap->ld, res); entry != NULL; entry = ldap_next_entry(dict_ldap->ld, entry)) {
+
+           /* And each entry could have multiple attributes. */
            attr_values = ldap_get_values(dict_ldap->ld, entry,
                                          dict_ldap->result_attribute);
            if (attr_values == NULL) {
                msg_warn("%s: entry doesn't have any values for %s", myname, dict_ldap->result_attribute);
                continue;
            }
-
            /*
             * Append each returned address to the result list.
             */
@@ -304,18 +306,28 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name)
        }
        if (msg_verbose)
            msg_info("%s: search returned: %s", myname, vstring_str(result));
+    } else {
+       /* Rats. That didn't work. */
+       msg_warn("%s: search error %d: %s ", myname, rc,
+                ldap_err2string(rc));
+
+       /*
+        * Tear down the connection so it gets set up from scratch on the
+        * next lookup.
+        */
+       ldap_unbind(dict_ldap->ld);
+       dict_ldap->ld = NULL;
+
+       /* And tell the caller to try again later. */
+       dict_errno = DICT_ERR_RETRY;
     }
 
-    /*
-     * Cleanup. Always return with dict_errno set when we were unable to
-     * perform the query.
-     */
+    /* Cleanup. */
     if (res != 0)
        ldap_msgfree(res);
-    else
-       dict_errno = 1;
     if (filter_buf != 0)
        vstring_free(filter_buf);
+
     return (VSTRING_LEN(result) > 0 ? vstring_str(result) : 0);
 }
 
@@ -412,17 +424,6 @@ DICT   *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags)
        msg_info("%s: %s is %s", myname, vstring_str(config_param),
                 dict_ldap->query_filter);
 
-    /*
-     * get configured value of "ldapsource_lookup_wildcards"; default to
-     * false
-     */
-    vstring_sprintf(config_param, "%s_lookup_wildcards", ldapsource);
-    dict_ldap->lookup_wildcards =
-       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->lookup_wildcards);
-
     vstring_sprintf(config_param, "%s_result_attribute", ldapsource);
     dict_ldap->result_attribute =
        mystrdup((char *) get_mail_conf_str(vstring_str(config_param),
@@ -462,28 +463,33 @@ DICT   *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags)
        msg_info("%s: connecting to server %s", myname,
                 dict_ldap->server_host);
 
-    if ((saved_alarm = signal(SIGALRM, dict_ldap_timeout)) == SIG_ERR)
-       msg_fatal("%s: signal: %m", myname);
-
+    if ((saved_alarm = signal(SIGALRM, dict_ldap_timeout)) == SIG_ERR) {
+       msg_warn("%s: error setting signal handler for open timeout: %m", myname);
+       dict_errno = DICT_ERR_RETRY;
+       return (0);
+    }
     alarm(dict_ldap->timeout);
     if (setjmp(env) == 0)
        dict_ldap->ld = ldap_open(dict_ldap->server_host,
                                  (int) dict_ldap->server_port);
     alarm(0);
 
-    if (signal(SIGALRM, saved_alarm) == SIG_ERR)
-       msg_fatal("%s: signal: %m", myname);
-
-    if (msg_verbose)
-       msg_info("%s: after ldap_open", myname);
-
-    if (dict_ldap->ld == 0) {
-       msg_fatal("%s: Unable to contact LDAP server %s",
-                 myname, dict_ldap->server_host);
+    if (signal(SIGALRM, saved_alarm) == SIG_ERR) {
+       msg_warn("%s: error resetting signal handler after open: %m", myname);
+       dict_errno = DICT_ERR_RETRY;
+       return (0);
+    }
+    if (dict_ldap->ld == NULL) {
+       msg_warn("%s: Unable to contact LDAP server %s",
+                myname, dict_ldap->server_host);
+       dict_errno = DICT_ERR_RETRY;
+       return (0);
     } else {
 
+       if (msg_verbose)
+           msg_info("%s: after ldap_open", myname);
        /*
-        * If this server requires us to bind, do so.
+        * If this server requires a bind, do so.
         */
        if (dict_ldap->bind) {
            if (msg_verbose)
@@ -493,7 +499,9 @@ DICT   *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags)
            rc = ldap_bind_s(dict_ldap->ld, dict_ldap->bind_dn,
                             dict_ldap->bind_pw, LDAP_AUTH_SIMPLE);
            if (rc != LDAP_SUCCESS) {
-               msg_fatal("%s: Unable to bind to server %s as %s (%d -- %s): ", myname, dict_ldap->server_host, dict_ldap->bind_dn, rc, ldap_err2string(rc));
+               msg_warn("%s: Unable to bind to server %s as %s (%d -- %s): ", myname, dict_ldap->server_host, dict_ldap->bind_dn, rc, ldap_err2string(rc));
+               dict_errno = DICT_ERR_RETRY;
+               return (0);
            } else {
                if (msg_verbose)
                    msg_info("%s: Successful bind to server %s as %s (%d -- %s): ", myname, dict_ldap->server_host, dict_ldap->bind_dn, rc, ldap_err2string(rc));