Subject: Postfix 19990906 Patch 08

From: [email-masked] (Wietse Venema)
Date: Mon, 22 Nov 1999 19:45:40 -0500 (EST)

A few minor glitches were found in Postfix 19990906 Patchlevel 07,
which was released a week ago.

  - The RFC822 parser mis-parsed atoms beginning with \. The \ would
    become an atom by itself. This bug also appears in older Postfix
    releases.

  - The LDAP client did not properly escape special characters in
    lookup keys. This bug also appears in older Postfix releases.
    The LDAP client revision makes up the majority of this patch.

  - When doing a fresh install, INSTALL.sh would bail out on systems
    with short (non-FQDN) hostnames.

  - When installing manual pages, INSTALL.sh copied the permissions
    and ownership attributes from the Postfix source tree, instead
    of forcing owner root and mode 0644.

A patch for the above defects is appended below the signature. A
complete source kit, postfix-19990906-pl08.tar.gz, will be made
available from the primary site:

   ftp://ftp.porcupine.org/mirrors/postfix-release/official/
   http://ftp.porcupine.org/mirrors/postfix-release/index.html

including a PGP signature (postfix-19990906-pl08.tar.gz.sig). 

These files are expected to arrive on the Postfix download sites
in a day or so. See http://www.postfix.org/ for a list of download
sites.

	Wietse

Prereq: "Postfix-19990906-pl07"
diff -bcr ../postfix-19990906-pl07/global/mail_version.h ./global/mail_version.h
*** ../postfix-19990906-pl07/global/mail_version.h	Sat Nov 13 12:19:54 1999
--- ./global/mail_version.h	Sun Nov 21 21:52:03 1999
***************
*** 15,21 ****
    * Version of this program.
    */
  #define VAR_MAIL_VERSION	"mail_version"
! #define DEF_MAIL_VERSION	"Postfix-19990906-pl07"
  extern char *var_mail_version;
  
  /* LICENSE
--- 15,21 ----
    * Version of this program.
    */
  #define VAR_MAIL_VERSION	"mail_version"
