explicit "multiple sessions per delivery request" model.
This uncovered ESMTP and SASL missing re-initialization
problems that were fixed in past week. Design by Victor
- and Wietse, initial implementation by Victor Duchovny.
+ and Wietse, initial implementation by Victor Duchovni.
20040620
Future proofing: after the reorganization of SMTP request
state and session state, added code to the smtp client
error handling routines to more consistently deal with the
- possibility that session information is not be available.
+ possibility that session information is not available.
20040621
Support for external command execution directory. Files:
global/pipe_command.[hc].
+20040622
+
+ Safety: when mail is delivered to a transport with per-delivery
+ recipient limit of 1, split the recipient address on the
+ recipient delimiter if one is defined, so that extended
+ addresses don't get extra delivery concurrency slots.
+ Files: *qmgr/qmgr_message.c.
+
+20040623
+
+ Workaround for fragile clients: add microsecond time to
+ maildir filename. Files: virtual/maildir.c, local/maildir.c.
+
Open problems:
Low: make sure CCARGS -I options come at the end.
Received: from porcupine.org ...
-Then I know that this is almost certainly forged mail. Mail that is really sent
-by my systems looks like this:
+Then I know that this is almost certainly forged mail (almost; see next section
+for the fly in the ointment). Mail that is really sent by my systems looks like
+this:
Received: from hostname.porcupine.org ...
Received: from host.example.com (HELO porcupine.org) ...
Received: from host.example.com (EHLO porcupine.org) ...
+Another frequent sign of forgery is the Message-ID: header. My systems produce
+a Message-ID: of <stuff@hostname.porcupine.org>. The following are forgeries,
+especially the first one:
+
+ Message-ID: <1cb479435d8eb9.2beb1.qmail@porcupine.org>
+ Message-ID: <yulszqocfzsficvzzju@porcupine.org>
+
To block such backscatter I use header_checks and body_checks patterns like
this:
/^Received: +from +[^ ]+ +\(([^ ]+ +[he]+lo=|[he]+lo +)
(porcupine\.org)\)/
reject forged client name in Received: header: $2
+ /^Message-ID:.*@(porcupine\.org)/
+ reject forged domain name in Message-ID: header: $1
/etc/postfix/body_checks:
/^[> ]*Received: +from +(porcupine\.org) /
/^[> ]*Received: +from +[^ ]+ +\(([^ ]+ +[he]+lo=|[he]+lo +)
(porcupine\.org)\)/
reject forged client name in Received: header: $2
+ /^[> ]*Message-ID:.*@(porcupine\.org)/
+ reject forged domain name in Message-ID: header: $1
Notes:
/etc/postfix/header_checks:
/^(From|Return-Path):.*[[:<:]](user@domain\.tld)[[:>:]]/
- reject forged sender address in $1: message header: $2
+ reject forged sender address in $1: header: $2
/etc/postfix/body_checks:
/^[> ]*(From|Return-Path):.*[[:<:]](user@domain\.tld)[[:>:]]/
- reject forged sender address in $1: message header: $2
+ reject forged sender address in $1: header: $2
Notes:
</pre>
</blockquote>
-<p> Then I know that this is almost certainly forged mail. Mail
-that is really sent by my systems looks like this: </p>
+<p> Then I know that this is almost certainly forged mail (almost;
+see next section for the fly in the ointment). Mail that is really
+sent by my systems looks like this: </p>
<blockquote>
<pre>
</pre>
</blockquote>
+<p> Another frequent sign of forgery is the Message-ID: header. My
+systems produce a Message-ID: of
+<<i>stuff</i>@<i>hostname</i>.porcupine.org>. The following
+are forgeries, especially the first one:
+
+<blockquote>
+<pre>
+Message-ID: <1cb479435d8eb9.2beb1.qmail@porcupine.org>
+Message-ID: <yulszqocfzsficvzzju@porcupine.org>
+</pre>
+</blockquote>
+
<p> To block such backscatter I use <a href="postconf.5.html#header_checks">header_checks</a> and <a href="postconf.5.html#body_checks">body_checks</a>
patterns like this: </p>
reject forged client name in Received: header: $1
/^Received: +from +[^ ]+ +\(([^ ]+ +[he]+lo=|[he]+lo +)(porcupine\.org)\)/
reject forged client name in Received: header: $2
+ /^Message-ID:.*@(porcupine\.org)/
+ reject forged domain name in Message-ID: header: $1
/etc/postfix/body_checks:
/^[> ]*Received: +from +(porcupine\.org) /
reject forged client name in Received: header: $1
/^[> ]*Received: +from +[^ ]+ +\(([^ ]+ +[he]+lo=|[he]+lo +)(porcupine\.org)\)/
reject forged client name in Received: header: $2
+ /^[> ]*Message-ID:.*@(porcupine\.org)/
+ reject forged domain name in Message-ID: header: $1
</pre>
</blockquote>
/etc/postfix/header_checks:
/^(From|Return-Path):.*[[:<:]](user@domain\.tld)[[:>:]]/
- reject forged sender address in $1: message header: $2
+ reject forged sender address in $1: header: $2
/etc/postfix/body_checks:
/^[> ]*(From|Return-Path):.*[[:<:]](user@domain\.tld)[[:>:]]/
- reject forged sender address in $1: message header: $2
+ reject forged sender address in $1: header: $2
</pre>
</blockquote>
<b>$user</b> (recipient username), <b>$home</b> (recipient home direc-
tory), <b>$shell</b> (recipient shell), <b>$recipient</b> (complete
recipient address), <b>$extension</b> (recipient address exten-
- sion), <b>$domain</b> (recipient domain), <b>local</b> (entire recipient
- address localpart) and <b>$<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a>.</b> The forms
+ sion), <b>$domain</b> (recipient domain), <b>$local</b> (entire recipi-
+ ent address localpart) and <b>$<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a>.</b> The forms
<i>${name?value}</i> and <i>${name:value}</i> expand conditionally to
<i>value</i> when <i>$name</i> is (is not) defined. Characters that may
have special meaning to the shell or file system are
(recipient home directory), <b>$shell</b> (recipient shell),
<b>$recipient</b> (complete recipient address), <b>$extension</b>
(recipient address extension), <b>$domain</b> (recipient domain),
- <b>local</b> (entire recipient address localpart) and <b>$recipi-</b>
+ <b>$local</b> (entire recipient address localpart) and <b>$recipi-</b>
<b>ent_delimiter.</b> The forms <i>${name?value}</i> and <i>${name:value}</i>
expand conditionally to <i>value</i> when <i>$name</i> is (is not)
defined. Characters that may have special meaning to the
the sender.
</p>
+<p> NOTE: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+for mail that Postfix forwards internally, nor for mail that Postfix
+generates itself. </p>
+
</DD>
the sender.
</p>
+<p> NOTE: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+for mail that Postfix forwards internally, nor for mail that Postfix
+generates itself. </p>
+
<p>
Example:
</p>
the sender.
</p>
+<p> NOTE: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+for mail that Postfix forwards internally, nor for mail that Postfix
+generates itself. </p>
+
<p>
Example:
</p>
.PP
NOTE: if mail to the BCC address bounces it will be returned to
the sender.
+.PP
+NOTE: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+for mail that Postfix forwards internally, nor for mail that Postfix
+generates itself.
.SH anvil_rate_time_unit (default: 60s)
The time unit over which client connection rates and other rates
are calculated.
NOTE: if mail to the BCC address bounces it will be returned to
the sender.
.PP
+NOTE: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+for mail that Postfix forwards internally, nor for mail that Postfix
+generates itself.
+.PP
Example:
.PP
.nf
NOTE: if mail to the BCC address bounces it will be returned to
the sender.
.PP
+NOTE: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+for mail that Postfix forwards internally, nor for mail that Postfix
+generates itself.
+.PP
Example:
.PP
.nf
\fB$user\fR (recipient username), \fB$home\fR (recipient home
directory), \fB$shell\fR (recipient shell), \fB$recipient\fR
(complete recipient address), \fB$extension\fR (recipient address
-extension), \fB$domain\fR (recipient domain), \fBlocal\fR
+extension), \fB$domain\fR (recipient domain), \fB$local\fR
(entire recipient address localpart) and
\fB$recipient_delimiter.\fR The forms \fI${name?value}\fR and
\fI${name:value}\fR expand conditionally to \fIvalue\fR when
\fB$home\fR (recipient home directory), \fB$shell\fR
(recipient shell), \fB$recipient\fR (complete recipient
address), \fB$extension\fR (recipient address extension),
-\fB$domain\fR (recipient domain), \fBlocal\fR (entire
+\fB$domain\fR (recipient domain), \fB$local\fR (entire
recipient address localpart) and \fB$recipient_delimiter.\fR
The forms \fI${name?value}\fR and \fI${name:value}\fR expand
conditionally to \fIvalue\fR when \fI$name\fR is (is not)
</pre>
</blockquote>
-<p> Then I know that this is almost certainly forged mail. Mail
-that is really sent by my systems looks like this: </p>
+<p> Then I know that this is almost certainly forged mail (almost;
+see next section for the fly in the ointment). Mail that is really
+sent by my systems looks like this: </p>
<blockquote>
<pre>
</pre>
</blockquote>
+<p> Another frequent sign of forgery is the Message-ID: header. My
+systems produce a Message-ID: of
+<<i>stuff</i>@<i>hostname</i>.porcupine.org>. The following
+are forgeries, especially the first one:
+
+<blockquote>
+<pre>
+Message-ID: <1cb479435d8eb9.2beb1.qmail@porcupine.org>
+Message-ID: <yulszqocfzsficvzzju@porcupine.org>
+</pre>
+</blockquote>
+
<p> To block such backscatter I use header_checks and body_checks
patterns like this: </p>
reject forged client name in Received: header: $1
/^Received: +from +[^ ]+ +\(([^ ]+ +[he]+lo=|[he]+lo +)(porcupine\.org)\)/
reject forged client name in Received: header: $2
+ /^Message-ID:.*@(porcupine\.org)/
+ reject forged domain name in Message-ID: header: $1
/etc/postfix/body_checks:
/^[> ]*Received: +from +(porcupine\.org) /
reject forged client name in Received: header: $1
/^[> ]*Received: +from +[^ ]+ +\(([^ ]+ +[he]+lo=|[he]+lo +)(porcupine\.org)\)/
reject forged client name in Received: header: $2
+ /^[> ]*Message-ID:.*@(porcupine\.org)/
+ reject forged domain name in Message-ID: header: $1
</pre>
</blockquote>
/etc/postfix/header_checks:
/^(From|Return-Path):.*[[:<:]](user@domain\.tld)[[:>:]]/
- reject forged sender address in $1: message header: $2
+ reject forged sender address in $1: header: $2
/etc/postfix/body_checks:
/^[> ]*(From|Return-Path):.*[[:<:]](user@domain\.tld)[[:>:]]/
- reject forged sender address in $1: message header: $2
+ reject forged sender address in $1: header: $2
</pre>
</blockquote>
the sender.
</p>
+<p> NOTE: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+for mail that Postfix forwards internally, nor for mail that Postfix
+generates itself. </p>
+
%PARAM berkeley_db_create_buffer_size 16777216
<p>
the sender.
</p>
+<p> NOTE: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+for mail that Postfix forwards internally, nor for mail that Postfix
+generates itself. </p>
+
<p>
Example:
</p>
the sender.
</p>
+<p> NOTE: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+for mail that Postfix forwards internally, nor for mail that Postfix
+generates itself. </p>
+
<p>
Example:
</p>
* Patches change the patchlevel and the release date. Snapshots change the
* release date only.
*/
-#define MAIL_RELEASE_DATE "20040621"
+#define MAIL_RELEASE_DATE "20040628"
#define MAIL_VERSION_NUMBER "2.2"
#define VAR_MAIL_VERSION "mail_version"
/* \fB$user\fR (recipient username), \fB$home\fR (recipient home
/* directory), \fB$shell\fR (recipient shell), \fB$recipient\fR
/* (complete recipient address), \fB$extension\fR (recipient address
-/* extension), \fB$domain\fR (recipient domain), \fBlocal\fR
+/* extension), \fB$domain\fR (recipient domain), \fB$local\fR
/* (entire recipient address localpart) and
/* \fB$recipient_delimiter.\fR The forms \fI${name?value}\fR and
/* \fI${name:value}\fR expand conditionally to \fIvalue\fR when
/* \fB$home\fR (recipient home directory), \fB$shell\fR
/* (recipient shell), \fB$recipient\fR (complete recipient
/* address), \fB$extension\fR (recipient address extension),
-/* \fB$domain\fR (recipient domain), \fBlocal\fR (entire
+/* \fB$domain\fR (recipient domain), \fB$local\fR (entire
/* recipient address localpart) and \fB$recipient_delimiter.\fR
/* The forms \fI${name?value}\fR and \fI${name:value}\fR expand
/* conditionally to \fIvalue\fR when \fI$name\fR is (is not)
#include "sys_defs.h"
#include <sys/stat.h>
+#include <sys/time.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
int deliver_status;
int copy_flags;
struct stat st;
- time_t starttime = time((time_t *) 0);
+ struct timeval starttime;
+
+ GETTIMEOFDAY(&starttime);
/*
* Make verbose logging easier to understand.
* filesystem: a maildir has to be within a single UNIX device for link()
* and rename() to work.)
*
- * [...]
+ * Mn, where n is (in decimal) the microsecond counter from the same
+ * gettimeofday() used for the left part of the unique name.
*
* Pn, where n is (in decimal) the process ID.
*
set_eugid(usr_attr.uid, usr_attr.gid);
vstring_sprintf(buf, "%lu.P%d.%s",
- (unsigned long) starttime, var_pid, get_hostname());
+ (unsigned long) starttime.tv_sec, var_pid, get_hostname());
tmpfile = concatenate(tmpdir, STR(buf), (char *) 0);
newfile = 0;
if ((dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0
} else if (fstat(vstream_fileno(dst), &st) < 0) {
vstring_sprintf(why, "create %s: %m", tmpfile);
} else {
- vstring_sprintf(buf, "%lu.V%lxI%lx.%s",
- (unsigned long) starttime, (unsigned long) st.st_dev,
- (unsigned long) st.st_ino, get_hostname());
+ vstring_sprintf(buf, "%lu.V%lxI%lxM%lu.%s",
+ (unsigned long) starttime.tv_sec,
+ (unsigned long) st.st_dev,
+ (unsigned long) st.st_ino,
+ (unsigned long) starttime.tv_usec,
+ get_hostname());
newfile = concatenate(newdir, STR(buf), (char *) 0);
if ((mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr),
dst, copy_flags, "\n", why)) == 0) {
* agent resources. We use recipient@nexthop as queue name rather
* than the actual recipient domain name, so that one recipient in
* multiple equivalent domains cannot evade the per-recipient
- * concurrency limit. XXX Should split the address on the recipient
- * delimiter if one is defined, but doing a proper job requires
- * knowledge of local aliases. Yuck! I don't want to duplicate
- * delivery-agent specific knowledge in the queue manager.
+ * concurrency limit. Split the address on the recipient delimiter if
+ * one is defined, so that extended addresses don't get extra
+ * delivery slots.
*
* Fold the result to lower case so that we don't have multiple queues
* for the same name.
* Important! All recipients in a queue must have the same nexthop
* value. It is OK to have multiple queues with the same nexthop
* value, but only when those queues are named after recipients.
+ *
+ * The single-recipient code below was written for local(8) like
+ * delivery agents, and assumes that all domains that deliver to the
+ * same (transport + nexthop) are aliases for $nexthop. Delivery
+ * concurrency is changed from per-domain into per-recipient, by
+ * changing the queue name from nexthop into localpart@nexthop.
+ *
+ * XXX This assumption is incorrect when different destinations share
+ * the same (transport + nexthop). In reality, such transports are
+ * rarely configured to use single-recipient deliveries. The fix is
+ * to decouple the per-destination recipient limit from the
+ * per-destination concurrency.
*/
vstring_strcpy(queue_name, STR(reply.nexthop));
if (strcmp(transport->name, MAIL_SERVICE_ERROR) != 0
&& transport->recipient_limit == 1) {
+ /* Copy the recipient localpart. */
at = strrchr(STR(reply.recipient), '@');
len = (at ? (at - STR(reply.recipient))
: strlen(STR(reply.recipient)));
- VSTRING_SPACE(queue_name, len + 2);
- memmove(STR(queue_name) + len + 1, STR(queue_name),
- LEN(queue_name) + 1);
- memcpy(STR(queue_name), STR(reply.recipient), len);
- STR(queue_name)[len] = '@';
+ vstring_strncpy(queue_name, STR(reply.recipient), len);
+ /* Remove the address extension from the recipient localpart. */
+ if (*var_rcpt_delim && split_addr(STR(queue_name), *var_rcpt_delim))
+ vstring_truncate(queue_name, strlen(STR(queue_name)));
+ /* Assume the recipient domain is equivalent to nexthop. */
+ vstring_sprintf_append(queue_name, "@%s", STR(reply.nexthop));
}
lowercase(STR(queue_name));
* agent resources. We use recipient@nexthop as queue name rather
* than the actual recipient domain name, so that one recipient in
* multiple equivalent domains cannot evade the per-recipient
- * concurrency limit. XXX Should split the address on the recipient
- * delimiter if one is defined, but doing a proper job requires
- * knowledge of local aliases. Yuck! I don't want to duplicate
- * delivery-agent specific knowledge in the queue manager.
+ * concurrency limit. Split the address on the recipient delimiter if
+ * one is defined, so that extended addresses don't get extra
+ * delivery slots.
*
* Fold the result to lower case so that we don't have multiple queues
* for the same name.
* Important! All recipients in a queue must have the same nexthop
* value. It is OK to have multiple queues with the same nexthop
* value, but only when those queues are named after recipients.
+ *
+ * The single-recipient code below was written for local(8) like
+ * delivery agents, and assumes that all domains that deliver to the
+ * same (transport + nexthop) are aliases for $nexthop. Delivery
+ * concurrency is changed from per-domain into per-recipient, by
+ * changing the queue name from nexthop into localpart@nexthop.
+ *
+ * XXX This assumption is incorrect when different destinations share
+ * the same (transport + nexthop). In reality, such transports are
+ * rarely configured to use single-recipient deliveries. The fix is
+ * to decouple the per-destination recipient limit from the
+ * per-destination concurrency.
*/
vstring_strcpy(queue_name, STR(reply.nexthop));
if (strcmp(transport->name, MAIL_SERVICE_ERROR) != 0
&& transport->recipient_limit == 1) {
+ /* Copy the recipient localpart. */
at = strrchr(STR(reply.recipient), '@');
len = (at ? (at - STR(reply.recipient))
: strlen(STR(reply.recipient)));
- VSTRING_SPACE(queue_name, len + 2);
- memmove(STR(queue_name) + len + 1, STR(queue_name),
- LEN(queue_name) + 1);
- memcpy(STR(queue_name), STR(reply.recipient), len);
- STR(queue_name)[len] = '@';
+ vstring_strncpy(queue_name, STR(reply.recipient), len);
+ /* Remove the address extension from the recipient localpart. */
+ if (*var_rcpt_delim && split_addr(STR(queue_name), *var_rcpt_delim))
+ vstring_truncate(queue_name, strlen(STR(queue_name)));
+ /* Assume the recipient domain is equivalent to nexthop. */
+ vstring_sprintf_append(queue_name, "@%s", STR(reply.nexthop));
}
lowercase(STR(queue_name));
#include "sys_defs.h"
#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
#include <time.h>
#include <errno.h>
int deliver_status;
int copy_flags;
struct stat st;
- time_t starttime = time((time_t *) 0);
+ struct timeval starttime;
+
+ GETTIMEOFDAY(&starttime);
/*
* Make verbose logging easier to understand.
* filesystem: a maildir has to be within a single UNIX device for link()
* and rename() to work.)
*
- * [...]
+ * Mn, where n is (in decimal) the microsecond counter from the same
+ * gettimeofday() used for the left part of the unique name.
*
* Pn, where n is (in decimal) the process ID.
*
set_eugid(usr_attr.uid, usr_attr.gid);
vstring_sprintf(buf, "%lu.P%d.%s",
- (unsigned long) starttime, var_pid, get_hostname());
+ (unsigned long) starttime.tv_sec, var_pid, get_hostname());
tmpfile = concatenate(tmpdir, STR(buf), (char *) 0);
newfile = 0;
if ((dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0
} else if (fstat(vstream_fileno(dst), &st) < 0) {
vstring_sprintf(why, "create %s: %m", tmpfile);
} else {
- vstring_sprintf(buf, "%lu.V%lxI%lx.%s",
- (unsigned long) starttime, (unsigned long) st.st_dev,
- (unsigned long) st.st_ino, get_hostname());
+ vstring_sprintf(buf, "%lu.V%lxI%lxM%lu.%s",
+ (unsigned long) starttime.tv_sec,
+ (unsigned long) st.st_dev,
+ (unsigned long) st.st_ino,
+ (unsigned long) starttime.tv_usec,
+ get_hostname());
newfile = concatenate(newdir, STR(buf), (char *) 0);
if ((mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr),
dst, copy_flags, "\n", why)) == 0) {