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.
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
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
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.
<li> <p> Uncomment the new "<tt>smtpd pass ... smtpd</tt>" service
in <a href="master.5.html">master.cf</a>, and duplicate any "<tt>-o parameter=value</tt>" entries
-from the smtpd service that was commented out in step 1. </p>
+from the smtpd service that was commented out in the previous step.
+</p>
<pre>
/etc/postfix/<a href="master.5.html">master.cf</a>:
<li> <p> Uncomment the new "<tt>smtpd pass ... smtpd</tt>" service
in master.cf, and duplicate any "<tt>-o parameter=value</tt>" entries
-from the smtpd service that was commented out in step 1. </p>
+from the smtpd service that was commented out in the previous step.
+</p>
<pre>
/etc/postfix/master.cf:
* 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
/* 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
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 */
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;
/*
* 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 */
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
}
/*
- * 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
* 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);
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,
/* 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
#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)
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,
* 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);
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
* 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)
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;
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);
}
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)
&& 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);
}
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 */
#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 */
#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 *,...);