disable further network writes. This eliminates the need
for clumsy code to avoid unwanted I/O while shutting down
a TLS engine or closing a VSTREAM. File: util/smtp_stream.c.
+
+20110201
+
+ Cleanup: when verifying that the client_address->client_name
+ lookup result resolves to the client_address, request
+ hostname->address lookup with the same protocol family (IPv4
+ or IPv6) as the client_address. Files: util/myaddrinfo.[hc],
+ smtpd/smtpd_peer.c, qmqpd/qmqpd_peer.c.
+
+20110205
+
+ Infrastructure: vstream_peek_data() primitive to look ahead
+ at buffered input. Use vstream_peek() to find out how much,
+ and escape() for human presentation. Files: util/vstream.[hc].
+
+ Cleanup: smtpd(8) and postscreen(8) now log the input that
+ triggers an SMTP command pipelining violation. File:
+ postscreen/postscreen_smtpd.c, smtpd/smtpd.c.
+
+ Infrastructure: smtp_get() option to skip over input in
+ excess of the line length limit. Files: smtp/smtp_stream.[hc].
+
+ Cleanup: handle excessively-long client requests and server
+ responses more gracefully, i.e. without losing synchronization.
+ Files: smtpd/smtpd_chat.c, smtpd/smtpd_proxy.c, smtp/smtp_chat.c,
+ smtpstone/smtp-source.c.
not receiving email.
The main challenge for postscreen(8) is to make an is-it-a-zombie decision
-based on a single measurement. This is necessary because many zombies avoid
-spamming the same site repeatedly, in an attempt to fly under the radar. Once
+based on a single measurement. This is necessary because many zombies try to
+fly under the radar and avoid spamming the same site repeatedly. Once
postscreen(8) decides that a client is not-a-zombie, it whitelists the client
temporarily to avoid further delays for legitimate mail.
1 week for complex tests. Whitelisting minimizes the impact of postscreen(8)'s
tests on legitimate mail clients.
-After logging its findings, postscreen(8) by default hands off all connections
-to a Postfix SMTP server process. This mode is useful for non-destructive
+By default, postscreen(8) hands off all connections to a Postfix SMTP server
+process after logging its findings. This mode is useful for non-destructive
testing.
In a typical production setting, postscreen(8) is configured to reject mail
Don't forget Apple's code donation for fetching mail from
IMAP server.
- propagate alias owner from pcre, regexp, etc. databases.
+ postconf command-line option to show the compile-time
+ settings (CCARGS, AUXLIBS) in case binary packages
+ don't install the makedefs.out file.
- vstream_peek_len() and vstream_peek_data() to count the
- unread data and to access it, respectively. vstream_peek_data()
- can access the saved read buffer if a double-buffered stream
- is in write mode.
+ propagate alias owner from pcre, regexp, cidr, texthash,
+ etc. databases, i.e. set the owner property at open time;
+ it can't be looked up at run-time with fstat(dict->stat_fd)
+ because there is no open file. What about *SQL, LDAP, etc.?
events.c: cache the side effects of file descriptor event
enable/disable operations in user space, and do bulk kernel
Make postconf aware of local_, smtp_, etc. parameter names
that have prefixes derived from mail delivery transport
- names. Unfortunately, it is wrong to assume that all "unix"
- master.cf entries are delivery agents (though it may be OK
- for postconf to peek in master.cf when given a parameter
- with an unknown prefix). This requires a new main.cf parameter
+ names, LDAP/SQL table names, spawn(8) services, and so on.
+ Clearly, it is wrong to assume that all "unix" master.cf
+ entries are delivery agents (though it may be OK for postconf
+ to peek in master.cf when given a parameter with an unknown
+ prefix). This requires a new main.cf parameter (delivery_prefixes?)
that lists all known mail delivery transport names. postconf
can safely ignore names that don't exist in master.cf, and
qmgr_transport_create() can safely warn about a name that
- isn't listed in that new main.cf parameter.
+ isn't listed in that new main.cf parameter. A similar
+ parameter would be needed for spawn(8) services (spawn_prefixes?)
+ and for legacy-style database "sources" (database_prefixes?).
+ The spawn(8) daemon could warn if the service name is not
+ listed in main.cf, and the LDAP/SQL/etc. drivers could
+ warn if a legacy-style database source is not listed in
+ main.cf.
Need a regular expression table to translate address
verification responses into hard/soft/accept reply codes.
<p> The main challenge for <a href="postscreen.8.html">postscreen(8)</a> is to make an is-it-a-zombie
decision based on a single measurement. This is necessary because
-many zombies avoid spamming the same site repeatedly, in an attempt
-to fly under the radar. Once <a href="postscreen.8.html">postscreen(8)</a> decides that a client
-is not-a-zombie, it whitelists the client temporarily to avoid
-further delays for legitimate mail. </p>
+many zombies try to fly under the radar and avoid spamming the same
+site repeatedly. Once <a href="postscreen.8.html">postscreen(8)</a> decides that a client is
+not-a-zombie, it whitelists the client temporarily to avoid further
+delays for legitimate mail. </p>
<p> Zombies have challenges too: they have only a limited amount
of time to deliver spam before their IP address becomes blacklisted.
tests. Whitelisting minimizes the impact of <a href="postscreen.8.html">postscreen(8)</a>'s tests
on legitimate mail clients. </p>
-<p> After logging its findings, <a href="postscreen.8.html">postscreen(8)</a> by default hands off
-all connections to a Postfix SMTP server process. This mode is
-useful for non-destructive testing. </p>
+<p> By default, <a href="postscreen.8.html">postscreen(8)</a> hands off all connections to a Postfix
+SMTP server process after logging its findings. This mode is useful
+for non-destructive testing. </p>
<p> In a typical production setting, <a href="postscreen.8.html">postscreen(8)</a> is configured
to reject mail from clients that fail one or more tests, after
<a href="http://tools.ietf.org/html/rfc1985">RFC 1985</a> (ETRN command)
<a href="http://tools.ietf.org/html/rfc2034">RFC 2034</a> (SMTP Enhanced Status Codes)
<a href="http://tools.ietf.org/html/rfc2821">RFC 2821</a> (SMTP protocol)
- <a href="http://tools.ietf.org/html/rfc2920">RFC 2920</a> (SMTP Pipelining)
+ Not: <a href="http://tools.ietf.org/html/rfc2920">RFC 2920</a> (SMTP Pipelining)
<a href="http://tools.ietf.org/html/rfc3207">RFC 3207</a> (STARTTLS command)
<a href="http://tools.ietf.org/html/rfc3461">RFC 3461</a> (SMTP DSN Extension)
<a href="http://tools.ietf.org/html/rfc3463">RFC 3463</a> (Enhanced Status Codes)
RFC 1985 (ETRN command)
RFC 2034 (SMTP Enhanced Status Codes)
RFC 2821 (SMTP protocol)
-RFC 2920 (SMTP Pipelining)
+Not: RFC 2920 (SMTP Pipelining)
RFC 3207 (STARTTLS command)
RFC 3461 (SMTP DSN Extension)
RFC 3463 (Enhanced Status Codes)
<p> The main challenge for postscreen(8) is to make an is-it-a-zombie
decision based on a single measurement. This is necessary because
-many zombies avoid spamming the same site repeatedly, in an attempt
-to fly under the radar. Once postscreen(8) decides that a client
-is not-a-zombie, it whitelists the client temporarily to avoid
-further delays for legitimate mail. </p>
+many zombies try to fly under the radar and avoid spamming the same
+site repeatedly. Once postscreen(8) decides that a client is
+not-a-zombie, it whitelists the client temporarily to avoid further
+delays for legitimate mail. </p>
<p> Zombies have challenges too: they have only a limited amount
of time to deliver spam before their IP address becomes blacklisted.
tests. Whitelisting minimizes the impact of postscreen(8)'s tests
on legitimate mail clients. </p>
-<p> After logging its findings, postscreen(8) by default hands off
-all connections to a Postfix SMTP server process. This mode is
-useful for non-destructive testing. </p>
+<p> By default, postscreen(8) hands off all connections to a Postfix
+SMTP server process after logging its findings. This mode is useful
+for non-destructive testing. </p>
<p> In a typical production setting, postscreen(8) is configured
to reject mail from clients that fail one or more tests, after
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20110130"
+#define MAIL_RELEASE_DATE "20110205"
#define MAIL_VERSION_NUMBER "2.9"
#ifdef SNAPSHOT
/* int smtp_fgetc(stream)
/* VSTREAM *stream;
/*
-/* int smtp_get(vp, stream, maxlen)
+/* int smtp_get(vp, stream, maxlen, flags)
/* VSTRING *vp;
/* VSTREAM *stream;
/* ssize_t maxlen;
+/* int flags;
/*
/* void smtp_fputs(str, len, stream)
/* const char *str;
/* and protects the program against running out of memory.
/* Specify a zero bound to turn off bounds checking.
/* The result is the last character read, or VSTREAM_EOF.
+/* The \fIflags\fR argument is either SMTP_GET_FLAG_NONE (no
+/* special processing) or SMTP_GET_FLAG_SKIP (skip over input
+/* in excess of \fImaxlen\fR). Either way, a result value of
+/* '\n' means that the input did not exceed \fImaxlen\fR.
/*
/* smtp_fputs() writes its string argument to the named stream.
/* Long strings are not broken. Each string is followed by a
/* smtp_get - read one line from SMTP peer */
-int smtp_get(VSTRING *vp, VSTREAM *stream, ssize_t bound)
+int smtp_get(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags)
{
int last_char;
int next_char;
break;
}
+ /*
+ * Optionally, skip over excess input, protected by the same time limit.
+ */
+ if (last_char != '\n' && (flags & SMTP_GET_FLAG_SKIP)
+ && vstream_feof(stream) == 0 && vstream_ferror(stream) == 0)
+ while ((next_char = VSTREAM_GETC(stream)) != VSTREAM_EOF
+ && next_char != '\n')
+ /* void */ ;
+
/*
* EOF is bad, whether or not it happens in the middle of a record. Don't
* allow data that was truncated because of EOF.
extern void PRINTFLIKE(2, 3) smtp_printf(VSTREAM *, const char *,...);
extern void smtp_flush(VSTREAM *);
extern int smtp_fgetc(VSTREAM *);
-extern int smtp_get(VSTRING *, VSTREAM *, ssize_t);
+extern int smtp_get(VSTRING *, VSTREAM *, ssize_t, int);
extern void smtp_fputs(const char *, ssize_t len, VSTREAM *);
extern void smtp_fwrite(const char *, ssize_t len, VSTREAM *);
extern void smtp_fputc(int, VSTREAM *);
extern void smtp_vprintf(VSTREAM *, const char *, va_list);
+#define SMTP_GET_FLAG_NONE 0
+#define SMTP_GET_FLAG_SKIP (1<<0) /* skip over excess input */
+
/* LICENSE
/* .ad
/* .fi
/* RFC 1985 (ETRN command)
/* RFC 2034 (SMTP Enhanced Status Codes)
/* RFC 2821 (SMTP protocol)
-/* RFC 2920 (SMTP Pipelining)
+/* Not: RFC 2920 (SMTP Pipelining)
/* RFC 3207 (STARTTLS command)
/* RFC 3461 (SMTP DSN Extension)
/* RFC 3463 (Enhanced Status Codes)
#define PSC_SMTPD_BUFFER_EMPTY(state) \
(!PSC_SMTPD_HAVE_PUSH_BACK(state) \
- && vstream_peek(state->smtp_client_stream) <= 0)
+ && vstream_peek((state)->smtp_client_stream) <= 0)
+
+#define PSC_SMTPD_PEEK_DATA(state) \
+ vstream_peek_data((state)->smtp_client_stream)
+#define PSC_SMTPD_PEEK_LEN(state) \
+ vstream_peek((state)->smtp_client_stream)
/*
* Dynamic reply strings. To minimize overhead we format these once.
if ((state->flags & PSC_STATE_MASK_PIPEL_TODO_SKIP)
== PSC_STATE_FLAG_PIPEL_TODO && !PSC_SMTPD_BUFFER_EMPTY(state)) {
printable(command, '?');
- msg_info("COMMAND PIPELINING from [%s]:%s after %.100s",
- PSC_CLIENT_ADDR_PORT(state), command);
+ escape(psc_temp, PSC_SMTPD_PEEK_DATA(state),
+ PSC_SMTPD_PEEK_LEN(state) < 100 ?
+ PSC_SMTPD_PEEK_LEN(state) : 100);
+ msg_info("COMMAND PIPELINING from [%s]:%s after %.100s: %s",
+ PSC_CLIENT_ADDR_PORT(state), command, STR(psc_temp));
PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_PIPEL_FAIL);
PSC_UNPASS_SESSION_STATE(state, PSC_STATE_FLAG_PIPEL_PASS);
state->pipel_stamp = PSC_TIME_STAMP_DISABLED; /* XXX */
/*
* Reject the hostname if it does not list the peer address.
*/
- aierr = hostname_to_sockaddr(state->name, (char *) 0, 0, &res0);
+ aierr = hostname_to_sockaddr_pf(state->name, state->addr_family,
+ (char *) 0, 0, &res0);
if (aierr) {
msg_warn("%s: hostname %s verification failed: %s",
state->addr, state->name, MAI_STRERROR(aierr));
* Censor out non-printable characters in server responses. Concatenate
* multi-line server responses. Separate the status code from the text.
* Leave further parsing up to the application.
+ *
+ * We can't parse or store input that exceeds var_line_limit, so we just
+ * skip over it to simplify the remainder of the code below.
*/
VSTRING_RESET(rdata.str_buf);
for (;;) {
- last_char = smtp_get(session->buffer, session->stream, var_line_limit);
+ last_char = smtp_get(session->buffer, session->stream, var_line_limit,
+ SMTP_GET_FLAG_SKIP);
+ /* XXX Update the per-line time limit. */
printable(STR(session->buffer), '?');
if (last_char != '\n')
msg_warn("%s: response longer than %d: %.30s...",
* because sendmail permits it.
*/
for (prev_rec_type = 0; /* void */ ; prev_rec_type = curr_rec_type) {
- if (smtp_get(state->buffer, state->client, var_line_limit) == '\n')
+ if (smtp_get(state->buffer, state->client, var_line_limit,
+ SMTP_GET_FLAG_NONE) == '\n')
curr_rec_type = REC_TYPE_NORM;
else
curr_rec_type = REC_TYPE_CONT;
&& (state->flags & SMTPD_FLAG_ILL_PIPELINING) == 0
&& (vstream_peek(state->client) > 0
|| peekfd(vstream_fileno(state->client)) > 0)) {
- msg_info("improper command pipelining after %s from %s",
- cmdp->name, state->namaddr);
+ if (state->expand_buf == 0)
+ state->expand_buf = vstring_alloc(100);
+ escape(state->expand_buf, vstream_peek_data(state->client),
+ vstream_peek(state->client) < 100 ?
+ vstream_peek(state->client) : 100);
+ msg_info("improper command pipelining after %s from %s: %s",
+ cmdp->name, state->namaddr, STR(state->expand_buf));
state->flags |= SMTPD_FLAG_ILL_PIPELINING;
}
if (cmdp->action(state, argc, argv) != 0)
{
int last_char;
- last_char = smtp_get(state->buffer, state->client, var_line_limit);
+ /*
+ * We can't parse or store input that exceeds var_line_limit, so we skip
+ * over it to avoid loss of synchronization.
+ */
+ last_char = smtp_get(state->buffer, state->client, var_line_limit,
+ SMTP_GET_FLAG_SKIP);
smtp_chat_append(state, "In: ", STR(state->buffer));
if (last_char != '\n')
msg_warn("%s: request longer than %d: %.30s...",
* must not be allowed to enter the audit trail, as people would
* draw false conclusions.
*/
- aierr = hostname_to_sockaddr(state->name, (char *) 0, 0, &res0);
+ aierr = hostname_to_sockaddr_pf(state->name, state->addr_family,
+ (char *) 0, 0, &res0);
if (aierr) {
msg_warn("%s: hostname %s verification failed: %s",
state->addr, state->name, MAI_STRERROR(aierr));
FORWARD_HELO(state)))
|| ((server_xforward_features & SMTPD_PROXY_XFORWARD_IDENT)
&& smtpd_proxy_xforward_send(state, buf, XFORWARD_IDENT,
- IS_AVAIL_CLIENT_IDENT(FORWARD_IDENT(state)),
+ IS_AVAIL_CLIENT_IDENT(FORWARD_IDENT(state)),
FORWARD_IDENT(state)))
|| ((server_xforward_features & SMTPD_PROXY_XFORWARD_PROTO)
&& smtpd_proxy_xforward_send(state, buf, XFORWARD_PROTO,
/*
* Censor out non-printable characters in server responses and save
* complete multi-line responses if possible.
+ *
+ * We can't parse or store input that exceeds var_line_limit, so we just
+ * skip over it to simplify the remainder of the code below.
*/
VSTRING_RESET(proxy->buffer);
if (buffer == 0)
buffer = vstring_alloc(10);
for (;;) {
- last_char = smtp_get(buffer, proxy->service_stream, var_line_limit);
+ last_char = smtp_get(buffer, proxy->service_stream, var_line_limit,
+ SMTP_GET_FLAG_SKIP);
printable(STR(buffer), '?');
if (last_char != '\n')
msg_warn("%s: response longer than %d: %.30s...",
#define BUF ((char *) vstring_str(buf))
VSTRING_RESET(rdata.buf);
for (;;) {
- smtp_get(buf, stream, var_line_limit);
+ smtp_get(buf, stream, var_line_limit, SMTP_GET_FLAG_SKIP);
for (cp = BUF; *cp != 0; cp++)
if (!ISPRINT(*cp) && !ISSPACE(*cp))
*cp = '?';
/* int socktype;
/* struct addrinfo **result;
/*
+/* int hostname_to_sockaddr_pf(hostname, pf, service, socktype, result)
+/* const char *hostname;
+/* int pf;
+/* const char *service;
+/* int socktype;
+/* struct addrinfo **result;
+/*
/* int hostaddr_to_sockaddr(hostaddr, service, socktype, result)
/* const char *hostaddr;
/* const char *service;
/* result should be destroyed with freeaddrinfo(). A null host
/* pointer converts to the null host address.
/*
+/* hostname_to_sockaddr_pf() is an extended interface that
+/* provides a protocol family override.
+/*
/* hostaddr_to_sockaddr() converts a printable network address
/* into the corresponding binary form. The result should be
/* destroyed with freeaddrinfo(). A null host pointer converts
/* hostname, or a null pointer (meaning the wild-card listen
/* address). On output from sockaddr_to_hostname(), storage
/* for the result hostname, or a null pointer.
+/* .IP pf
+/* Protocol type: PF_UNSPEC (meaning: use any protocol that is
+/* available), PF_INET, or PF_INET6. This argument is ignored
+/* in EMULATE_IPV4_ADDRINFO mode.
/* .IP hostaddr
/* On input to hostaddr_to_sockaddr(), a numeric hostname,
/* or a null pointer (meaning the wild-card listen address).
#endif
-/* hostname_to_sockaddr - hostname to binary address form */
+/* hostname_to_sockaddr_pf - hostname to binary address form */
-int hostname_to_sockaddr(const char *hostname, const char *service,
- int socktype, struct addrinfo ** res)
+int hostname_to_sockaddr_pf(const char *hostname, int pf,
+ const char *service, int socktype,
+ struct addrinfo ** res)
{
#ifdef EMULATE_IPV4_ADDRINFO
int err;
memset((char *) &hints, 0, sizeof(hints));
- hints.ai_family = inet_proto_info()->ai_family;
+ hints.ai_family = (pf != PF_UNSPEC) ? pf : inet_proto_info()->ai_family;
hints.ai_socktype = service ? socktype : MAI_SOCKTYPE;
if (!hostname) {
hints.ai_flags = AI_PASSIVE;
char buf[MAI_SERVPORT_STRSIZE];
} MAI_SERVPORT_STR;
-extern int hostname_to_sockaddr(const char *, const char *, int,
- struct addrinfo **);
+extern int hostname_to_sockaddr_pf(const char *, int, const char *, int,
+ struct addrinfo **);
extern int hostaddr_to_sockaddr(const char *, const char *, int,
struct addrinfo **);
extern int sockaddr_to_hostaddr(const struct sockaddr *, SOCKADDR_SIZE,
#define MAI_STRERROR(e) ((e) == EAI_SYSTEM ? strerror(errno) : gai_strerror(e))
+#define hostname_to_sockaddr(host, serv, sock, res) \
+ hostname_to_sockaddr_pf((host), PF_UNSPEC, (serv), (sock), (res))
+
/*
* Macros for the case where we really don't want to be bothered with things
* that may fail.
*/
-#define HOSTNAME_TO_SOCKADDR(host, serv, sock, res) \
+#define HOSTNAME_TO_SOCKADDR_PF(host, pf, serv, sock, res) \
do { \
int _aierr; \
- _aierr = hostname_to_sockaddr((host), (serv), (sock), (res)); \
+ _aierr = hostname_to_sockaddr_pf((host), (pf), (serv), (sock), (res)); \
if (_aierr) \
- msg_fatal("hostname_to_sockaddr: %s", MAI_STRERROR(_aierr)); \
+ msg_fatal("hostname_to_sockaddr_pf: %s", MAI_STRERROR(_aierr)); \
} while (0)
+#define HOSTNAME_TO_SOCKADDR(host, serv, sock, res) \
+ HOSTNAME_TO_SOCKADDR_PF((host), PF_UNSPEC, (serv), (sock), (res))
+
#define HOSTADDR_TO_SOCKADDR(host, serv, sock, res) \
do { \
int _aierr; \
/* ssize_t vstream_peek(stream)
/* VSTREAM *stream;
/*
+/* const char *vstream_peek_data(stream)
+/* VSTREAM *stream;
+/*
/* int vstream_setjmp(stream)
/* VSTREAM *stream;
/*
/* read from the named stream without refilling the read buffer.
/* This is an alias for vstream_bufstat(stream, VSTREAM_BST_IN_PEND).
/*
+/* vstream_peek_data() returns a pointer to the unread bytes
+/* that exist according to vstream_peek().
+/*
/* vstream_setjmp() saves processing context and makes that context
/* available for use with vstream_longjmp(). Normally, vstream_setjmp()
/* returns zero. A non-zero result means that vstream_setjmp() returned
return (0);
}
}
+
+/* vstream_peek_data - peek at unread data */
+
+const char *vstream_peek_data(VSTREAM *vp)
+{
+ if (vp->buf.flags & VSTREAM_FLAG_READ) {
+ return (vp->buf.ptr);
+ } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) {
+ return (vp->read_buf.ptr);
+ } else {
+ return (0);
+ }
+}
#define vstream_peek(vp) vstream_bufstat((vp), VSTREAM_BST_IN_PEND)
+extern const char *vstream_peek_data(VSTREAM *);
+
/*
* Exception handling. We use pointer to jmp_buf to avoid a lot of unused
* baggage for streams that don't need this functionality.