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
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
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.
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.
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
=========================================================
#
#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.
#
#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".
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>.
<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>
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>
<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>
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
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);
"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.
* 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);
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++) {
{
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));
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);
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);
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) {
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);
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 ?
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);
}
#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;
#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
* 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
/* 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
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;
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);
/*
* 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
/* 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;
/* 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.
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 */
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));
/*
* 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
* 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);
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:
*/
#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); \
* 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);
* 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);
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);
"\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 {
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 ?
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)
*/
#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); \
#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); \
} \
"\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 {
"\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
/* 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. */
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))
* 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;
* 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)
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);
/*
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);
/*
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);
/*
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);
/*
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);
}
* 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.
/* 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
bool var_append_at_myorigin;
bool var_percent_hack;
char *var_local_transport;
+int var_resolve_dequoted;
/* rewrite_service - read request and send reply */
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,
};
&& 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);
}
&& 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);
}
/* .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.
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;
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";
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
#ifdef LDAP_OPT_NETWORK_TIMEOUT
struct timeval mytimeval;
+#else
+ void (*saved_alarm) (int);
+
#endif
dict_errno = 0;
}
#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.
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
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)",
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.
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)
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++) {
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);
}
/*
* 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,
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);
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));
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.
*/
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.
*/