diff --git a/postfix/BEWARE b/postfix/BEWARE index cac1f38e9..5b0b893aa 100644 --- a/postfix/BEWARE +++ b/postfix/BEWARE @@ -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: diff --git a/postfix/HISTORY b/postfix/HISTORY index 1c0e4895b..2f6a99da6 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -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. diff --git a/postfix/INSTALL.sh b/postfix/INSTALL.sh index 50f4b83a0..4555e4c08 100644 --- a/postfix/INSTALL.sh +++ b/postfix/INSTALL.sh @@ -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 -while : +# Find out the location of configuration files. + +for name 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 + while : + do + eval echo \$n "$name: [\$$name]\ \$c" + read ans + case $ans in + "") break;; + *) eval $name=\$ans; break;; + esac + done done +# Sanity checks + +for path in $install_root $config_directory +do + 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,27 +151,7 @@ done # Sanity checks -rm -f foobar- -touch foobar- - -chown $mail_owner foobar- >/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 -} - -case $setgid in -no) ;; - *) chgrp "$setgid" foobar- >/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 \ +for path in $daemon_directory $command_directory \ $queue_directory $sendmail_path $newaliases_path $mailq_path $manpages do case $path in @@ -156,9 +161,28 @@ do esac done -# Create any missing directories. +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 +} + +case $setgid in +no) ;; + *) 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 junk + +# 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 diff --git a/postfix/LDAP_README b/postfix/LDAP_README index 77a0989d1..76d26eb05 100644 --- a/postfix/LDAP_README +++ b/postfix/LDAP_README @@ -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. diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES index 533cb6197..322c09f60 100644 --- a/postfix/RELEASE_NOTES +++ b/postfix/RELEASE_NOTES @@ -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 +I haven't figured out yet how to use this easily on hosts that must +relay mail for other systems. + +- Use "postmap -q key" or "postalias -q key" for testing Postfix +lookup tables or alias files. + +- 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. -- "postconf -m" displays all supported lookup table types (Scott -Cotton). - -- 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. - -- 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 index 44c999ee7..000000000 --- a/postfix/aux/rmail/rmail +++ /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" -- "$@" diff --git a/postfix/conf/sample-transport.cf b/postfix/conf/sample-transport.cf index 8ecd2b56b..ce582d294 100644 --- a/postfix/conf/sample-transport.cf +++ b/postfix/conf/sample-transport.cf @@ -16,3 +16,11 @@ # 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 diff --git a/postfix/global/local_transport.c b/postfix/global/local_transport.c index 76783dd82..cb109f7be 100644 --- a/postfix/global/local_transport.c +++ b/postfix/global/local_transport.c @@ -6,7 +6,7 @@ /* SYNOPSIS /* #include /* -/* 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) { /* diff --git a/postfix/global/local_transport.h b/postfix/global/local_transport.h index a1070c092..de8512d69 100644 --- a/postfix/global/local_transport.h +++ b/postfix/global/local_transport.h @@ -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 *); diff --git a/postfix/global/mail_version.h b/postfix/global/mail_version.h index a0d539550..c6f7979fc 100644 --- a/postfix/global/mail_version.h +++ b/postfix/global/mail_version.h @@ -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 diff --git a/postfix/trivial-rewrite/resolve.c b/postfix/trivial-rewrite/resolve.c index 4bf5ec695..db7df1942 100644 --- a/postfix/trivial-rewrite/resolve.c +++ b/postfix/trivial-rewrite/resolve.c @@ -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); } diff --git a/postfix/util/dict_ldap.c b/postfix/util/dict_ldap.c index 8ebdeb096..67f716cfd 100644 --- a/postfix/util/dict_ldap.c +++ b/postfix/util/dict_ldap.c @@ -36,10 +36,8 @@ /* .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 #include +#include +#include #include #include #include @@ -92,12 +91,9 @@ #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));