]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-1.1.9-20020513
authorWietse Venema <wietse@porcupine.org>
Mon, 13 May 2002 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <viktor@dukhovni.org>
Tue, 5 Feb 2013 06:27:57 +0000 (06:27 +0000)
36 files changed:
postfix/HISTORY
postfix/README_FILES/LDAP_README
postfix/RELEASE_NOTES
postfix/conf/sample-ldap.cf
postfix/conf/sample-misc.cf
postfix/html/trivial-rewrite.8.html
postfix/man/man8/trivial-rewrite.8
postfix/src/bounce/bounce_append_service.c
postfix/src/bounce/bounce_notify_util.c
postfix/src/cleanup/cleanup_map11.c
postfix/src/cleanup/cleanup_map1n.c
postfix/src/cleanup/cleanup_masquerade.c
postfix/src/cleanup/cleanup_message.c
postfix/src/cleanup/cleanup_rewrite.c
postfix/src/global/mail_copy.c
postfix/src/global/mail_params.h
postfix/src/global/mail_version.h
postfix/src/global/quote_821_local.c
postfix/src/global/quote_821_local.h
postfix/src/global/quote_822_local.c
postfix/src/global/quote_822_local.h
postfix/src/global/rewrite_clnt.c
postfix/src/global/tok822_parse.c
postfix/src/lmtp/lmtp_proto.c
postfix/src/local/delivered.c
postfix/src/pipe/pipe.c
postfix/src/qmqpd/qmqpd.c
postfix/src/showq/showq.c
postfix/src/smtp/smtp_proto.c
postfix/src/smtpd/smtpd.c
postfix/src/smtpd/smtpd_check.c
postfix/src/trivial-rewrite/resolve.c
postfix/src/trivial-rewrite/trivial-rewrite.c
postfix/src/util/attr_scan0.c
postfix/src/util/attr_scan64.c
postfix/src/util/dict_ldap.c

index f34023646053f808ead6e53545d6cf36399bf96f..6d68a08e63f7ad386940435c889b511b1025b5bd 100644 (file)
@@ -6417,6 +6417,24 @@ Apologies for any names omitted.
        recipient address, resulting in too much quoting in bounce
        reports.  Files:  src/smtp_proto.c, lmtp/lmtp_proto.c.
 
+20020513
+
+
+       Bugfix: the LDAP client used the "wrong" @ character in
+       addresses with multiple @. LaMont Jones, HP. File:
+       util/dict_ldap.c.
+
+       Feature: lots of new LDAP stuff: result_filter (filter to
+       expand results from queries), chase_referrals,
+       LaMont Jones, HP. The LDAP bind timeout now works thanks
+       to Victor Duchovni, Morgan Stanley. File:  util/dict_ldap.c.
+
+       Cleanup: specify "resolve_dequoted_address = no" to prevent
+       Postfix from looking inside quotes for extra @ etc. characters
+       when resolving an address. This behavior is technically
+       more correct, but it opens a mail relay loophole with "user
+       @domain"@domain when relaying mail to a Sendmail system.
+
 Open problems:
 
        Low: all table lookups should consistently use internalized
index a69ebde3b9985c18f6cdb214b599fc8d78804d09..0b4dd869bc6e8c747433a86ef6ea08aa64d1d83d 100644 (file)
@@ -94,6 +94,11 @@ parameter below, "server_host", would be defined in main.cf as
        substitute for the address Postfix is trying to resolve, e.g.
                ldapsource_query_filter = (&(mail=%s)(paid_up=true))
 
+    result_filter (%s)
+       Filter applied to result attributes.  Supports the same expansions
+       as the query_filter, and can be easily used to append (or prepend)
+       text.
+
     domain (Default is to ignore this.)
        This is a list of domain names, paths to files, or dictionaries.
        If specified, only lookups for the domains on this list will be
@@ -166,6 +171,13 @@ parameter below, "server_host", would be defined in main.cf as
        other possible values, please bring it to the attention of the
        postfix-users@postfix.org mailing list.
 
+    chase_referrals (0)
+       Sets (or clears) LDAP_OPT_REFERRALS (requires LDAP version 3
+       support.
+
+    version (2)
+       Specifies the LDAP protocol version to use.
+
     debuglevel (0)
        What level to set for debugging in the OpenLDAP libraries.
 
@@ -327,5 +339,6 @@ Keith Stevenson: RFC 2254 escaping in queries.
 Samuel Tardieu: Noticed that searches could include wildcards, prompting
                 the work on RFC 2254 escaping in queries. Spotted a bug
                 in binding.
+Sami Haahtinen: Referral chasing and v3 support.
 
 And of course Wietse.
index 7fbd39fa139722f3f0caa9893ff5b87e6ab67541..b962ce2ae2f0193c591b8ffef398a74e9e46d069 100644 (file)
@@ -12,6 +12,20 @@ snapshot release).  Patches change the patchlevel and the release
 date. Snapshots change only the release date, unless they include
 the same bugfixes as a patch release.
 
+Major changes with Postfix snapshot 1.1.9-20020513
+==================================================
+
+Updated LDAP client module with better handling of dead LDAP servers,
+and with configurable filtering of query results.
+
+In order to allow user@domain@domain addresses from untrusted
+systems, specify "resolve_dequoted_address = no" in main.cf (when
+resolving mail, quote the address localpart as per RFC 822, so that
+@ or % or !  operators in the address localpart remain invisible).
+Although this behavior is technically more correct, it also opens
+opportunities for mail relay attacks when Postfix provides backup
+MX service for Sendmail systems.
+
 Incompatible changes with Postfix snapshot 1.1.9-20020512
 =========================================================
 
index 58a752a33b256e34946c9349711dfa1c55c51281..e8bb8bccc60989dd882da34119d1ae6843914abb 100644 (file)
 #
 #ldap_query_filter = (mailacceptinggeneralid=%s)
 
+# The ldap_result_filter parameter specifies the filter to be applied
+# to the result attribute(s).  See ldap_query_filter for valid expansions.
+#
+#ldap_result_filter = %s
+
 # The ldap_result_attribute parameter specifies the attribute returned by
 # the search.
 #
index d9f7b005db273e64fbc550a33701f26e715b1d56..4f790f0ca4f16309292caf8bf284ac937245b739 100644 (file)
@@ -389,6 +389,22 @@ relayhost =
 #relocated_maps = hash:/etc/postfix/relocated
 relocated_maps =
 
+# The resolve_dequoted_address parameter controls how Postfix
+# resolves addresses.
+# 
+# By default, the Postfix address resolver does not quote the address
+# localpart as per RFC 822, so that additional @ or % or ! operators
+# remain visible. This behavior is safe but it is also technically
+# incorrect.
+# 
+# If you specify "resolve_dequoted_address = no", then the Postfix
+# resolver will not know about additional @ etc. operators in the
+# address localpart. This opens opportunities for obscure mail relay
+# attacks with user@domain@domain addresses when Postfix provides
+# backup MX service for Sendmail systems.
+#
+resolve_dequoted_address = no
+
 # The syslog_facility parameter controls where Postfix logging is
 # sent by the syslog daemon. Specify a logging facility as defined
 # in syslog.conf(5). The default logging facility is "mail".