! #define DEF_MAIL_VERSION	"Postfix-19990906-pl08"
  extern char *var_mail_version;
  
  /* LICENSE
diff -bcr ../postfix-19990906-pl07/HISTORY ./HISTORY
*** ../postfix-19990906-pl07/HISTORY	Sat Nov 13 12:59:42 1999
--- ./HISTORY	Mon Nov 22 19:03:36 1999
***************
*** 3140,3142 ****
--- 3140,3161 ----
  
  	Documentation: removed spurious "do not edit" comments from
  	the sample pcre and regexp configuration files.
+ 
+ 19991117
+ 
+ 	Portability: SunOS 4 has no SA_RESTART. File: util/watchdog.c.
+ 
+ 19991118
+ 
+ 	Bugfix: the RFC822 address parser would misparse a leading
+ 	\ as an atom all by itself. Problem reported by Keith
+ 	Stevenson @ louisville.edu. File: global/tok822_parse.c.
+ 
+ 19991121
+ 
+ 	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 -bcr ../postfix-19990906-pl07/INSTALL.sh ./INSTALL.sh
*** ../postfix-19990906-pl07/INSTALL.sh	Sat Nov 13 12:57:20 1999
--- ./INSTALL.sh	Mon Nov 22 19:11:37 1999
***************
*** 7,13 ****
  umask 022
  
  # Workaround, should edit main.cf in place.
! trap 'rm -f ./main.cf; exit 1' 0 1 2 3 15
  
  cat <<EOF
  
--- 7,13 ----
  umask 022
  
  # Workaround, should edit main.cf in place.
! trap 'rm -f ./main.cf ./junk; exit 1' 1 2 3 15
  
  cat <<EOF
  
***************
*** 143,148 ****
--- 143,149 ----
  
  # Workaround, should edit main.cf in place.
  echo "mail_owner = $owner" >./main.cf
+ echo "myhostname = xx.yy" >>./main.cf
  
  bin/postmap -c . -q "$owner" unix:passwd.byname >/dev/null || {
      echo "$owner needs an entry in the passwd file" 1>&2
***************
*** 241,250 ****
  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.
  
  case $manpages in
  no) ;;
!  *) test -d $manpages || mkdir -p $manpages || exit 1
!     (cd man && tar cf - man?) | (cd $manpages && tar xf -)
  esac
--- 242,265 ----
  compare_or_replace a+x,go-w $postfix_script $config_directory/postfix-script ||
      exit 1
  
! # Install manual pages (optional). We just replace whatever is there.
  
  case $manpages in
  no) ;;
!  *) (
!      cd man || exit 1
!      for dir in man?
! 	 do mkdir -p $manpages/$dir || exit 1
!      done
!      for file in man?/*
!      do
! 	 cmp -s $file $manpages/$file || {
! 	     rm -f $manpages/$file
! 	     cp $file $manpages/$file || exit 1
! 	     chmod 644 $manpages/$file || exit 1
! 	 }
!      done
!     )
  esac
+ 
+ rm -f ./main.cf ./junk
diff -bcr ../postfix-19990906-pl07/LDAP_README ./LDAP_README
*** ../postfix-19990906-pl07/LDAP_README	Sun Sep  5 16:09:02 1999
--- ./LDAP_README	Mon Nov 22 16:26:46 1999
***************
*** 67,79 ****
  	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.
--- 67,72 ----
diff -bcr ../postfix-19990906-pl07/Makefile.in ./Makefile.in
*** ../postfix-19990906-pl07/Makefile.in	Mon Sep  6 12:40:09 1999
--- ./Makefile.in	Mon Nov 22 18:40:48 1999
***************
*** 20,25 ****
--- 20,28 ----
  	 (set -e; echo "[$$i]"; cd $$i; $(MAKE) $(OPTS) $@) || exit 1; \
  	done
  
+ install: update
+ 	@sh INSTALL.sh
+ 
  printfck: update
  
  depend clean:
diff -bcr ../postfix-19990906-pl07/conf/master.cf ./conf/master.cf
*** ../postfix-19990906-pl07/conf/master.cf	Thu Jul  8 13:29:21 1999
--- ./conf/master.cf	Fri Nov 19 20:37:15 1999
***************
*** 71,75 ****
  ifmail    unix  -       n       n       -       -       pipe
      flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
  bsmtp     unix  -       n       n       -       -       pipe
!     flags=F user=foo argv=/usr/local/sbin/bsmtp -f $sender $nexthop $recipient
! 
--- 71,74 ----
  ifmail    unix  -       n       n       -       -       pipe
      flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
  bsmtp     unix  -       n       n       -       -       pipe
!     flags=F. user=foo argv=/usr/local/sbin/bsmtp -f $sender $nexthop $recipient
Only in ../postfix-19990906-pl07/dns: snd.h
diff -bcr ../postfix-19990906-pl07/global/tok822_parse.c ./global/tok822_parse.c
*** ../postfix-19990906-pl07/global/tok822_parse.c	Fri Dec 11 13:55:23 1998
--- ./global/tok822_parse.c	Fri Nov 19 16:14:49 1999
***************
*** 324,334 ****
  	} else if (ch == '"') {
  	    tp = tok822_alloc(TOK822_QSTRING, (char *) 0);
  	    COLLECT_SKIP_LAST(tp, str, ch, ch != '"');
! 	} else if (strchr(tok822_opchar, ch)) {
  	    tp = tok822_alloc(ch, (char *) 0);
  	} else {
  	    tp = tok822_alloc(TOK822_ATOM, (char *) 0);
! 	    VSTRING_ADDCH(tp->vstr, ch);
  	    COLLECT(tp, str, ch, !ISSPACE(ch) && !strchr(tok822_opchar, ch));
  	    tok822_quote_atom(tp);
  	}
--- 324,334 ----
  	} else if (ch == '"') {
  	    tp = tok822_alloc(TOK822_QSTRING, (char *) 0);
  	    COLLECT_SKIP_LAST(tp, str, ch, ch != '"');
! 	} else if (ch != '\\' && strchr(tok822_opchar, ch)) {
  	    tp = tok822_alloc(ch, (char *) 0);
  	} else {
  	    tp = tok822_alloc(TOK822_ATOM, (char *) 0);
! 	    str -= 1;				/* \ may be first */
  	    COLLECT(tp, str, ch, !ISSPACE(ch) && !strchr(tok822_opchar, ch));
  	    tok822_quote_atom(tp);
  	}
