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 *,...);