index b5293eebfabc1b5c3a02977a256a2cb820b794b7..3f81b47a50b2d6acad0d77895c90e115aec2b528 100644 (file)
@@ -79,6 +79,14 @@ TRIVIAL-REWRITE(8)                             TRIVIAL-REWRITE(8)
               The domain that locally-posted mail appears to come
               from.
 
+       <b>resolve</b><i>_</i><b>unquoted</b><i>_</i><b>address</b>
+              When resolving an address, do not quote the address
+              localpart as per <a href="http://www.faqs.org/rfcs/rfc822.html">RFC 822</a>, so that additional  <b>@</b>,  <b>%</b>
+              or  <b>!</b>   characters  remain visible. This is techni-
+              cally  incorrect,  but  allows  us  to  stop  relay
+              attacks  when forwarding mail to a Sendmail primary
+              MX host.
+
 <b>Rewriting</b>
        <b>allow</b><i>_</i><b>percent</b><i>_</i><b>hack</b>
               Rewrite <i>user</i>%<i>domain</i> to <i>user</i>@<i>domain</i>.
@@ -94,11 +102,11 @@ TRIVIAL-REWRITE(8)                             TRIVIAL-REWRITE(8)
 
 <b>Routing</b>
        <b>local</b><i>_</i><b>transport</b>
-              Where  to  deliver mail for destinations that match
-              $<b>mydestination</b> or  $<b>inet</b><i>_</i><b>interfaces</b>.   The  default
+              Where to deliver mail for destinations  that  match
+              $<b>mydestination</b>  or  $<b>inet</b><i>_</i><b>interfaces</b>.   The default
               transport is <b>local</b>.
 
-              Syntax  is  <i>transport</i>:<i>nexthop</i>; see <a href="transport.5.html"><b>transport</b>(5)</a> for
+              Syntax is <i>transport</i>:<i>nexthop</i>; see  <a href="transport.5.html"><b>transport</b>(5)</a>  for
               details. The :<i>nexthop</i> part is optional.
 
        <b>default</b><i>_</i><b>transport</b>
@@ -106,23 +114,23 @@ TRIVIAL-REWRITE(8)                             TRIVIAL-REWRITE(8)
               is explicitly given in the <a href="transport.5.html"><b>transport</b>(5)</a> table.  The
               default transport is <b>smtp</b>.
 
-              Syntax is <i>transport</i>:<i>nexthop</i>; see  <a href="transport.5.html"><b>transport</b>(5)</a>  for
+              Syntax  is  <i>transport</i>:<i>nexthop</i>; see <a href="transport.5.html"><b>transport</b>(5)</a> for
               details. The :<i>nexthop</i> part is optional.
 
        <b>parent</b><i>_</i><b>domain</b><i>_</i><b>matches</b><i>_</i><b>subdomains</b>
-              List  of Postfix features that use <i>domain.name</i> pat-
+              List of Postfix features that use <i>domain.name</i>  pat-
               terns  to  match  <i>sub.domain.name</i>  (as  opposed  to
               requiring <i>.domain.name</i> patterns).
 
        <b>relayhost</b>
-              The  default host to send non-local mail to when no
+              The default host to send non-local mail to when  no
               entry is matched in the <a href="transport.5.html"><b>transport</b>(5)</a> table.
 
-              When no <b>relayhost</b>  is  specified,  mail  is  routed
+              When  no  <b>relayhost</b>  is  specified,  mail is routed
               directly to the destination's mail exchanger.
 
        <b>transport</b><i>_</i><b>maps</b>
-              List  of tables with <i>domain</i> to (<i>transport,</i> <i>nexthop</i>)
+              List of tables with <i>domain</i> to (<i>transport,</i>  <i>nexthop</i>)
               mappings.
 
 <b>SEE</b> <b>ALSO</b>
@@ -131,7 +139,7 @@ TRIVIAL-REWRITE(8)                             TRIVIAL-REWRITE(8)
        <a href="transport.5.html">transport(5)</a> transport table format
 
 <b>LICENSE</b>
-       The Secure Mailer license must be  distributed  with  this
+       The  Secure  Mailer  license must be distributed with this
        software.
 
 <b>AUTHOR(S)</b>
index 3aa705f575e58bea3bf24d81ef39dd26493cb574..2fcdf307d3852aa55228ed4a8938faaf3eda0dde 100644 (file)
@@ -80,6 +80,12 @@ This information is used to determine if
 List of domains that this machine considers local.
 .IP \fBmyorigin\fR
 The domain that locally-posted mail appears to come from.
+.IP \fBresolve_unquoted_address\fR
+When resolving an address, do not quote the address localpart as
+per RFC 822, so that additional \fB@\fR, \fB%\fR or \fB!\fR
+characters remain visible. This is technically incorrect, but
+allows us to stop relay attacks when forwarding mail to a Sendmail
+primary MX host.
 .SH Rewriting
 .ad
 .fi
index 65697bb99bffeab12bbb05e2ff8afac38bfc55fa..82620f02b7684783cc14efb64cc95b4904dfb6b4 100644 (file)
@@ -103,8 +103,7 @@ int     bounce_append_service(char *service, char *queue_id,
 
     if (*recipient)
        vstream_fprintf(log, "<%s>: ",
-          printable(vstring_str(quote_822_local(in_buf, recipient,
-                                                QUOTE_FLAG_8BITCLEAN)), '?'));
+          printable(vstring_str(quote_822_local(in_buf, recipient)), '?'));
     else
        vstream_fprintf(log, "<>: ");
     vstream_fputs(printable(why, '?'), log);
index b6a919efd8e93a69d2785f15320ee0ef9d205046..d4264d945e18d6a679e64dac4bafa1c3e725d4d3 100644 (file)
@@ -302,8 +302,7 @@ int     bounce_header(VSTREAM *bounce, BOUNCE_INFO *bounce_info,
                        "Subject: Delayed Mail (still being retried)");
     }
     post_mail_fprintf(bounce, "To: %s",
-                     STR(quote_822_local(bounce_info->buf, dest,
-                                         QUOTE_FLAG_8BITCLEAN)));
+                     STR(quote_822_local(bounce_info->buf, dest)));
 
     /*
      * MIME header.
index 350799861e2d24b88ddb20eb02577271fea5fe6a..6d699d3483b1da4371df653d2f28fa8f91846dcb 100644 (file)
@@ -162,7 +162,7 @@ void    cleanup_map11_internal(CLEANUP_STATE *state, VSTRING *addr,
      * checking in one place, instead of having error handling code all over
      * the place.
      */
-    quote_822_local(temp, STR(addr), QUOTE_FLAG_8BITCLEAN);
+    quote_822_local(temp, STR(addr));
     cleanup_map11_external(state, temp, maps, propagate);
     unquote_822_local(addr, STR(temp));
     vstring_free(temp);
