diff --git a/postfix/HISTORY b/postfix/HISTORY index d2f12947a..7b1195fbc 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -16514,3 +16514,16 @@ Apologies for any names omitted. Bugfix: support for the "dunno" command somehow disappeared from the postscreen_access_list implementation. File: postscreen/postscreen_access.c. + +20110123 + + Feature: read/write deadlines. Deadlines were introduced + with postscreen's dummy SMTP engine. In the Postfix SMTP + client and server, deadlines limit the total amount of time + to read or write one command line, one response line, or + one line of message content. This reduces the impact of + application exhaustion attacks that trickle data one byte + at a time. Files: util/vstream.[hc], global/smtp_stream.c. + + Cleanup: remove #ifdef MIGRATION_WARNING transitional code + from postscreen. File: postscreen/postscreen.c. diff --git a/postfix/README_FILES/POSTSCREEN_README b/postfix/README_FILES/POSTSCREEN_README index 294dbda4f..789aa379b 100644 --- a/postfix/README_FILES/POSTSCREEN_README +++ b/postfix/README_FILES/POSTSCREEN_README @@ -505,7 +505,7 @@ mail: 3. Uncomment the new "smtpd pass ... smtpd" service in master.cf, and duplicate any "-o parameter=value" entries from the smtpd service that was - commented out in step 1. + commented out in the previous step. /etc/postfix/master.cf: smtpd pass - - n - - smtpd diff --git a/postfix/WISHLIST b/postfix/WISHLIST index 32833cfa5..80d381fd5 100644 --- a/postfix/WISHLIST +++ b/postfix/WISHLIST @@ -6,6 +6,9 @@ Wish list: Things to do after the stable release: + Don't forget Apple's code donation for fetching mail from + IMAP server. + 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 @@ -35,6 +38,7 @@ Wish list: means that many tlsproxy_ parameters become postscreen_ parameters, and that tls_server_init() parameters move to to tls_server_start(). That is a significant API change. + It also means tlsproxy can't open all files before chroot(). anvil rate limit for sasl_username. diff --git a/postfix/html/POSTSCREEN_README.html b/postfix/html/POSTSCREEN_README.html index 5c74bbcae..3561164b4 100644 --- a/postfix/html/POSTSCREEN_README.html +++ b/postfix/html/POSTSCREEN_README.html @@ -701,7 +701,8 @@ that follow.
Uncomment the new "smtpd pass ... smtpd" service in master.cf, and duplicate any "-o parameter=value" entries -from the smtpd service that was commented out in step 1.
+from the smtpd service that was commented out in the previous step. +/etc/postfix/master.cf: diff --git a/postfix/proto/POSTSCREEN_README.html b/postfix/proto/POSTSCREEN_README.html index de4640099..e71ebe1a9 100644 --- a/postfix/proto/POSTSCREEN_README.html +++ b/postfix/proto/POSTSCREEN_README.html @@ -701,7 +701,8 @@ that follow.
Uncomment the new "smtpd pass ... smtpd" service in master.cf, and duplicate any "-o parameter=value" entries -from the smtpd service that was commented out in step 1.
+from the smtpd service that was commented out in the previous step. +/etc/postfix/master.cf: diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index e4ceb0dc5..77e8821a0 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 "20110120" +#define MAIL_RELEASE_DATE "20110124" #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 82d18e513..73ad87054 100644 --- a/postfix/src/global/smtp_stream.c +++ b/postfix/src/global/smtp_stream.c @@ -52,7 +52,7 @@ /* and write operations described below. /* This routine alters the behavior of streams as follows: /* .IP \(bu -/* The read/write timeout is set to the specified value. +/* The read/write total time limit is set to the specified value. /* .IP \f(bu /* The stream is configured to use double buffering. /* .IP \f(bu @@ -151,6 +151,16 @@ static void smtp_timeout_reset(VSTREAM *stream) { vstream_clearerr(stream); + + /* + * Important: the time limit feature must not introduce any system calls + * when the input is already in the buffer, or when the output still fits + * in the buffer. Such system calls would really hurt when receiving or + * sending body content one line at a time. + */ + vstream_control(stream, + VSTREAM_CTL_TIME_LIMIT, stream->timeout, + VSTREAM_CTL_END); } /* smtp_timeout_detect - test the per-stream timeout flag */ diff --git a/postfix/src/postscreen/postscreen.c b/postfix/src/postscreen/postscreen.c index 7b3332b61..67bfe3caa 100644 --- a/postfix/src/postscreen/postscreen.c +++ b/postfix/src/postscreen/postscreen.c @@ -416,13 +416,6 @@ int var_psc_post_queue_limit; int var_psc_pre_queue_limit; int var_psc_watchdog; -#undef MIGRATION_WARNING - -#ifdef MIGRATION_WARNING -char *var_psc_wlist_nets; -char *var_psc_blist_nets; - -#endif char *var_psc_acl; char *var_psc_blist_action; @@ -495,11 +488,6 @@ HTABLE *psc_client_concurrency; /* per-client concurrency */ /* * Local variables. */ -#ifdef MIGRATION_WARNING -static ADDR_MATCH_LIST *psc_wlist_nets; /* permanently whitelisted networks */ -static ADDR_MATCH_LIST *psc_blist_nets; /* permanently blacklisted networks */ - -#endif static ARGV *psc_acl; /* permanent white/backlist */ static int psc_blist_action; /* PSC_ACT_DROP/ENFORCE/etc */ @@ -715,47 +703,6 @@ static void psc_service(VSTREAM *smtp_client_stream, break; } } -#ifdef MIGRATION_WARNING - - /* - * The permanent whitelist has highest precedence (never block mail from - * whitelisted sites, and never run tests against those sites). - */ - if (psc_wlist_nets != 0 - && psc_addr_match_list_match(psc_wlist_nets, state->smtp_client_addr)) { - msg_info("WHITELISTED [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); - psc_conclude(state); - return; - } - - /* - * The permanent blacklist has second precedence. If the client is - * permanently blacklisted, send some generic reply and hang up - * immediately, or run more tests for logging purposes. - */ - if (psc_blist_nets != 0 - && psc_addr_match_list_match(psc_blist_nets, state->smtp_client_addr)) { - msg_info("BLACKLISTED [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); - PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_BLIST_FAIL); - switch (psc_blist_action) { - case PSC_ACT_DROP: - PSC_DROP_SESSION_STATE(state, - "521 5.3.2 Service currently unavailable\r\n"); - return; - case PSC_ACT_ENFORCE: - PSC_ENFORCE_SESSION_STATE(state, - "550 5.3.2 Service currently unavailable\r\n"); - break; - case PSC_ACT_IGNORE: - PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_BLIST_FAIL); - /* Not: PSC_PASS_SESSION_STATE. Repeat this test the next time. */ - break; - default: - msg_panic("%s: unknown blacklist action value %d", - myname, psc_blist_action); - } - } -#endif /* * The temporary whitelist (i.e. the postscreen cache) has the lowest @@ -787,7 +734,8 @@ static void psc_service(VSTREAM *smtp_client_stream, } /* - * Reply with 421 when we can't analyze more connections. + * Reply with 421 when we can't analyze more connections. That also means + * no deep protocol tests when the noforward flag is raised. */ if (var_psc_pre_queue_limit > 0 && psc_check_queue_length - psc_post_queue_length @@ -841,21 +789,6 @@ static void pre_jail_init(char *unused_name, char **unused_argv) * Open read-only maps before dropping privilege, for consistency with * other Postfix daemons. */ -#ifdef MIGRATION_WARNING - if (*var_psc_wlist_nets) - psc_wlist_nets = - addr_match_list_init(MATCH_FLAG_NONE, var_psc_wlist_nets); - - if (*var_psc_blist_nets) - psc_blist_nets = addr_match_list_init(MATCH_FLAG_NONE, - var_psc_blist_nets); - if (psc_blist_nets || psc_wlist_nets) { - msg_warn("The %s and %s features will be removed soon. Use %s instead", - VAR_PSC_WLIST_NETS, VAR_PSC_BLIST_NETS, VAR_PSC_ACL); - msg_warn("To stop this warning, specify empty values for %s and %s", - VAR_PSC_WLIST_NETS, VAR_PSC_BLIST_NETS); - } -#endif psc_acl_pre_jail_init(); if (*var_psc_acl) psc_acl = psc_acl_parse(var_psc_acl, VAR_PSC_ACL); @@ -1095,10 +1028,6 @@ int main(int argc, char **argv) VAR_PSC_PIPEL_ACTION, DEF_PSC_PIPEL_ACTION, &var_psc_pipel_action, 1, 0, VAR_PSC_NSMTP_ACTION, DEF_PSC_NSMTP_ACTION, &var_psc_nsmtp_action, 1, 0, VAR_PSC_BARLF_ACTION, DEF_PSC_BARLF_ACTION, &var_psc_barlf_action, 1, 0, -#ifdef MIGRATION_WARNING - VAR_PSC_WLIST_NETS, DEF_PSC_WLIST_NETS, &var_psc_wlist_nets, 0, 0, - VAR_PSC_BLIST_NETS, DEF_PSC_BLIST_NETS, &var_psc_blist_nets, 0, 0, -#endif VAR_PSC_ACL, DEF_PSC_ACL, &var_psc_acl, 0, 0, VAR_PSC_BLIST_ACTION, DEF_PSC_BLIST_ACTION, &var_psc_blist_action, 1, 0, VAR_PSC_FORBID_CMDS, DEF_PSC_FORBID_CMDS, &var_psc_forbid_cmds, 0, 0, diff --git a/postfix/src/util/vstream.c b/postfix/src/util/vstream.c index ba3e842b1..9ee143a81 100644 --- a/postfix/src/util/vstream.c +++ b/postfix/src/util/vstream.c @@ -304,6 +304,12 @@ /* int. Use an explicit cast to avoid problems on LP64 /* environments and other environments where ssize_t is larger /* than int. +/* .IP "VSTREAM_CTL_TIME_LIMIT (int)" +/* Specify an upper bound on the total time to complete all +/* subsequent read or write operations. This is different from +/* VSTREAM_CTL_TIMEOUT, which specifies a deadline for each +/* read or write operation. Specify a relative time in seconds, +/* or zero to disable this feature. /* .PP /* vstream_fileno() gives access to the file handle associated with /* a buffered stream. With streams that have separate read/write @@ -522,6 +528,21 @@ VSTREAM vstream_fstd[] = { #define VSTREAM_FFLUSH_SOME(stream) \ vstream_fflush_some((stream), (stream)->buf.len - (stream)->buf.cnt) +/* Note: this does not change a negative result into a zero result. */ +#define VSTREAM_SUB_TIME(x, y, z) \ + do { \ + (x).tv_sec = (y).tv_sec - (z).tv_sec; \ + (x).tv_usec = (y).tv_usec - (z).tv_usec; \ + while ((x).tv_usec < 0) { \ + (x).tv_usec += 1000000; \ + (x).tv_sec -= 1; \ + } \ + while ((x).tv_usec >= 1000000) { \ + (x).tv_usec -= 1000000; \ + (x).tv_sec += 1; \ + } \ + } while (0) + /* vstream_buf_init - initialize buffer */ static void vstream_buf_init(VBUF *bp, int flags) @@ -590,6 +611,9 @@ static int vstream_fflush_some(VSTREAM *stream, ssize_t to_flush) char *data; ssize_t len; ssize_t n; + int timeout; + struct timeval before; + struct timeval elapsed; /* * Sanity checks. It is illegal to flush a read-only stream. Otherwise, @@ -630,14 +654,31 @@ static int vstream_fflush_some(VSTREAM *stream, ssize_t to_flush) * any. */ for (data = (char *) bp->data, len = to_flush; len > 0; len -= n, data += n) { - if ((n = stream->write_fn(stream->fd, data, len, stream->timeout, stream->context)) <= 0) { + if (bp->flags & VSTREAM_FLAG_DEADLINE) { + timeout = stream->time_limit.tv_sec + (stream->time_limit.tv_usec > 0); + if (timeout <= 0) { + bp->flags |= (VSTREAM_FLAG_ERR | VSTREAM_FLAG_TIMEOUT); + errno = ETIMEDOUT; + return (VSTREAM_EOF); + } + if (len == to_flush) + GETTIMEOFDAY(&before); + else + before = stream->iotime; + } else + timeout = stream->timeout; + if ((n = stream->write_fn(stream->fd, data, len, timeout, stream->context)) <= 0) { bp->flags |= VSTREAM_FLAG_ERR; if (errno == ETIMEDOUT) bp->flags |= VSTREAM_FLAG_TIMEOUT; return (VSTREAM_EOF); } - if (stream->timeout) + if (timeout) GETTIMEOFDAY(&stream->iotime); + if (bp->flags & VSTREAM_FLAG_DEADLINE) { + VSTREAM_SUB_TIME(elapsed, stream->iotime, before); + VSTREAM_SUB_TIME(stream->time_limit, stream->time_limit, elapsed); + } if (msg_verbose > 2 && stream != VSTREAM_ERR && n != to_flush) msg_info("%s: %d flushed %ld/%ld", myname, stream->fd, (long) n, (long) to_flush); @@ -698,6 +739,9 @@ static int vstream_buf_get_ready(VBUF *bp) VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf); const char *myname = "vstream_buf_get_ready"; ssize_t n; + struct timeval before; + struct timeval elapsed; + int timeout; /* * Detect a change of I/O direction or position. If so, flush any @@ -759,7 +803,17 @@ static int vstream_buf_get_ready(VBUF *bp) * data as is available right now, whichever is less. Update the cached * file seek position, if any. */ - switch (n = stream->read_fn(stream->fd, bp->data, bp->len, stream->timeout, stream->context)) { + if (bp->flags & VSTREAM_FLAG_DEADLINE) { + timeout = stream->time_limit.tv_sec + (stream->time_limit.tv_usec > 0); + if (timeout <= 0) { + bp->flags |= (VSTREAM_FLAG_ERR | VSTREAM_FLAG_TIMEOUT); + errno = ETIMEDOUT; + return (VSTREAM_EOF); + } + GETTIMEOFDAY(&before); + } else + timeout = stream->timeout; + switch (n = stream->read_fn(stream->fd, bp->data, bp->len, timeout, stream->context)) { case -1: bp->flags |= VSTREAM_FLAG_ERR; if (errno == ETIMEDOUT) @@ -769,8 +823,12 @@ static int vstream_buf_get_ready(VBUF *bp) bp->flags |= VSTREAM_FLAG_EOF; return (VSTREAM_EOF); default: - if (stream->timeout) + if (timeout) GETTIMEOFDAY(&stream->iotime); + if (bp->flags & VSTREAM_FLAG_DEADLINE) { + VSTREAM_SUB_TIME(elapsed, stream->iotime, before); + VSTREAM_SUB_TIME(stream->time_limit, stream->time_limit, elapsed); + } if (msg_verbose > 2) msg_info("%s: fd %d got %ld", myname, stream->fd, (long) n); bp->cnt = -n; @@ -1082,6 +1140,7 @@ VSTREAM *vstream_fdopen(int fd, int flags) stream->context = 0; stream->jbuf = 0; stream->iotime.tv_sec = stream->iotime.tv_usec = 0; + stream->time_limit.tv_sec = stream->time_limit.tv_usec = 0; stream->req_bufsize = VSTREAM_BUFSIZE; return (stream); } @@ -1227,6 +1286,7 @@ void vstream_control(VSTREAM *stream, int name,...) int old_fd; ssize_t req_bufsize = 0; VSTREAM *stream2; + int time_limit; #define SWAP(type,a,b) do { type temp = (a); (a) = (b); (b) = (temp); } while (0) @@ -1334,6 +1394,24 @@ void vstream_control(VSTREAM *stream, int name,...) && req_bufsize > stream->req_bufsize) stream->req_bufsize = req_bufsize; break; + + /* + * Make no gettimeofday() etc. system call until we really know + * that we need to do I/O. This avoids a performance hit when + * sending or receiving body content one line at a time. + */ + case VSTREAM_CTL_TIME_LIMIT: + time_limit = va_arg(ap, int); + if (time_limit < 0) { + msg_panic("%s: bad time limit: %d", myname, time_limit); + } else if (time_limit == 0) { + stream->buf.flags &= ~VSTREAM_FLAG_DEADLINE; + } else { + stream->buf.flags |= VSTREAM_FLAG_DEADLINE; + stream->time_limit.tv_sec = time_limit; + stream->time_limit.tv_usec = 0; + } + break; default: msg_panic("%s: bad name %d", myname, name); } diff --git a/postfix/src/util/vstream.h b/postfix/src/util/vstream.h index 3c6c16aea..fb7d05854 100644 --- a/postfix/src/util/vstream.h +++ b/postfix/src/util/vstream.h @@ -57,6 +57,7 @@ typedef struct VSTREAM { int timeout; /* read/write timout */ VSTREAM_JMP_BUF *jbuf; /* exception handling */ struct timeval iotime; /* time of last fill/flush */ + struct timeval time_limit; /* read/write time limit */ } VSTREAM; extern VSTREAM vstream_fstd[]; /* pre-defined streams */ @@ -76,6 +77,7 @@ extern VSTREAM vstream_fstd[]; /* pre-defined streams */ #define VSTREAM_FLAG_SEEK (1<<10) /* seek info valid */ #define VSTREAM_FLAG_NSEEK (1<<11) /* can't seek this file */ #define VSTREAM_FLAG_DOUBLE (1<<12) /* double buffer */ +#define VSTREAM_FLAG_DEADLINE (1<<13) /* deadline active */ #define VSTREAM_PURGE_READ (1<<0) /* flush unread data */ #define VSTREAM_PURGE_WRITE (1<<1) /* flush unwritten data */ @@ -133,6 +135,7 @@ extern void vstream_control(VSTREAM *, int,...); #endif #define VSTREAM_CTL_BUFSIZE 12 #define VSTREAM_CTL_SWAP_FD 13 +#define VSTREAM_CTL_TIME_LIMIT 14 extern VSTREAM *PRINTFLIKE(1, 2) vstream_printf(const char *,...); extern VSTREAM *PRINTFLIKE(2, 3) vstream_fprintf(VSTREAM *, const char *,...);