20020508
- Bugfix: close an obscure source routing relaying loophole
- involving postfix-style virtual domains. Problem reported
- by Victor Duchovny. File: smtpd/smtpd_check.c.
+ Bugfix: close user@domain@postfix-style.virtual.domain
+ source routing relaying loophole involving postfix-style
+ virtual domains with @virtual.domain catch-all patterns.
+ Problem reported by Victor Duchovny. File: smtpd/smtpd_check.c.
Bugfix: mail_addr_map() used the "wrong" @ character in
addresses with multiple @. Victor Duchovny. File:
not even in SMTP commands. Files: global/quote_82[12]_local.c
and clients.
+20020509
+
+ Safety: don't allow an OK access rule lookup result for
+ user@domain@postfix-style.virtual.domain. Suggested by
+ Victor Duchovny, Morgan Stanley. File: smtpd/smtpd_check.c.
+
+ Bugfix: quote unquoted address localparts that need quoting.
+ Files: global/tok822_parse.c, global/quote_82[12]_local.c.
+
+ Documentation: simplified the advanced content filtering
+ example, and included a more advanced example for those
+ who want to squeeze out more performance without running
+ multiple Postfix instances. Text by Victor Duchovny, Morgan
+ Stanley. File: README_FILES/FILTER_README.
+
Open problems:
+ Low: smtpd should reply with the original recipient address,
+ or it should at least externalize it.
+
+ Low: all table lookups should consistently use internalized
+ (unquoted) or externalized (quoted) forms as lookup keys.
+ smtpd, qmgr, local, etc. use internalized forms as keys.
+ cleanup uses externalized forms.
+
Low: sendmail does not store null command-line recipients.
Low: have a configurable list of errno values for mailbox
via localhost port 10025, and that submits SMTP mail back into
Postfix via localhost port 10026.
- ...................................
- : Postfix :
- ----->smtpd \ :
- : -cleanup-\ /local---->
- ---->pickup / -queue- :
- : cleanup2/ | \smtp----->
- : ^ v :
- : | v :
- : smtpd smtp :
- : 10026 | :
- .......................|...........
- ^ |
- | v
- ....|.............
- : | 10025 :
- : filter :
- : :
- ..................
+ ..................................
+ : Postfix :
+ ----->smtpd \ /local---->
+ : -cleanup->queue- :
+ ---->pickup / ^ | \smtp----->
+ : | v :
+ : smtpd smtp :
+ : 10026 | :
+ ......................|...........
+ ^ |
+ | v
+ ....|............
+ : | 10025 :
+ : filter :
+ : :
+ .................
To enable content filtering in this manner, specify in main.cf a
new parameter:
-o content_filter=
-o local_recipient_maps=
-o myhostname=localhost.domain.name
- -o disable_dns_lookups=yes
- -o cleanup_service=cleanup2
- cleanup2 unix n - n - 0 cleanup
- -o header_checks=
- -o body_checks=
+ -o smtpd_helo_restrictions=
+ -o smtpd_client_restrictions=
+ -o smtpd_sender_restrictions=
+ -o smtpd_recipient_restrictions=permit_mynetworks,reject
+ -o mynetworks=127.0.0.0/8
This is just another SMTP server. The "-o content_filter=" requests
no content filtering for incoming mail. The server has the same
if the content filter is picky about the hostname that Postfix
sends in SMTP server replies.
-The "-o disable_dns_lookups=yes" turns off unnecessary DNS lookups
-that would only slow things down.
+The "-o smtpd_xxx_restrictions" and "-o mynetworks=127.0.0.0/8"
+turn off UCE controls that would only waste time here.
+
+Squeezing out more performance
+==============================
+
+Many refinements are possible, such as running a specially-configured
+smtp delivery agent for feeding mail into the content filter, and
+turning off address rewriting before or after content filtering.
+
+As the example below shows, things quickly become very complex,
+because a lot of main.cf like information gets listed in the
+master.cf file. This makes the system hard to understand.
+
+If you need to squeeze out more performance, it is probably simpler
+to run multiple Postfix instances, one before and one after the
+content filter. That way, each instance can have simple main.cf
+and master.cf files, and the system will be easier to understand.
+
+As before, we will set up a content filtering program that receives
+SMTP mail via localhost port 10025, and that submits SMTP mail back
+into Postfix via localhost port 10026.
+
+ ...................................
+ : Postfix :
+ ----->smtpd \ :
+ : -cleanup-\ /local---->
+ ---->pickup / -queue- :
+ : cleanup2/ | \smtp----->
+ : ^ v :
+ : | v :
+ : smtpd smtp :
+ : 10026 | :
+ .......................|...........
+ ^ |
+ | v
+ ....|.............
+ : | 10025 :
+ : filter :
+ : :
+ ..................
+
+To enable content filtering in this manner, specify in main.cf a
+new parameter:
+
+/etc/postfix/main.cf:
+ content_filter = scan:localhost:10025
+
+/etc/postfix/master.cf:
+#
+# This is the usual input "smtpd" already present in master.cf
+#
+smtp inet n - n - - smtpd
+#
+# This is the cleanup daemon that handles messages in front of
+# the content filter, it does header_checks and body_checks (if
+# any), but does not do any address rewriting.
+#
+# The address rewriting happens in the second cleanup phase after
+# the content filter. This gives the content_filter access to
+# *largely* unmodified addresses for maximum flexibility.
+#
+# The trivial-rewrite daemon handles the logic of append_myorigin
+# and append_dot_mydomain, turning these off requires two
+# trivial-rewrite services, by which point (if you are not
+# already) you are much better off with two complete Postfix
+# instances one in front of and one behind the content filter.
+#
+# Note that some sites may specifically want to do the opposite:
+# perform rewrites in front of the content_filter which would
+# then see only cleaned up addresses, in this case the parameter
+# settings below should be moved to the second "cleanup"
+# instance.
+#
+cleanup unix n - n - 0 cleanup
+ -o canonical_maps=
+ -o sender_canonical_maps=
+ -o recipient_canonical_maps=
+ -o masquerade_domains=
+ -o virtual_maps=
+#
+# This is the delivery agent that injects mail into the content
+# filter. It is tuned for low latency and low concurrency, most
+# content filters burn CPU and high concurrency causes thrashing.
+# The process limit of 10 reenforces the effect of
+# $default_destination_concurrency_limit, even without an
+# explicit process limit, the concurrency is bounded because all
+# messages heading into the content filter have the same
+# destination. The "disable_dns_lookups" setting prevents the
+# delivery agent from consuming precious "bandwidth" in the
+# narrow deliver channel waiting for slow DNS responses. It also
+# ensures that the original envelope recipient is seen by the
+# content filter.
+#
+scan unix n - n - 10 smtp
+ -o disable_dns_lookups=yes
+#
+# This is the SMTP listener that receives filtered messages from
+# the content filter. It *MUST* clear the content_filter
+# parameter to avoid loops, and use a different hostname to avoid
+# triggering the Postfix SMTP loop detection code.
+#
+#
+# Since all recipients have been validated by the first "smtpd",
+# clear local_recipient_maps, virtual_maps and
+# virtual_mailbox_maps.
+#
+# This "smtpd" uses a separate cleanup that does no header or
+# body checks, but does do the various address rewrites disabled
+# in the first cleanup.
+#
+# The parameters from mynetworks onward disable all access
+# control other than insisting on connections from one of the IP
+# addresses of the host. This is typically overkill, but can
+# reduce resource usage, if the default restrictions use lots of
+# tables.
+#
+localhost:10026 inet n - n - - smtpd
+ -o content_filter=
+ -o myhostname=localhost.domain.name
+ -o local_recipient_maps=
+ -o virtual_maps=
+ -o virtual_mailbox_maps=
+ -o cleanup_service=cleanup2
+ -o mynetworks=127.0.0.0/8
+ -o mynetworks_style=host
+ -o smtpd_restriction_classes=
+ -o smtpd_client_restrictions=
+ -o smtpd_helo_restrictions=
+ -o smtpd_sender_restrictions=
+ -o smtpd_recipient_restrictions=permit_mynetworks,reject
+#
+# This is the second cleanup daemon. No header or body checks.
+# If preferable, enable rewrites in the first cleanup daemon, and
+# disable them here.
+#
+cleanup2 unix n - n - 0 cleanup
+ -o header_checks=
+ -o body_checks=
+#
+# The normal "smtp" delivery agent for contrast with "scan".
+# Definitely do not set "disable_dns_lookups = yes" here if you
+# send mail to the Internet.
+#
+smtp unix n - n - - smtp
+
+This causes Postfix to add one extra content filtering record to
+each incoming mail message, with content scan:localhost:10025.
+You can use the same syntax as in the right-hand side of a Postfix
+transport table. The content filtering records are added by the
+smtpd and pickup servers.
-The "-o cleanup_service=cleanup2" specifies that mail received via
-the secondary SMTP server should be send into the cleanup2 service.
+The "scan" transport is a dedicated instance of the "smtp" delivery
+agent for injecting messages into the SMTP content filter. Using
+a dedicated "smtp" transport allows one to tune it for the specific
+task of delivering mail to a local content filter (low latency,
+low concurrency, throughput dependent on predictably low latency).
-As the master.cf entry above shows, the cleanup2 service is like
-the normal cleanup server, but with some features turned off: no
-processing of header patterns, and no processing of body message
-patterns. This can save you some precious CPU cycles.
+See the previous example for setting up the content filter with
+the Postfix spawn service; you can of course use any server that
+can be run stand-alone outside the Postfix environment.
date. Snapshots change only the release date, unless they include
the same bugfixes as a patch release.
+Incompatible changes with Postfix snapshot 1.1.9-20020509
+=========================================================
+
+The Postfix SMTP server no longer honors OK access rules for
+user@domain@postfix-style.virtual.domain, to close a relaying
+loophole with postfix-style virtual domains that have @domain.name
+catch-all patterns.
+
+The appearance of user@domain1@domain2 addresses has changed. In
+mail headers, such addresses are now properly quoted as
+"user@domain1"@domain2. This quoted form is now also expected on
+the left-hand side of virtual and canonical lookup tables, but only
+by some of the Postfix components. For now, it is better not to
+use user@domain1@domain2 address forms on the left-hand side of
+lookup tables.
+
+Incompatible changes with Postfix snapshot 1.1.8-20020508
+=========================================================
+
+The Postfix SMTP server by default no longer accepts mail for
+user@domain@postfix-style.virtual.domain, to close a relaying
+loophole with postfix-style virtual domains that have @domain.name
+catch-all patterns.
+
Incompatible changes with Postfix snapshot 1.1.8-20020505
=========================================================
<html> <head> </head> <body> <pre>
-
CLEANUP(8) CLEANUP(8)
<b>NAME</b>
P.O. Box 704
Yorktown Heights, NY 10598, USA
- 1
-
+ CLEANUP(8)
</pre> </body> </html>
/* char *addr;
/* DESCRIPTION
/* This module implements one-to-many table mapping via table lookup.
+/* Table lookups are done with quoted (externalized) address forms.
/* The process is recursive. The recursion terminates when the
/* left-hand side appears in its own expansion, or when a maximal
/* nesting level is reached.
tok822_parse.o: ../../include/vstring.h
tok822_parse.o: ../../include/vbuf.h
tok822_parse.o: ../../include/msg.h
+tok822_parse.o: quote_822_local.h
+tok822_parse.o: quote_flags.h
tok822_parse.o: tok822.h
tok822_parse.o: resolve_clnt.h
tok822_resolve.o: tok822_resolve.c
#include <msg.h>
#include <stdlib.h>
+#include <unistd.h>
#include <vstream.h>
#include <msg_vstream.h>
msg_fatal("usage: %s [-v] patterns hostname", progname);
}
-main(int argc, char **argv)
+int main(int argc, char **argv)
{
DOMAIN_LIST *list;
char *host;
#include <msg_vstream.h>
#include <mail_conf.h>
-main(int argc, char **argv)
+int main(int argc, char **argv)
{
VSTRING *why = vstring_alloc(100);
#include <vstream.h>
-main(void)
+int main(void)
{
vstream_printf("%s\n", mail_date(time((time_t *) 0)));
vstream_fflush(VSTREAM_OUT);
* 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 "20020508"
+#define MAIL_RELEASE_DATE "20020509"
#define VAR_MAIL_VERSION "mail_version"
-#define DEF_MAIL_VERSION "1.1.8-" MAIL_RELEASE_DATE
+#define DEF_MAIL_VERSION "1.1.9-" MAIL_RELEASE_DATE
extern char *var_mail_version;
/*
#include <vstream.h>
#include <vstring_vstream.h>
-main(int argc, char **argv)
+int main(int argc, char **argv)
{
VSTRING *buf = vstring_alloc(100);
MAPS *maps;
#include <msg.h>
#include <stdlib.h>
+#include <unistd.h>
#include <vstream.h>
#include <msg_vstream.h>
msg_fatal("usage: %s [-v] pattern_list hostname address", progname);
}
-main(int argc, char **argv)
+int main(int argc, char **argv)
{
NAMADR_LIST *list;
char *host;
/* In violation with RFCs, treat 8-bit text as ordinary text.
/* .IP QUOTE_FLAG_EXPOSE_AT
/* In violation with RFCs, treat `@' as an ordinary character.
+/* .IP QUOTE_FLAG_APPEND
+/* Append to the result buffer, instead of overwriting it.
/* .RE
/* STANDARDS
/* RFC 821 (SMTP protocol)
/* make_821_quoted_string - make quoted-string from local-part */
-static VSTRING *make_821_quoted_string(VSTRING *dst, char *local_part, char *end)
+static VSTRING *make_821_quoted_string(VSTRING *dst, char *local_part,
+ char *end, int flags)
{
char *cp;
int ch;
* Put quotes around the result, and prepend a backslash to characters
* that need quoting when they occur in a quoted-string.
*/
- VSTRING_RESET(dst);
VSTRING_ADDCH(dst, '"');
for (cp = local_part; cp < end && (ch = *cp) != 0; cp++) {
- if (ch > 127 || ch == '\r' || ch == '\n' || ch == '"' || ch == '\\')
+ if ((ch > 127 && !(flags & QUOTE_FLAG_8BITCLEAN))
+ || ch == '\r' || ch == '\n' || ch == '"' || ch == '\\')
VSTRING_ADDCH(dst, '\\');
VSTRING_ADDCH(dst, ch);
}
*/
if ((at = strrchr(addr, '@')) == 0) /* just in case */
at = addr + strlen(addr); /* should not happen */
+ if ((flags & QUOTE_FLAG_APPEND) == 0)
+ VSTRING_RESET(dst);
if (is_821_dot_string(addr, at, flags)) {
- return (vstring_strcpy(dst, addr));
+ return (vstring_strcat(dst, addr));
} else {
- make_821_quoted_string(dst, addr, at);
+ make_821_quoted_string(dst, addr, at, flags & QUOTE_FLAG_8BITCLEAN);
return (vstring_strcat(dst, at));
}
}
#include <vstring_vstream.h>
#include "quote_821_local.h"
-main(void)
+int main(void)
{
VSTRING *src = vstring_alloc(100);
VSTRING *dst = vstring_alloc(100);
while (vstring_fgets_nonl(src, VSTREAM_IN)) {
vstream_fprintf(VSTREAM_OUT, "%s\n",
- vstring_str(quote_821_local(dst, vstring_str(src))));
+ vstring_str(quote_821_local(dst, vstring_str(src),
+ QUOTE_FLAG_8BITCLEAN)));
vstream_fflush(VSTREAM_OUT);
}
exit(0);
/* In violation with RFCs, treat 8-bit text as ordinary text.
/* .IP QUOTE_FLAG_EXPOSE_AT
/* In violation with RFCs, treat `@' as an ordinary character.
+/* .IP QUOTE_FLAG_APPEND
+/* Append to the result buffer, instead of overwriting it.
/* .RE
/* STANDARDS
/* RFC 822 (ARPA Internet Text Messages)
/* make_822_quoted_string - make quoted-string from local-part */
static VSTRING *make_822_quoted_string(VSTRING *dst, const char *local_part,
- const char *end)
+ const char *end, int flags)
{
const char *cp;
int ch;
*/
VSTRING_ADDCH(dst, '"');
for (cp = local_part; cp < end && (ch = *cp) != 0; cp++) {
- if ( /* ch > 127 || */ ch == '"' || ch == '\\' || ch == '\r')
+ if ((ch > 127 && !(flags & QUOTE_FLAG_8BITCLEAN))
+ || ch == '"' || ch == '\\' || ch == '\r')
VSTRING_ADDCH(dst, '\\');
VSTRING_ADDCH(dst, ch);
}
start = mbox;
if ((end = strrchr(start, '@')) == 0)
end = start + strlen(start);
+ if ((flags & QUOTE_FLAG_APPEND) == 0)
+ VSTRING_RESET(dst);
if (is_822_dot_string(start, end, flags)) {
- return (vstring_strcpy(dst, mbox));
+ return (vstring_strcat(dst, mbox));
} else {
- vstring_strncpy(dst, mbox, start - mbox);
- make_822_quoted_string(dst, start, end);
+ vstring_strncat(dst, mbox, start - mbox);
+ make_822_quoted_string(dst, start, end, flags & QUOTE_FLAG_8BITCLEAN);
return (vstring_strcat(dst, end));
}
}
VSTRING *unquoted = vstring_alloc(100);
while (vstring_fgets_nonl(raw, VSTREAM_IN)) {
- quote_822_local(quoted, STR(raw));
+ quote_822_local(quoted, STR(raw), QUOTE_FLAG_8BITCLEAN);
vstream_printf("quoted: %s\n", STR(quoted));
unquote_822_local(unquoted, STR(quoted));
vstream_printf("unquoted: %s\n", STR(unquoted));
*/
#define QUOTE_FLAG_8BITCLEAN (1<<0) /* be 8-bit clean */
#define QUOTE_FLAG_EXPOSE_AT (1<<1) /* @ is ordinary text */
+#define QUOTE_FLAG_APPEND (1<<2) /* append, not overwrite */
/* LICENSE
/* .ad
#include <record.h>
#include <rec_streamlf.h>
-main(void)
+int main(void)
{
VSTRING *buf = vstring_alloc(100);
int type;
vstream_fflush(VSTREAM_OUT);
}
-main(int argc, char **argv)
+int main(int argc, char **argv)
{
RESOLVE_REPLY reply;
int ch;
vstream_fflush(VSTREAM_OUT);
}
-main(int argc, char **argv)
+int main(int argc, char **argv)
{
VSTRING *reply;
int ch;
#include <record.h>
#include <rec_streamlf.h>
-main(void)
+int main(void)
{
VSTRING *buf = vstring_alloc(150);
int type;
#include <msg.h>
#include <stdlib.h>
+#include <unistd.h>
#include <vstream.h>
#include <vstring.h>
#include <msg_vstream.h>
msg_fatal("usage: %s [-v] patterns string", progname);
}
-main(int argc, char **argv)
+int main(int argc, char **argv)
{
STRING_LIST *list;
char *string;
/* Global library. */
+#include "quote_822_local.h"
#include "tok822.h"
/*
VSTRING *tok822_externalize(VSTRING *vp, TOK822 *tree, int flags)
{
+ VSTRING *tmp;
TOK822 *tp;
if (flags & TOK822_STR_WIPE)
continue;
}
break;
+
+ /*
+ * XXX In order to correctly externalize an address, it is not
+ * sufficient to quote individual atoms. There are higher-level
+ * rules that say when an address localpart needs to be quoted.
+ * We wing it with the quote_822_local() routine, which ignores
+ * the issue of atoms in the domain part that would need quoting.
+ */
case TOK822_ADDR:
- tok822_externalize(vp, tp->head, TOK822_STR_NONE);
+ 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);
+ vstring_free(tmp);
break;
case TOK822_ATOM:
case TOK822_COMMENT:
named group: foo@bar,
baz@barf;
->>>wietse@foo (wietse
- venema)<<<
+>>>wietse@foo (wietse venema)<<<
Parse tree:
address
atom "wietse"
OP "@"
atom "foo"
- comment "(wietse
- venema)"
+ comment "(wietse venema)"
Internalized:
-wietse@foo (wietse
- venema)
+wietse@foo (wietse venema)
Externalized, no newlines inserted:
-wietse@foo (wietse
- venema)
+wietse@foo (wietse venema)
Externalized, newlines inserted:
-wietse@foo (wietse
- venema)
+wietse@foo (wietse venema)
"\tid %s; %s", state->queue_id, mail_date(state->time));
}
#ifdef RECEIVED_ENVELOPE_FROM
- quote_822_local(state->buf, state->sender);
+ quote_822_local(state->buf, state->sender, QUOTE_FLAG_8BITCLEAN);
rec_fprintf(state->cleanup, REC_TYPE_NORM,
"\t(envelope-from <%s>)", STR(state->buf));
#endif
#include <tok822.h>
#include <verp_sender.h>
#include <string_list.h>
+#include <quote_822_local.h>
/* Single-threaded server skeleton. */
"\tby %s (%s) with %s id %s",
var_myhostname, var_mail_name,
state->protocol, state->queue_id);
- /* XXX Should RFC 822 externalize recipient address */
+ quote_822_local(state->buffer, state->recipient, QUOTE_FLAG_8BITCLEAN);
rec_fprintf(state->cleanup, REC_TYPE_NORM,
- "\tfor <%s>; %s", state->recipient, mail_date(state->time));
+ "\tfor <%s>; %s", STR(state->buffer), mail_date(state->time));
} else {
rec_fprintf(state->cleanup, REC_TYPE_NORM,
"\tby %s (%s) with %s",
"\tid %s; %s", state->queue_id, mail_date(state->time));
}
#ifdef RECEIVED_ENVELOPE_FROM
- /* XXX Should RFC 822 externalize sender address */
+ quote_822_local(state->buffer, state->sender, QUOTE_FLAG_8BITCLEAN);
rec_fprintf(state->cleanup, REC_TYPE_NORM,
- "\t(envelope-from %s)", state->sender);
+ "\t(envelope-from %s)", STR(state->buffer));
#endif
smtpd_chat_reply(state, "354 End data with <CR><LF>.<CR><LF>");
{ if (bare_addr) myfree(bare_addr); return(x); }
/*
- * Source-routed, non-local, recipient addresses are too suspicious for
- * returning an "OK" result. The complicated expression below was brought
- * to you by the keyboard of Victor Duchovni, Morgan Stanley and hacked
- * up a bit by Wietse.
+ * Source-routed (non-local or virtual) recipient addresses are too
+ * suspicious for returning an "OK" result. The complicated expression
+ * 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) \
(var_allow_untrust_route == 0 \
&& (reply->flags & RESOLVE_FLAG_ROUTED) \
- && strcmp(reply_class, SMTPD_NAME_RECIPIENT) == 0 \
- && !resolve_final(state, reply_name, domain))
+ && strcmp(reply_class, SMTPD_NAME_RECIPIENT) == 0)
/*
* Look up user+foo@domain if the address has an extension, user@domain