diff -bcr ../postfix-19990906-pl07/util/watchdog.c ./util/watchdog.c
*** ../postfix-19990906-pl07/util/watchdog.c	Sun Nov  7 11:16:14 1999
--- ./util/watchdog.c	Fri Nov 19 16:12:47 1999
***************
*** 155,161 ****
--- 155,165 ----
      wp->saved_watchdog = watchdog_curr;
      wp->saved_time = alarm(0);
      sigemptyset(&sig_action.sa_mask);
+ #ifdef SA_RESTART
      sig_action.sa_flags = SA_RESTART;
+ #else
+     sig_action.sa_flags = 0;
+ #endif
      sig_action.sa_handler = watchdog_event;
      if (sigaction(SIGALRM, &sig_action, &wp->saved_action) < 0)
  	msg_fatal("%s: sigaction(SIGALRM): %m", myname);
diff -bcr ../postfix-19990906-pl07/util/dict_ldap.c ./util/dict_ldap.c
*** ../postfix-19990906-pl07/util/dict_ldap.c	Fri Nov  5 09:38:23 1999
--- ./util/dict_ldap.c	Mon Nov 22 16:56:36 1999
***************
*** 36,45 ****
  /* .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
  /*	RFC822 addresses, for example \fImaildrop\fR.
  /* .IP \fIldapsource_\fRbind
  /*	Whether or not to bind to the server -- LDAP v3 implementations don't
--- 36,43 ----
  /* .IP \fIldapsource_\fRquery_filter
  /*	The filter used to search for directory entries, for example
  /*	\fI(mailacceptinggeneralid=%s)\fR.
  /* .IP \fIldapsource_\fRresult_attribute
! /*	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,55 ****
  /* .IP \fIldapsource_\fRbind_pw
  /*	\&... and this password.
  /* BUGS
! /*	Of course not! :)
  /* SEE ALSO
  /*	dict(3) generic dictionary manager
  /* DIAGNOSTICS
--- 47,53 ----
  /* .IP \fIldapsource_\fRbind_pw
  /*	\&... and this password.
  /* BUGS
! /*	Thrice a year, needed or not.
  /* SEE ALSO
  /*	dict(3) generic dictionary manager
  /* DIAGNOSTICS
***************
*** 65,72 ****
  /*	Yorktown Heights, NY 10532, USA
  /*
  /*	John Hensley
! /*	Merit Network, Inc.
! /*	[email-masked]
  /*
  /*--*/
  
--- 63,69 ----
  /*	Yorktown Heights, NY 10532, USA
  /*
  /*	John Hensley
! /*	[email-masked]
  /*
  /*--*/
  
***************
*** 78,83 ****
--- 75,82 ----
  
  #include <sys/time.h>
  #include <stdio.h>
+ #include <unistd.h>
+ #include <string.h>
  #include <signal.h>
  #include <setjmp.h>
  #include <stdlib.h>
***************
*** 92,103 ****
  #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.
!   */
  
  /*
   * structure containing all the configuration parameters for a given
--- 91,99 ----
  #include "dict.h"
  #include "dict_ldap.h"
  
! /* Global library. */
! 
! #include "../global/mail_conf.h"	/* XXX Fixme. */
  
  /*
   * structure containing all the configuration parameters for a given
***************
*** 110,116 ****
      int     server_port;
      char   *search_base;
      char   *query_filter;
-     int     lookup_wildcards;
      char   *result_attribute;
      int     bind;
      char   *bind_dn;
--- 106,111 ----
***************
*** 139,145 ****
      LDAPMessage *res = 0;
      LDAPMessage *entry = 0;
      struct timeval tv;
!     VSTRING *filter_buf = 0;
      char  **attr_values;
      long    i = 0;
      int     rc = 0;
--- 134,141 ----
      LDAPMessage *res = 0;
      LDAPMessage *entry = 0;
      struct timeval tv;
!     VSTRING *escaped_name = 0,
!            *filter_buf = 0;
      char  **attr_values;
      long    i = 0;
      int     rc = 0;
***************
*** 150,166 ****
      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.
       */
      if (result == 0)