index 0a181e4637d4779035f4bf1889125ac1ab52ce4d..4e6e6f290e99236c11fec81615d48a4876e03832 100644 (file)
@@ -113,8 +113,7 @@ ARGV   *cleanup_map1n_internal(CLEANUP_STATE *state, char *addr,
                         state->queue_id, maps->title, addr);
                break;
            }
-           quote_822_local(state->temp1, argv->argv[arg],
-                           QUOTE_FLAG_8BITCLEAN);
+           quote_822_local(state->temp1, argv->argv[arg]);
            if ((lookup = mail_addr_map(maps, STR(state->temp1), propagate)) != 0) {
                saved_lhs = mystrdup(argv->argv[arg]);
                for (i = 0; i < lookup->argc; i++) {
index eeb38ee3c7fb13a450c0b1a6847bb298a4110396..b742e25ca9152def9d1ad710bc14aea7df245432 100644 (file)
@@ -165,7 +165,7 @@ void    cleanup_masquerade_internal(VSTRING *addr, ARGV *masq_domains)
 {
     VSTRING *temp = vstring_alloc(100);
 
-    quote_822_local(temp, STR(addr), QUOTE_FLAG_8BITCLEAN);
+    quote_822_local(temp, STR(addr));
     cleanup_masquerade_external(temp, masq_domains);
     unquote_822_local(addr, STR(temp));
 
index 3357152206e86f217e622a2e9a5fb9032528e69b..86785783e413b481a12c9e4256accb8d14258ebf 100644 (file)
@@ -197,7 +197,6 @@ static void cleanup_rewrite_sender(CLEANUP_STATE *state, HEADER_OPTS *hdr_opts)
                cleanup_extract_internal(state->header_buf, *tpp);
     }
     vstring_sprintf(state->header_buf, "%s: ", hdr_opts->name);
-    /* XXX should quote_822_local the address local parts. */
     tok822_externalize(state->header_buf, tree, TOK822_STR_HEAD);
     myfree((char *) addr_list);
     tok822_free_tree(tree);
@@ -251,7 +250,6 @@ static void cleanup_rewrite_recip(CLEANUP_STATE *state, HEADER_OPTS *hdr_opts)
            cleanup_masquerade_tree(*tpp, cleanup_masq_domains);
     }
     vstring_sprintf(state->header_buf, "%s: ", hdr_opts->name);
-    /* XXX should quote_822_local the address local parts. */
     tok822_externalize(state->header_buf, tree, TOK822_STR_HEAD);
     myfree((char *) addr_list);
     tok822_free_tree(tree);
@@ -465,8 +463,7 @@ static void cleanup_missing_headers(CLEANUP_STATE *state)
     if ((state->headers_seen & (1 << (state->resent[0] ?
                                      HDR_RESENT_FROM : HDR_FROM))) == 0) {
        quote_822_local(state->temp1, *state->sender ?
-                       state->sender : MAIL_ADDR_MAIL_DAEMON,
-                       QUOTE_FLAG_8BITCLEAN);
+                       state->sender : MAIL_ADDR_MAIL_DAEMON);
        vstring_sprintf(state->temp2, "%sFrom: %s",
                        state->resent, vstring_str(state->temp1));
        if (*state->sender && state->fullname && *state->fullname) {
index ab714c6387af9f4089d9aa025342d83670c7d05a..1ae6871d8bf9f9ef42fa1580d2494384a809acd0 100644 (file)
@@ -95,7 +95,7 @@ void    cleanup_rewrite_internal(VSTRING *result, const char *addr)
     VSTRING *dst = vstring_alloc(100);
     VSTRING *src = vstring_alloc(100);
 
-    quote_822_local(src, addr, QUOTE_FLAG_8BITCLEAN);
+    quote_822_local(src, addr);
     cleanup_rewrite_external(dst, STR(src));
     unquote_822_local(result, STR(dst));
     vstring_free(dst);
index 6b8122b70ad3a0a5a6feab17c62ace031776a2c3..c2d328981ca292a2b292e0aa30bdd3a0cb6c9fd2 100644 (file)
@@ -144,7 +144,7 @@ int     mail_copy(const char *sender, const char *delivered,
     if (flags & (MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH)) {
        if (sender == 0)
            msg_panic("%s: null sender", myname);
-       quote_822_local(buf, sender, QUOTE_FLAG_8BITCLEAN);
+       quote_822_local(buf, sender);
        if (flags & MAIL_COPY_FROM) {
            time(&now);
            vstream_fprintf(dst, "From %s  %.24s%s", *sender == 0 ?
@@ -159,7 +159,7 @@ int     mail_copy(const char *sender, const char *delivered,
     if (flags & MAIL_COPY_DELIVERED) {
        if (delivered == 0)
            msg_panic("%s: null delivered", myname);
-       quote_822_local(buf, delivered, QUOTE_FLAG_8BITCLEAN);
+       quote_822_local(buf, delivered);
        vstream_fprintf(dst, "Delivered-To: %s%s",
                        lowercase(vstring_str(buf)), eol);
     }
index 7a1d62dd234f9004468323ac6660a7d418982df9..0385b336f9a279324967f0610d7be11901fb0cd7 100644 (file)
@@ -741,7 +741,7 @@ extern char *var_smtp_helo_name;
 #define VAR_SMTP_RAND_ADDR     "smtp_randomize_addresses"
 #define DEF_SMTP_RAND_ADDR     1
 extern bool var_smtp_rand_addr;
+
 #define VAR_SMTP_LINE_LIMIT    "smtp_line_length_limit"
 #define DEF_SMTP_LINE_LIMIT    990
 extern int var_smtp_line_limit;
@@ -1419,6 +1419,17 @@ extern int var_fault_inj_code;
 #define DEF_README_DIR                 "no"
 #endif
 
+ /*
+  * Safety: resolve the address with unquoted localpart (default, but
+  * technically incorrect), instead of resolving the address with quoted
+  * localpart (technically correct, but unsafe). The default prevents mail
+  * relay loopholes with "user@domain"@domain when relaying mail to a
+  * Sendmail system.
+  */
+#define VAR_RESOLVE_DEQUOTED           "resolve_dequoted_address"
+#define DEF_RESOLVE_DEQUOTED           1
+extern bool var_resolve_dequoted;
+
  /*
   * Service names. The transport (TCP, FIFO or UNIX-domain) type is frozen
   * because you cannot simply mix them, and accessibility (private/public) is
index 8370958da9758d66739210261840f2e5fd87fd3a..0f5af1c0be8a81cd673acbad165e609779521072 100644 (file)
@@ -20,7 +20,7 @@
   * Patches change the patchlevel and the release date. Snapshots change the
   * release date only, unless they include the same bugfix as a patch release.
   */
-#define MAIL_RELEASE_DATE      "20020512"
+#define MAIL_RELEASE_DATE      "20020513"
 
 #define VAR_MAIL_VERSION       "mail_version"
 #define DEF_MAIL_VERSION       "1.1.9-" MAIL_RELEASE_DATE
index 2fa6bd526ff95ac2b7cb5757b69213fa751dcdb7..b87cf7eb10ea38c5391dd9fc9673eb058836dce4 100644 (file)
@@ -6,14 +6,20 @@
 /* SYNOPSIS
 /*     #include "quote_821_local.h"
 /*
-/*     VSTRING *quote_821_local(dst, src, flags)
+/*     VSTRING *quote_821_local(dst, src)
+/*     VSTRING *dst;
+/*     char    *src;
+/*
+/*     VSTRING *quote_821_local_flags(dst, src, flags)
 /*     VSTRING *dst;
 /*     char    *src;
 /*     int     flags;
 /* DESCRIPTION
 /*     quote_821_local() quotes the local part of a mailbox address and
 /*     returns a result that can be used in SMTP commands as specified
-/*     by RFC 821.
+/*     by RFC 821. It implements an 8-bit clean version of RFC 821.
+/*
+/*     quote_821_local_flags() provides finer control.
 /*
 /*     Arguments:
 /* .IP dst
@@ -124,9 +130,9 @@ static VSTRING *make_821_quoted_string(VSTRING *dst, char *local_part,
     return (dst);
 }
 
-/* quote_821_local - quote local part of address according to rfc 821 */
+/* quote_821_local_flags - quote local part of address according to rfc 821 */
 
-VSTRING *quote_821_local(VSTRING *dst, char *addr, int flags)
+VSTRING *quote_821_local_flags(VSTRING *dst, char *addr, int flags)
 {
     char   *at;
 
@@ -164,8 +170,7 @@ int     main(void)
 
     while (vstring_fgets_nonl(src, VSTREAM_IN)) {
        vstream_fprintf(VSTREAM_OUT, "%s\n",
-                       vstring_str(quote_821_local(dst, vstring_str(src),
-                                                   QUOTE_FLAG_8BITCLEAN)));
+                       vstring_str(quote_821_local(dst, vstring_str(src))));
        vstream_fflush(VSTREAM_OUT);
     }
     exit(0);
index c2a8564fba63e5f8e12ca844d0defa03f4507831..d1e91e5623588a351a226c72eccc4fdee7bedf21 100644 (file)
@@ -21,7 +21,9 @@
  /*
   * External interface.
   */
-extern VSTRING *quote_821_local(VSTRING *, char *, int);
+extern VSTRING *quote_821_local_flags(VSTRING *, char *, int);
+#define quote_821_local(dst, src) \
+       quote_821_local_flags((dst), (src), QUOTE_FLAG_8BITCLEAN)
 
 /* LICENSE
 /* .ad
index 54392805d2171183c9498261792a8d392cdca1d5..5f4672f742394221833e246414834c6f372f36ae 100644 (file)
@@ -6,7 +6,11 @@
 /* SYNOPSIS
 /*     #include <quote_822_local.h>
 /*
-/*     VSTRING *quote_822_local(dst, src, flags)
+/*     VSTRING *quote_822_local(dst, src)
+/*     VSTRING *dst;
+/*     const char *src;
+/*
+/*     VSTRING *quote_822_local_flags(dst, src, flags)
 /*     VSTRING *dst;
 /*     const char *src;
 /*     int     flags;
@@ -18,7 +22,9 @@
 /*     quote_822_local() quotes the local part of a mailbox and
 /*     returns a result that can be used in message headers as
 /*     specified by RFC 822 (actually, an 8-bit clean version of
-/*     RFC 822).
+/*     RFC 822). It implements an 8-bit clean version of RFC 822.
+/*
+/*     quote_822_local_flags() provides finer control.
 /*
 /*     unquote_822_local() transforms the local part of a mailbox
 /*     address to unquoted (internal) form.
@@ -136,9 +142,9 @@ static VSTRING *make_822_quoted_string(VSTRING *dst, const char *local_part,
     return (dst);
 }
 
-/* quote_822_local - quote local part of mailbox according to rfc 822 */
+/* quote_822_local_flags - quote local part of mailbox according to rfc 822 */
 
-VSTRING *quote_822_local(VSTRING *dst, const char *mbox, int flags)
+VSTRING *quote_822_local_flags(VSTRING *dst, const char *mbox, int flags)
 {
     const char *start;                 /* first byte of localpart */
     const char *end;                   /* first byte after localpart */
@@ -220,7 +226,7 @@ int     main(int unused_argc, char **unused_argv)
     VSTRING *unquoted = vstring_alloc(100);
 
     while (vstring_fgets_nonl(raw, VSTREAM_IN)) {
-       quote_822_local(quoted, STR(raw), QUOTE_FLAG_8BITCLEAN);
+       quote_822_local(quoted, STR(raw));
        vstream_printf("quoted:         %s\n", STR(quoted));
        unquote_822_local(unquoted, STR(quoted));
        vstream_printf("unquoted:       %s\n", STR(unquoted));
index a24f34b1000d95f7560883a7a852619c8a211753..cb5454360413afd089653154c30dd9601d5842f6 100644 (file)
  /*
   * External interface.
   */
-extern VSTRING *quote_822_local(VSTRING *, const char *, int);
+extern VSTRING *quote_822_local_flags(VSTRING *, const char *, int);
 extern VSTRING *unquote_822_local(VSTRING *, const char *);
+#define quote_822_local(dst, src) \
+       quote_822_local_flags((dst), (src), QUOTE_FLAG_8BITCLEAN)
 
 /* LICENSE
 /* .ad
index 5cff7e0ba3f7dde3304d60557fabd17eb31e891a..c02375b44b715e0995bafec7891974955d2d964e 100644 (file)
@@ -166,7 +166,7 @@ VSTRING *rewrite_clnt_internal(const char *ruleset, const char *addr, VSTRING *r
      * Convert the address from internal address form to external RFC822
      * form, then rewrite it. After rewriting, convert to internal form.
      */
-    quote_822_local(src, addr, QUOTE_FLAG_8BITCLEAN);
+    quote_822_local(src, addr);
     rewrite_clnt(ruleset, STR(src), dst);
     unquote_822_local(result, STR(dst));
     vstring_free(src);
index 097b4c351b723bc3b78118ae64c75879d30a2ff5..194b973a0e6ef9a3d0d2b35050143c6681fc29de 100644 (file)
@@ -248,8 +248,8 @@ VSTRING *tok822_externalize(VSTRING *vp, TOK822 *tree, int flags)
        case TOK822_ADDR:
            tmp = vstring_alloc(100);
            tok822_internalize(tmp, tp->head, TOK822_STR_NONE);
-           quote_822_local(vp, vstring_str(tmp),
-                           QUOTE_FLAG_8BITCLEAN | QUOTE_FLAG_APPEND);
+           quote_822_local_flags(vp, vstring_str(tmp),
+                                 QUOTE_FLAG_8BITCLEAN | QUOTE_FLAG_APPEND);
            vstring_free(tmp);
            break;
        case TOK822_ATOM:
index 6dffbc729b2c7dd2c4c869c966af04f8647bcc63..2fb41f1cf9c89282d0f0525c75aff715cd5eb712 100644 (file)
@@ -297,7 +297,7 @@ static int lmtp_loop(LMTP_STATE *state, int send_state, int recv_state)
      */
 #define REWRITE_ADDRESS(dst, src) do { \
          if (*(src)) { \
-             quote_821_local(dst, src, QUOTE_FLAG_8BITCLEAN); \
+             quote_821_local(dst, src); \
              lowercase(vstring_str(dst)); \
          } else { \
              vstring_strcpy(dst, src); \
index 6ab3f569612f1a31994c00da6fe9cf32643efbed..50faeae64b50a4ce064277b09ac29a66acaee228 100644 (file)
@@ -137,7 +137,7 @@ int     delivered_find(HTABLE *table, char *address)
      * header. We must therefore apply the same transformation when looking
      * up the recipient. Lowercase the delivered-to address for consistency.
      */
-    quote_822_local(buf, address, QUOTE_FLAG_8BITCLEAN);
+    quote_822_local(buf, address);
     lowercase(STR(buf));
     ht = htable_locate(table, STR(buf));
     return (ht != 0);
index e5d0805b6d4886a1904ec402dec122f87588df55..59741bda0483b4fc65eb41f70d8428ac231fde62 100644 (file)
@@ -382,7 +382,7 @@ static void morph_recipient(VSTRING *buf, const char *address, int flags)
      * Quote the recipient address as appropriate.
      */
     if (flags & PIPE_OPT_QUOTE_LOCAL)
-       quote_822_local(buf, address, QUOTE_FLAG_8BITCLEAN);
+       quote_822_local(buf, address);
     else
        vstring_strcpy(buf, address);
 
@@ -826,7 +826,7 @@ static int deliver_message(DELIVER_REQUEST *request, char *service, char **argv)
 
     buf = vstring_alloc(10);
     if (attr.flags & PIPE_OPT_QUOTE_LOCAL) {
-       quote_822_local(buf, request->sender, QUOTE_FLAG_8BITCLEAN);
+       quote_822_local(buf, request->sender);
        dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, STR(buf));
     } else
        dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, request->sender);
index 47106e99069d2597a1572beaf36bbb98945c4fd7..263ac17dac1c9cf190ef262a1840634827e1058e 100644 (file)
@@ -340,7 +340,7 @@ static void qmqpd_write_content(QMQPD_STATE *state)
                    "\tby %s (%s) with %s id %s",
                    var_myhostname, var_mail_name,
                    state->protocol, state->queue_id);
-       quote_822_local(state->buf, state->recipient, QUOTE_FLAG_8BITCLEAN);
+       quote_822_local(state->buf, state->recipient);
        rec_fprintf(state->cleanup, REC_TYPE_NORM,
                 "\tfor <%s>; %s", STR(state->buf), mail_date(state->time));
     } else {
index 475d31eac3ff4ecea269ccb98c768fc11a528761..8a59cbec6e8677341695d9e31ec39d1ce81c3e9c 100644 (file)
@@ -137,7 +137,7 @@ static void showq_report(VSTREAM *client, char *queue, char *id,
        case REC_TYPE_FROM:
            if (*start == 0)
                start = var_empty_addr;
-           quote_822_local(printable_quoted_addr, start, QUOTE_FLAG_8BITCLEAN);
+           quote_822_local(printable_quoted_addr, start);
            printable(STR(printable_quoted_addr), '?');
            vstream_fprintf(client, DATA_FORMAT, id, status,
                          msg_size > 0 ? msg_size : size, arrival_time > 0 ?
@@ -147,7 +147,7 @@ static void showq_report(VSTREAM *client, char *queue, char *id,
        case REC_TYPE_RCPT:
            if (*start == 0)                    /* can't happen? */
                start = var_empty_addr;
-           quote_822_local(printable_quoted_addr, start, QUOTE_FLAG_8BITCLEAN);
+           quote_822_local(printable_quoted_addr, start);
            printable(STR(printable_quoted_addr), '?');
            if (dup_filter == 0
              || htable_locate(dup_filter, STR(printable_quoted_addr)) == 0)
index fe9d2a177ec35a95b687eb87ef4a4ac01bcd05dd..e52e3c37d9a624f207f306a26b9d697a5918448f 100644 (file)
@@ -306,7 +306,7 @@ int     smtp_xfer(SMTP_STATE *state)
      */
 #define REWRITE_ADDRESS(dst, mid, src) do { \
        if (*(src)) { \
-           quote_821_local(mid, src, QUOTE_FLAG_8BITCLEAN); \
+           quote_821_local(mid, src); \
            smtp_unalias_addr(dst, vstring_str(mid)); \
        } else { \
            vstring_strcpy(dst, src); \
@@ -315,7 +315,7 @@ int     smtp_xfer(SMTP_STATE *state)
 
 #define QUOTE_ADDRESS(dst, src) do { \
        if (*(src)) { \
-           quote_821_local(dst, src, QUOTE_FLAG_8BITCLEAN); \
+           quote_821_local(dst, src); \
        } else { \
            vstring_strcpy(dst, src); \
        } \
index ddc016bc9cfad6740950a14361c4faf5c1256ae5..bf7d7748a20f2c11a034a7e231072da30ea7c6af 100644 (file)
@@ -954,7 +954,7 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
                    "\tby %s (%s) with %s id %s",
                    var_myhostname, var_mail_name,
                    state->protocol, state->queue_id);
-       quote_822_local(state->buffer, state->recipient, QUOTE_FLAG_8BITCLEAN);
+       quote_822_local(state->buffer, state->recipient);
        rec_fprintf(state->cleanup, REC_TYPE_NORM,
                "\tfor <%s>; %s", STR(state->buffer), mail_date(state->time));
     } else {
@@ -965,7 +965,7 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv)
                    "\tid %s; %s", state->queue_id, mail_date(state->time));
     }
 #ifdef RECEIVED_ENVELOPE_FROM
-    quote_822_local(state->buffer, state->sender, QUOTE_FLAG_8BITCLEAN);
+    quote_822_local(state->buffer, state->sender);
     rec_fprintf(state->cleanup, REC_TYPE_NORM,
                "\t(envelope-from %s)", STR(state->buffer));
 #endif
index 19bcf4adeab4f6e733ac963438a0dc70d2de12db..d6f7bc331d1add9c13c76e44a74b0aa450319f89 100644 (file)
@@ -739,8 +739,8 @@ static const char *check_mail_addr_find(SMTPD_STATE *state,
 
 /* resolve_final - do we do final delivery for the domain? */
 
-static int resolve_final(SMTPD_STATE *state, const char *reply_name, 
-const char *domain)
+static int resolve_final(SMTPD_STATE *state, const char *reply_name,
+                                const char *domain)
 {
 
     /* If matches $mydestination or $inet_interfaces. */
@@ -1027,7 +1027,7 @@ static int permit_auth_destination(SMTPD_STATE *state, char *recipient)
        return (SMTPD_CHECK_DUNNO);
 
     /*
-     * Permit final delivery: the destination matches mydestination, 
+     * Permit final delivery: the destination matches mydestination,
      * virtual_maps, or virtual_mailbox_maps.
      */
     if (resolve_final(state, recipient, domain))
@@ -1729,8 +1729,8 @@ static int check_mail_access(SMTPD_STATE *state, const char *table,
      * from resolve_clnt_query() must be fully qualified.
      */
     if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0) {
-       msg_warn("%s: no @domain in address: %s", myname, 
-       CONST_STR(reply->recipient));
+       msg_warn("%s: no @domain in address: %s", myname,
+                CONST_STR(reply->recipient));
        return (0);
     }
     domain += 1;
@@ -1765,7 +1765,7 @@ static int check_mail_access(SMTPD_STATE *state, const char *table,
      * below was brought to you by the keyboard of Victor Duchovni, Morgan
      * Stanley and hacked up a bit by Wietse.
      */
-#define SUSPICIOUS(domain, reply, state, reply_name, reply_class) \
+#define SUSPICIOUS(reply, reply_class) \
        (var_allow_untrust_route == 0 \
        && (reply->flags & RESOLVE_FLAG_ROUTED) \
        && strcmp(reply_class, SMTPD_NAME_RECIPIENT) == 0)
@@ -1778,7 +1778,7 @@ static int check_mail_access(SMTPD_STATE *state, const char *table,
                               found, reply_name, reply_class, def_acl)) != 0
        || *found)
        CHECK_MAIL_ACCESS_RETURN(status == SMTPD_CHECK_OK
-             && SUSPICIOUS(domain, reply, state, reply_name, reply_class) ?
+                                && SUSPICIOUS(reply, reply_class) ?
                                 SMTPD_CHECK_DUNNO : status);
 
     /*
@@ -1789,7 +1789,7 @@ static int check_mail_access(SMTPD_STATE *state, const char *table,
                              found, reply_name, reply_class, def_acl)) != 0
            || *found)
            CHECK_MAIL_ACCESS_RETURN(status == SMTPD_CHECK_OK
-             && SUSPICIOUS(domain, reply, state, reply_name, reply_class) ?
+                                    && SUSPICIOUS(reply, reply_class) ?
                                     SMTPD_CHECK_DUNNO : status);
 
     /*
@@ -1799,7 +1799,7 @@ static int check_mail_access(SMTPD_STATE *state, const char *table,
                              found, reply_name, reply_class, def_acl)) != 0
        || *found)
        CHECK_MAIL_ACCESS_RETURN(status == SMTPD_CHECK_OK
-             && SUSPICIOUS(domain, reply, state, reply_name, reply_class) ?
+                                && SUSPICIOUS(reply, reply_class) ?
                                 SMTPD_CHECK_DUNNO : status);
 
     /*
@@ -1813,7 +1813,7 @@ static int check_mail_access(SMTPD_STATE *state, const char *table,
     myfree(local_at);
     if (status != 0 || *found)
        CHECK_MAIL_ACCESS_RETURN(status == SMTPD_CHECK_OK
-             && SUSPICIOUS(domain, reply, state, reply_name, reply_class) ?
+                                && SUSPICIOUS(reply, reply_class) ?
                                 SMTPD_CHECK_DUNNO : status);
 
     /*
@@ -1827,7 +1827,7 @@ static int check_mail_access(SMTPD_STATE *state, const char *table,
        myfree(local_at);
        if (status != 0 || *found)
            CHECK_MAIL_ACCESS_RETURN(status == SMTPD_CHECK_OK
-             && SUSPICIOUS(domain, reply, state, reply_name, reply_class) ?
+                                    && SUSPICIOUS(reply, reply_class) ?
                                     SMTPD_CHECK_DUNNO : status);
     }
 
index ad23735ffaa21077fc10fbfafa0684acc05c7121..7a7db6fa3806390dcc00f3330c140976edfc9ccb 100644 (file)
@@ -101,10 +101,17 @@ void    resolve_addr(char *addr, VSTRING *channel, VSTRING *nexthop,
      * While quoting the address local part, do not treat @ as a special
      * character. This allows us to detect extra @ characters and block
      * source routed relay attempts.
+     * 
+     * But practically, we have to look at the unquoted form so that routing
+     * characters like @ remain visible, in order to stop user@domain@domain
+     * relay attempts when forwarding mail to a primary Sendmail MX host.
      */
-    quote_822_local(addr_buf, addr,
-                   QUOTE_FLAG_8BITCLEAN | QUOTE_FLAG_EXPOSE_AT);
-    tree = tok822_scan_addr(vstring_str(addr_buf));
+    if (var_resolve_dequoted) {
+       tree = tok822_scan_addr(addr);
+    } else {
+       quote_822_local(addr_buf, addr);
+       tree = tok822_scan_addr(vstring_str(addr_buf));
+    }
 
     /*
      * Preliminary resolver: strip off all instances of the local domain.
index 1411d7b0e0cd8e51db9583df23bc1ec73cb5a139..7cc9a44d0f2192eacb52c28f8ce132e4fd79ab64 100644 (file)
 /*     List of domains that this machine considers local.
 /* .IP \fBmyorigin\fR
 /*     The domain that locally-posted mail appears to come from.
+/* .IP \fBresolve_unquoted_address\fR
+/*     When resolving an address, do not quote the address localpart as
+/*     per RFC 822, so that additional \fB@\fR, \fB%\fR or \fB!\fR
+/*     characters remain visible. This is technically incorrect, but
+/*     allows us to stop relay attacks when forwarding mail to a Sendmail
+/*     primary MX host.
 /* .SH Rewriting
 /* .ad
 /* .fi
@@ -167,6 +173,7 @@ bool    var_append_dot_mydomain;
 bool    var_append_at_myorigin;
 bool    var_percent_hack;
 char   *var_local_transport;
+int     var_resolve_dequoted;
 
 /* rewrite_service - read request and send reply */
 
@@ -239,6 +246,7 @@ int     main(int argc, char **argv)
        VAR_APP_DOT_MYDOMAIN, DEF_APP_DOT_MYDOMAIN, &var_append_dot_mydomain,
        VAR_APP_AT_MYORIGIN, DEF_APP_AT_MYORIGIN, &var_append_at_myorigin,
        VAR_PERCENT_HACK, DEF_PERCENT_HACK, &var_percent_hack,
+       VAR_RESOLVE_DEQUOTED, DEF_RESOLVE_DEQUOTED, &var_resolve_dequoted,
        0,
     };
 
index 0ccfc040faff67944974310338f0cd2d8cba59c3..528c6147a94084408e7dbf035b63b2f20a1c6f36 100644 (file)
@@ -308,7 +308,7 @@ int     attr_vscan0(VSTREAM *fp, int flags, va_list ap)
                    && strcmp(wanted_name, STR(name_buf)) == 0))
                break;
            if ((flags & ATTR_FLAG_EXTRA) != 0) {
-               msg_warn("spurious attribute %s in input from %s",
+               msg_warn("unexpected attribute %s in input from %s",
                         STR(name_buf), VSTREAM_PATH(fp));
                return (conversions);
            }
index 0c38c5ea5967f32da1630c393367fd04e6c7af59..26e7d04d2ee8365744e42036d2aac88d4cce5225 100644 (file)
@@ -327,7 +327,7 @@ int     attr_vscan64(VSTREAM *fp, int flags, va_list ap)
                    && strcmp(wanted_name, STR(name_buf)) == 0))
                break;
            if ((flags & ATTR_FLAG_EXTRA) != 0) {
-               msg_warn("spurious attribute %s in input from %s",
+               msg_warn("unexpected attribute %s in input from %s",
                         STR(name_buf), VSTREAM_PATH(fp));
                return (conversions);
            }
index 2cf8936c43ecef16325768bd4bcb79789ffa384d..60a07e92afce03b6c6e29875e348172bdde1ffe5 100644 (file)
@@ -39,6 +39,9 @@
 /* .IP \fIldapsource_\fRquery_filter
 /*     The filter used to search for directory entries, for example
 /*     \fI(mailacceptinggeneralid=%s)\fR.
+/* .IP \fIldapsource_\fRresult_filter
+/*     The filter used to expand results from queries.  Default is
+/*     \fI%s\fR.
 /* .IP \fIldapsource_\fRresult_attribute
 /*     The attribute(s) returned by the search, in which to find
 /*     RFC822 addresses, for example \fImaildrop\fR.
@@ -135,6 +138,7 @@ typedef struct {
     char   *search_base;
     MATCH_LIST *domain;
     char   *query_filter;
+    char   *result_filter;
     ARGV   *result_attributes;
     int     num_attributes;            /* rest of list is DN's. */
     int     bind;
@@ -145,15 +149,25 @@ typedef struct {
     long    cache_expiry;
     long    cache_size;
     int     dereference;
+    int     chase_referrals;
     int     debuglevel;
+    int     version;
     LDAP   *ld;
 } DICT_LDAP;
 
+#ifndef LDAP_OPT_NETWORK_TIMEOUT
 /*
  * LDAP connection timeout support.
  */
 static jmp_buf env;
 
+static void dict_ldap_timeout(int unused_sig)
+{
+    longjmp(env, 1);
+}
+
+#endif
+
 static void dict_ldap_logprint(LDAP_CONST char *data)
 {
     char   *myname = "dict_ldap_debug";
@@ -161,16 +175,61 @@ static void dict_ldap_logprint(LDAP_CONST char *data)
     msg_info("%s: %s", myname, data);
 }
 
-static void dict_ldap_timeout(int unused_sig)
+
+static int dict_ldap_get_errno(LDAP * ld)
 {
-    longjmp(env, 1);
+    int     rc;
+
+#if (LDAP_API_VERSION >= 2000)
+    if (ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &rc) != LDAP_OPT_SUCCESS)
+       rc = LDAP_OTHER;
+#else
+    rc = dict_ldap->ld->ld_errno;
+#endif
+    return rc;
+}
+
+static int dict_ldap_set_errno(LDAP * ld, int rc)
+{
+#if (LDAP_API_VERSION >= 2000)
+    (void) ldap_set_option(ld, LDAP_OPT_ERROR_NUMBER, &rc);
+#else
+    ld->ld_errno = rc;
+#endif
+    return rc;
+}
+
+/*
+ * We need a version of ldap_bind that times out, otherwise all
+ * of Postfix can get wedged during daemon initialization.
+ */
+static int dict_ldap_bind_st(DICT_LDAP *dict_ldap)
+{
+    int     msgid;
+    LDAPMessage *res;
+    struct timeval mytimeval;
+
+    if ((msgid = ldap_bind(dict_ldap->ld, dict_ldap->bind_dn,
+                          dict_ldap->bind_pw, LDAP_AUTH_SIMPLE)) == -1)
+       return (dict_ldap_get_errno(dict_ldap->ld));
+
+    mytimeval.tv_sec = dict_ldap->timeout;
+    mytimeval.tv_usec = 0;
+
+    if (ldap_result(dict_ldap->ld, msgid, 1, &mytimeval, &res) == -1)
+       return (dict_ldap_get_errno(dict_ldap->ld));
+
+    if (dict_ldap_get_errno(dict_ldap->ld) == LDAP_TIMEOUT) {
+       (void) ldap_abandon(dict_ldap->ld, msgid);
+       return (dict_ldap_set_errno(dict_ldap->ld, LDAP_TIMEOUT));
+    }
+    return (ldap_result2error(dict_ldap->ld, res, 1));
 }
 
 /* Establish a connection to the LDAP server. */
 static int dict_ldap_connect(DICT_LDAP *dict_ldap)
 {
     char   *myname = "dict_ldap_connect";
-    void    (*saved_alarm) (int);
     int     rc = 0;
 
 #ifdef LDAP_API_FEATURE_X_MEMCACHE
@@ -181,6 +240,9 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap)
 #ifdef LDAP_OPT_NETWORK_TIMEOUT
     struct timeval mytimeval;
 
+#else
+    void    (*saved_alarm) (int);
+
 #endif
 
     dict_errno = 0;
@@ -232,6 +294,21 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap)
     }
 #endif
 
+    /*
+     * v3 support is needed for referral chasing.  Thanks to Sami Haahtinen
+     * for the patch.
+     */
+#ifdef LDAP_OPT_PROTOCOL_VERSION
+    if (ldap_set_option(dict_ldap->ld, LDAP_OPT_PROTOCOL_VERSION,
+                       &dict_ldap->version) != LDAP_OPT_SUCCESS)
+       msg_warn("%s: Unable to set LDAP protocol version", myname);
+
+    if (msg_verbose)
+       msg_warn("%s: Actual Protocol version used was %d.",
+                myname, ldap_get_option(dict_ldap->ld,
+                   LDAP_OPT_PROTOCOL_VERSION, (int *) dict_ldap->version));
+#endif
+
     /*
      * Configure alias dereferencing for this connection. Thanks to Mike
      * Mattice for this, and to Hery Rakotoarisoa for the v3 update.
@@ -254,6 +331,22 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap)
        msg_warn("%s: Unable to set LDAP debug level.", myname);
 #endif
 
+    /* Chase referrals. */
+
+    /*
+     * I have no clue where this was originally added so i'm skipping all
+     * tests
+     */
+#ifdef LDAP_OPT_REFERRALS
+    if (ldap_set_option(dict_ldap->ld, LDAP_OPT_REFERRALS,
+                   dict_ldap->chase_referrals ? LDAP_OPT_ON : LDAP_OPT_OFF)
+       != LDAP_OPT_SUCCESS)
+       msg_warn("%s: Unable to set Referral chasing.", myname);
+#else
+    if (dict_ldap->chase_referrals) {
+       msg_warn("%s: Unable to set Referral chasing.", myname);
+    }
+#endif
 
     /*
      * If this server requires a bind, do so. Thanks to Sam Tardieu for
@@ -264,8 +357,7 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap)
            msg_info("%s: Binding to server %s as dn %s",
                     myname, dict_ldap->server_host, dict_ldap->bind_dn);
 
-       rc = ldap_bind_s(dict_ldap->ld, dict_ldap->bind_dn,
-                        dict_ldap->bind_pw, LDAP_AUTH_SIMPLE);
+       rc = dict_ldap_bind_st(dict_ldap);
 
        if (rc != LDAP_SUCCESS) {
            msg_warn("%s: Unable to bind to server %s as %s: %d (%s)",
@@ -331,6 +423,59 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap)
     return (0);
 }
 
+/*
+ * expand a filter (lookup or result)
+ */
+static void dict_ldap_expand_filter(char *filter, char *value, VSTRING *out)
+{
+    char   *myname = "dict_ldap_expand_filter";
+    char   *sub,
+           *end;
+
+    /*
+     * Yes, replace all instances of %s with the address to look up. Replace
+     * %u with the user portion, and %d with the domain portion.
+     */
+    sub = filter;
+    end = sub + strlen(filter);
+    while (sub < end) {
+
+       /*
+        * Make sure it's %[sud] and not something else.  For backward
+        * compatibilty, treat anything other than %u or %d as %s, with a
+        * warning.
+        */
+       if (*(sub) == '%') {
+           char   *u = value;
+           char   *p = strrchr(u, '@');
+
+           switch (*(sub + 1)) {
+           case 'd':
+               if (p)
+                   vstring_strcat(out, p + 1);
+               break;
+           case 'u':
+               if (p)
+                   vstring_strncat(out, u, p - u);
+               else
+                   vstring_strcat(out, u);
+               break;
+           default:
+               msg_warn
+                   ("%s: Invalid filter substitution format '%%%c'!",
+                    myname, *(sub + 1));
+               /* fall through */
+           case 's':
+               vstring_strcat(out, u);
+               break;
+           }
+           sub++;
+       } else
+           vstring_strncat(out, sub, 1);
+       sub++;
+    }
+}
+
 /*
  * dict_ldap_get_values: for each entry returned by a search, get the values
  * of all its attributes. Recurses to resolve any DN or URL values found.
@@ -360,6 +505,7 @@ static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage * res,
 
     for (entry = ldap_first_entry(dict_ldap->ld, res); entry != NULL;
         entry = ldap_next_entry(dict_ldap->ld, entry)) {
+       ber = NULL;
        attr = ldap_first_attribute(dict_ldap->ld, entry, &ber);
        if (attr == NULL) {
            if (msg_verbose)
@@ -393,7 +539,11 @@ static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage * res,
                for (i = 0; vals[i] != NULL; i++) {
                    if (VSTRING_LEN(result) > 0)
                        vstring_strcat(result, ",");
-                   vstring_strcat(result, vals[i]);
+                   if (dict_ldap->result_filter == NULL)
+                       vstring_strcat(result, vals[i]);
+                   else
+                       dict_ldap_expand_filter(dict_ldap->result_filter,
+                                               vals[i], result);
                }
            } else if (dict_ldap->result_attributes->argv[i]) {
                for (i = 0; vals[i] != NULL; i++) {
@@ -570,49 +720,8 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name)
                 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.
-        * Replace %u with the user portion, and %d with the domain portion.
-        */
-       sub = dict_ldap->query_filter;
-       end = sub + strlen(dict_ldap->query_filter);
-       while (sub < end) {
-
-           /*
-            * Make sure it's %[sud] and not something else.  For backward
-            * compatibilty, treat anything other than %u or %d as %s, with a
-            * warning.
-            */
-           if (*(sub) == '%') {
-               char   *u = vstring_str(escaped_name);
-               char   *p = strchr(u, '@');
-
-               switch (*(sub + 1)) {
-               case 'd':
-                   if (p)
-                       vstring_strcat(filter_buf, p + 1);
-                   break;
-               case 'u':
-                   if (p)
-                       vstring_strncat(filter_buf, u, p - u);
-                   else
-                       vstring_strcat(filter_buf, u);
-                   break;
-               default:
-                   msg_warn
-                       ("%s: Invalid lookup substitution format '%%%c'!",
-                        myname, *(sub + 1));
-                   /* fall through */
-               case 's':
-                   vstring_strcat(filter_buf, u);
-                   break;
-               }
-               sub++;
-           } else
-               vstring_strncat(filter_buf, sub, 1);
-           sub++;
-       }
+       dict_ldap_expand_filter(dict_ldap->query_filter,
+                               vstring_str(escaped_name), filter_buf);
     }
 
     /*
@@ -663,19 +772,11 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name)
         * LDAP_DECODING_ERROR; I'm ignoring that for now.
         */
 
-#if (LDAP_API_VERSION >= 2000)
-       if (ldap_get_option(dict_ldap->ld, LDAP_OPT_ERROR_NUMBER, &rc) !=
-           LDAP_OPT_SUCCESS)
-           msg_warn("%s: Unable to get last error number.", myname);
+       rc = dict_ldap_get_errno(dict_ldap->ld);
        if (rc != LDAP_SUCCESS && rc != LDAP_DECODING_ERROR)
-           msg_warn("%s: Had some trouble with entries returned by search: %s", myname, ldap_err2string(rc));
-#else
-       if (dict_ldap->ld->ld_errno != LDAP_SUCCESS &&
-           dict_ldap->ld->ld_errno != LDAP_DECODING_ERROR)
            msg_warn
                ("%s: Had some trouble with entries returned by search: %s",
-                myname, ldap_err2string(dict_ldap->ld->ld_errno));
-#endif
+                myname, ldap_err2string(rc));
 
        if (msg_verbose)
            msg_info("%s: Search returned %s", myname,
@@ -735,6 +836,8 @@ static void dict_ldap_close(DICT *dict)
     if (dict_ldap->domain)
        match_list_free(dict_ldap->domain);
     myfree(dict_ldap->query_filter);
+    if (dict_ldap->result_filter)
+       myfree(dict_ldap->result_filter);
     argv_free(dict_ldap->result_attributes);
     myfree(dict_ldap->bind_dn);
     myfree(dict_ldap->bind_pw);
@@ -865,6 +968,19 @@ 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);
 
