From: Wietse Venema
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.
+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.Zombies have challenges too: they have only a limited amount of time to deliver spam before their IP address becomes blacklisted. @@ -126,9 +126,9 @@ whitelisted from 24 hours for simple tests, to 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 testing.
+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 from clients that fail one or more tests, after diff --git a/postfix/html/postscreen.8.html b/postfix/html/postscreen.8.html index 7b459f768..b2b5367bb 100644 --- a/postfix/html/postscreen.8.html +++ b/postfix/html/postscreen.8.html @@ -61,7 +61,7 @@ POSTSCREEN(8) POSTSCREEN(8) 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) diff --git a/postfix/man/man8/postscreen.8 b/postfix/man/man8/postscreen.8 index 84b85c23a..93062d888 100644 --- a/postfix/man/man8/postscreen.8 +++ b/postfix/man/man8/postscreen.8 @@ -64,7 +64,7 @@ RFC 1870 (Message Size Declaration) 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) diff --git a/postfix/proto/POSTSCREEN_README.html b/postfix/proto/POSTSCREEN_README.html index e71ebe1a9..85e675559 100644 --- a/postfix/proto/POSTSCREEN_README.html +++ b/postfix/proto/POSTSCREEN_README.html @@ -92,10 +92,10 @@ spending most of its resources 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 postscreen(8) decides that a client -is not-a-zombie, it whitelists the client temporarily to avoid -further delays for legitimate mail.
+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.Zombies have challenges too: they have only a limited amount of time to deliver spam before their IP address becomes blacklisted. @@ -126,9 +126,9 @@ whitelisted from 24 hours for simple tests, to 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 testing.
+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 from clients that fail one or more tests, after diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index a4f7c7230..9a12c01a5 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,7 +20,7 @@ * 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 diff --git a/postfix/src/global/smtp_stream.c b/postfix/src/global/smtp_stream.c index 7c5d19ad9..84de8348c 100644 --- a/postfix/src/global/smtp_stream.c +++ b/postfix/src/global/smtp_stream.c @@ -20,10 +20,11 @@ /* 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; @@ -72,6 +73,10 @@ /* 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 @@ -275,7 +280,7 @@ int smtp_fgetc(VSTREAM *stream) /* 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; @@ -330,6 +335,15 @@ int smtp_get(VSTRING *vp, VSTREAM *stream, ssize_t bound) 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. diff --git a/postfix/src/global/smtp_stream.h b/postfix/src/global/smtp_stream.h index 559392216..3bf33c912 100644 --- a/postfix/src/global/smtp_stream.h +++ b/postfix/src/global/smtp_stream.h @@ -36,13 +36,16 @@ extern void smtp_timeout_setup(VSTREAM *, int); 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 diff --git a/postfix/src/postscreen/postscreen.c b/postfix/src/postscreen/postscreen.c index 67bfe3caa..de7661549 100644 --- a/postfix/src/postscreen/postscreen.c +++ b/postfix/src/postscreen/postscreen.c @@ -54,7 +54,7 @@ /* 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) diff --git a/postfix/src/postscreen/postscreen_smtpd.c b/postfix/src/postscreen/postscreen_smtpd.c index f42855c53..062d19bff 100644 --- a/postfix/src/postscreen/postscreen_smtpd.c +++ b/postfix/src/postscreen/postscreen_smtpd.c @@ -173,7 +173,12 @@ #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. @@ -941,8 +946,11 @@ static void psc_smtpd_read_event(int event, char *context) 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 */ diff --git a/postfix/src/qmqpd/qmqpd_peer.c b/postfix/src/qmqpd/qmqpd_peer.c index a4bfe5fe7..ba297117a 100644 --- a/postfix/src/qmqpd/qmqpd_peer.c +++ b/postfix/src/qmqpd/qmqpd_peer.c @@ -243,7 +243,8 @@ void qmqpd_peer_init(QMQPD_STATE *state) /* * 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)); diff --git a/postfix/src/smtp/smtp_chat.c b/postfix/src/smtp/smtp_chat.c index 31aa0c61b..7cb1e11f3 100644 --- a/postfix/src/smtp/smtp_chat.c +++ b/postfix/src/smtp/smtp_chat.c @@ -255,10 +255,15 @@ SMTP_RESP *smtp_chat_resp(SMTP_SESSION *session) * 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...", diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c index 3dd9367b9..2c569f254 100644 --- a/postfix/src/smtpd/smtpd.c +++ b/postfix/src/smtpd/smtpd.c @@ -2942,7 +2942,8 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) * 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; @@ -4653,8 +4654,13 @@ static void smtpd_proto(SMTPD_STATE *state) && (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) diff --git a/postfix/src/smtpd/smtpd_chat.c b/postfix/src/smtpd/smtpd_chat.c index da6e80ce6..9cc30385e 100644 --- a/postfix/src/smtpd/smtpd_chat.c +++ b/postfix/src/smtpd/smtpd_chat.c @@ -127,7 +127,12 @@ void smtpd_chat_query(SMTPD_STATE *state) { 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...", diff --git a/postfix/src/smtpd/smtpd_peer.c b/postfix/src/smtpd/smtpd_peer.c index 2aa921774..af678b680 100644 --- a/postfix/src/smtpd/smtpd_peer.c +++ b/postfix/src/smtpd/smtpd_peer.c @@ -337,7 +337,8 @@ void smtpd_peer_init(SMTPD_STATE *state) * 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)); diff --git a/postfix/src/smtpd/smtpd_proxy.c b/postfix/src/smtpd/smtpd_proxy.c index a31cd83b5..9952c1e9d 100644 --- a/postfix/src/smtpd/smtpd_proxy.c +++ b/postfix/src/smtpd/smtpd_proxy.c @@ -434,7 +434,7 @@ static int smtpd_proxy_connect(SMTPD_STATE *state) 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, @@ -764,12 +764,16 @@ static int smtpd_proxy_cmd(SMTPD_STATE *state, int expect, const char *fmt,...) /* * 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...", diff --git a/postfix/src/smtpstone/smtp-source.c b/postfix/src/smtpstone/smtp-source.c index d9d8826bd..4bc531aaa 100644 --- a/postfix/src/smtpstone/smtp-source.c +++ b/postfix/src/smtpstone/smtp-source.c @@ -316,7 +316,7 @@ static RESPONSE *response(VSTREAM *stream, VSTRING *buf) #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 = '?'; diff --git a/postfix/src/util/myaddrinfo.c b/postfix/src/util/myaddrinfo.c index 171528c22..d4a6938c7 100644 --- a/postfix/src/util/myaddrinfo.c +++ b/postfix/src/util/myaddrinfo.c @@ -22,6 +22,13 @@ /* 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; @@ -59,6 +66,9 @@ /* 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 @@ -100,6 +110,10 @@ /* 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). @@ -274,10 +288,11 @@ static int find_service(const char *service, int socktype) #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 @@ -408,7 +423,7 @@ int hostname_to_sockaddr(const char *hostname, const char *service, 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; diff --git a/postfix/src/util/myaddrinfo.h b/postfix/src/util/myaddrinfo.h index 766eed27a..95d5ee9ee 100644 --- a/postfix/src/util/myaddrinfo.h +++ b/postfix/src/util/myaddrinfo.h @@ -154,8 +154,8 @@ typedef struct { 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, @@ -168,18 +168,24 @@ extern void myaddrinfo_control(int,...); #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; \ diff --git a/postfix/src/util/vstream.c b/postfix/src/util/vstream.c index 2e1f0a068..67ebca6d6 100644 --- a/postfix/src/util/vstream.c +++ b/postfix/src/util/vstream.c @@ -111,6 +111,9 @@ /* ssize_t vstream_peek(stream) /* VSTREAM *stream; /* +/* const char *vstream_peek_data(stream) +/* VSTREAM *stream; +/* /* int vstream_setjmp(stream) /* VSTREAM *stream; /* @@ -376,6 +379,9 @@ /* 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 @@ -1502,3 +1508,16 @@ ssize_t vstream_peek(VSTREAM *vp) 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); + } +} diff --git a/postfix/src/util/vstream.h b/postfix/src/util/vstream.h index 20b983e63..448d2f5ba 100644 --- a/postfix/src/util/vstream.h +++ b/postfix/src/util/vstream.h @@ -179,6 +179,8 @@ extern ssize_t vstream_bufstat(VSTREAM *, int); #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.