--- 146,151 ----
***************
*** 171,205 ****
      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 (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 (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);
  	} else {
  
  	    /*
! 	     * If this server requires us to bind, do so.
  	     */
  	    if (dict_ldap->bind) {
  		if (msg_verbose)
--- 156,198 ----
      if (msg_verbose)
  	msg_info("%s: In dict_ldap_lookup", myname);
  
!     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);
  
  	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_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 == 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 a bind, do so.
  	     */
  	    if (dict_ldap->bind) {
  		if (msg_verbose)
***************
*** 209,215 ****
  		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));
  		} 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));
--- 202,210 ----
  		rc = ldap_bind_s(dict_ldap->ld, dict_ldap->bind_dn,
  				 dict_ldap->bind_pw, LDAP_AUTH_SIMPLE);
  		if (rc != LDAP_SUCCESS) {
! 		    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,256 ****
  			 myname, dict_ldap->ldapsource);
  	}
      }
- 
      /*
!      * Look for entries matching query_filter.
       */
      tv.tv_sec = dict_ldap->timeout;
      tv.tv_usec = 0;
      filter_buf = vstring_alloc(30);
  
      /* Does the supplied query_filter even include a substitution? */
      if (strstr(dict_ldap->query_filter, "%s") == NULL) {
  	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.
! 	 */
  	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.
  	     */
  	    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);
  		sub++;
  	    } else
  		vstring_strncat(filter_buf, sub, 1);
--- 215,266 ----
  			 myname, dict_ldap->ldapsource);
  	}
      }
      /*
!      * 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 {
  
! 	/* 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; the token could be any single character.
  	     */
  	    if (*(sub) == '%') {
  		if ((sub + 1) != end && *(sub + 1) != 's')
! 		    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,263 ****
--- 268,274 ----
  	}
      }
  
+     /* On to the search. */
      if (msg_verbose)
  	msg_info("%s: searching with filter %s", myname,
  		 vstring_str(filter_buf));
***************
*** 265,297 ****
      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 {
! 
  	/*
! 	 * Extract the requested result_attribute.
  	 */
  	if (msg_verbose)
! 	    msg_info("%s: search found %d", myname,
  		     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)) {
  	    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.
  	     */
--- 276,299 ----
      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) {
  	/*
! 	 * Search worked; extract the requested result_attribute.
  	 */
  	if (msg_verbose)
! 	    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,321 ****
  	}
  	if (msg_verbose)
  	    msg_info("%s: search returned: %s", myname, vstring_str(result));
!     }
  
      /*
!      * Cleanup. Always return with dict_errno set when we were unable to
!      * perform the query.
       */
      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);
  }
  
--- 306,333 ----
  	}
  	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. */
      if (res != 0)
  	ldap_msgfree(res);
      if (filter_buf != 0)
  	vstring_free(filter_buf);
+ 
      return (VSTRING_LEN(result) > 0 ? vstring_str(result) : 0);
  }
  
***************
*** 412,428 ****
  	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),
--- 424,429 ----
***************
*** 462,489 ****
  	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 (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);
      } else {
  
  	/*
! 	 * If this server requires us to bind, do so.
  	 */
  	if (dict_ldap->bind) {
  	    if (msg_verbose)
--- 463,495 ----
  	msg_info("%s: connecting to server %s", myname,
  		 dict_ldap->server_host);
  
!     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_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 a bind, do so.
  	 */
  	if (dict_ldap->bind) {
  	    if (msg_verbose)
***************
*** 493,499 ****
  	    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));
  	    } 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));
--- 499,507 ----
  	    rc = ldap_bind_s(dict_ldap->ld, dict_ldap->bind_dn,
  			     dict_ldap->bind_pw, LDAP_AUTH_SIMPLE);
  	    if (rc != LDAP_SUCCESS) {
! 		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));