+    vstring_sprintf(config_param, "%s_result_filter", ldapsource);
+    dict_ldap->result_filter =
+       mystrdup((char *) get_mail_conf_str(vstring_str(config_param),
+                                           "%s",
+                                           0, 0));
+    if (msg_verbose)
+       msg_info("%s: %s is %s", myname, vstring_str(config_param),
+                dict_ldap->result_filter);
+
+    if (strcmp(dict_ldap->result_filter, "%s") == 0) {
+       myfree(dict_ldap->result_filter);
+       dict_ldap->result_filter = NULL;
+    }
     vstring_sprintf(config_param, "%s_result_attribute", ldapsource);
     attr = mystrdup((char *) get_mail_conf_str(vstring_str(config_param),
                                               "maildrop", 0, 0));
@@ -953,6 +1069,24 @@ DICT   *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags)
     dict_ldap->dereference = get_mail_conf_int(vstring_str(config_param), 0, 0,
                                               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.
      */
@@ -965,6 +1099,13 @@ DICT   *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags)
        msg_info("%s: %s is %d", myname, vstring_str(config_param),
                 dict_ldap->dereference);
 
+    /* Referral chasing */
+    vstring_sprintf(config_param, "%s_chase_referrals", ldapsource);
+    dict_ldap->chase_referrals = 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->chase_referrals);
+
     /*
      * Debug level.
      */