Bugfix: Postfix would incorrectly reject domain names with
adjacent - characters. File: util/valid_hostname.c.
- The 20000505 pipeline tarpit delay flush was wrong and
- caused SMTP protocol errors.
+ Bugfix: the 20000505 pipeline tarpit delay flush was wrong
+ and caused the client and server to get out of phase. Yuck!
+
+20000513
+
+ Feature: VSTREAMs now have the concept of last fill/flush
+ time, which is needed to prevent timeouts with pipelined
+ SMTP sessions as detailed in the next item.
+
+ Bugfix: automatic SMTP command/reply flushing to prevent
+ delays from accumulating within pipelined SMTP sessions.
+ For example, client-side delays happen when a client does
+ DNS lookups to replace hostname aliases in a MAIL FROM or
+ RCPT TO commands; server-side delays happen when an UCE
+ restriction involves a time-consuming DNS lookup, or when
+ a server generates a tarpit delay. Files: */*chat.c.
+
+ Portability: define ANAL_CAST for compilation environments
+ that complain about explicit casts between pointers and
+ integral types. File: util/sys_defs.h, master/*server.c.
+Major changes with snapshot-20000513
+====================================
+
+LaMont Jones and Patrik Rak reported two different scenarios in
+which pipelined SMTP sessions could time out forever. Postfix now
+automatically flushes delayed SMTP commands/replies to prevent
+delays from accumulating and causing timeouts in pipelined SMTP
+sessions. For example, client-side delays happen when a client
+does DNS lookups to replace hostname aliases in a MAIL FROM or RCPT
+TO commands; server-side delays happen when an UCE restriction
+involves DNS lookup, or when a server generates a tarpit delay.
+
Incompatible changes with snapshot-20000507
===========================================
* Version of this program.
*/
#define VAR_MAIL_VERSION "mail_version"
-#define DEF_MAIL_VERSION "Snapshot-20000511"
+#define DEF_MAIL_VERSION "Snapshot-20000513"
extern char *var_mail_version;
/* LICENSE
* Send the command to the LMTP server.
*/
smtp_fputs(STR(state->buffer), LEN(state->buffer), session->stream);
+
+ /*
+ * Flush unsent output if no I/O happened for a while. This avoids
+ * timeouts with pipelined LMTP sessions that have lots of client-side
+ * delays. The code is here so that it applies to the entire
+ * conversation, never mind that it violates layering.
+ */
+ if (time((time_t *) 0) - vstream_ftime(session->stream) > 10)
+ vstream_fflush(session->stream);
}
/* lmtp_chat_resp - read and process LMTP server response */
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
+#include <string.h>
/* Utility library. */
static void multi_server_accept_local(int unused_event, char *context)
{
- int listen_fd = (int) context;
+ int listen_fd = CAST_CHAR_PTR_TO_INT(context);
int time_left = -1;
int fd;
static void multi_server_accept_inet(int unused_event, char *context)
{
- int listen_fd = (int) context;
+ int listen_fd = CAST_CHAR_PTR_TO_INT(context);
int time_left = -1;
int fd;
if (var_idle_limit > 0)
event_request_timer(multi_server_timeout, (char *) 0, var_idle_limit);
for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
- event_enable_read(fd, multi_server_accept, (char *) fd);
+ event_enable_read(fd, multi_server_accept, CAST_INT_TO_CHAR_PTR(fd));
close_on_exec(fd, CLOSE_ON_EXEC);
}
event_enable_read(MASTER_STATUS_FD, multi_server_abort, (char *) 0);
static void single_server_accept_local(int unused_event, char *context)
{
- int listen_fd = (int) context;
+ int listen_fd = CAST_CHAR_PTR_TO_INT(context);
int time_left = -1;
int fd;
static void single_server_accept_inet(int unused_event, char *context)
{
- int listen_fd = (int) context;
+ int listen_fd = CAST_CHAR_PTR_TO_INT(context);
int time_left = -1;
int fd;
if (var_idle_limit > 0)
event_request_timer(single_server_timeout, (char *) 0, var_idle_limit);
for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
- event_enable_read(fd, single_server_accept, (char *) fd);
+ event_enable_read(fd, single_server_accept, CAST_INT_TO_CHAR_PTR(fd));
close_on_exec(fd, CLOSE_ON_EXEC);
}
event_enable_read(MASTER_STATUS_FD, single_server_abort, (char *) 0);
static void trigger_server_accept_fifo(int unused_event, char *context)
{
char *myname = "trigger_server_accept_fifo";
- int listen_fd = (int) context;
+ int listen_fd = CAST_CHAR_PTR_TO_INT(context);
if (trigger_server_lock != 0
&& myflock(vstream_fileno(trigger_server_lock), MYFLOCK_NONE) < 0)
static void trigger_server_accept_local(int unused_event, char *context)
{
char *myname = "trigger_server_accept_local";
- int listen_fd = (int) context;
+ int listen_fd = CAST_CHAR_PTR_TO_INT(context);
int time_left = 0;
int fd;
if (var_idle_limit > 0)
event_request_timer(trigger_server_timeout, (char *) 0, var_idle_limit);
for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
- event_enable_read(fd, trigger_server_accept, (char *) fd);
+ event_enable_read(fd, trigger_server_accept, CAST_INT_TO_CHAR_PTR(fd));
close_on_exec(fd, CLOSE_ON_EXEC);
}
event_enable_read(MASTER_STATUS_FD, trigger_server_abort, (char *) 0);
* Send the command to the SMTP server.
*/
smtp_fputs(STR(state->buffer), LEN(state->buffer), session->stream);
+
+ /*
+ * Flush unsent output if no I/O happened for a while. This avoids
+ * timeouts with pipelined SMTP sessions that have lots of client-side
+ * delays. The code is here so that it applies to the entire
+ * conversation, never mind that it violates layering.
+ */
+ if (time((time_t *) 0) - vstream_ftime(session->stream) > 10)
+ vstream_fflush(session->stream);
}
/* smtp_chat_resp - read and process SMTP server response */
#include <sys_defs.h>
#include <setjmp.h>
#include <unistd.h>
+#include <time.h>
#include <stdlib.h> /* 44BSD stdarg.h uses abort() */
#include <stdarg.h>
void smtpd_chat_reply(SMTPD_STATE *state, char *format,...)
{
va_list ap;
- int slept = 0;
va_start(ap, format);
vstring_vsprintf(state->buffer, format, ap);
* errors within a session.
*/
if (state->error_count > var_smtpd_soft_erlim)
- sleep(slept = state->error_count);
+ sleep(state->error_count);
else if (STR(state->buffer)[0] == '4' || STR(state->buffer)[0] == '5')
- sleep(slept = var_smtpd_err_sleep);
+ sleep(var_smtpd_err_sleep);
smtp_fputs(STR(state->buffer), LEN(state->buffer), state->client);
/*
- * Flush unsent output AFTER writing instead of before sleeping (so that
- * vstream_fflush() flushes the output half of a bidirectional stream).
- * Pipelined error responses could result in client-side timeouts.
+ * Flush unsent output if no I/O happened for a while. This avoids
+ * timeouts with pipelined SMTP sessions that have lots of server-side
+ * delays (tarpit delays or DNS lookups for UCE restrictions).
*/
- if (slept)
- (vstream_fflush(state->client));
+ if (time((time_t *) 0) - vstream_ftime(state->client) > 10)
+ vstream_fflush(state->client);
}
/* print_line - line_wrap callback */
* directory. Adding support for a new system type means updating the
* makedefs script, and adding a section below for the new system.
*/
-#if (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 104250000)
-#define ALIAS_DB_MAP "hash:/etc/mail/aliases" /* sendmail 8.10 */
-#endif
-
#if defined(FREEBSD2) || defined(FREEBSD3) || defined(FREEBSD4) \
|| defined(FREEBSD5) \
|| defined(BSDI2) || defined(BSDI3) || defined(BSDI4) \
|| defined(OPENBSD2) || defined(NETBSD1)
#define SUPPORTED
#include <sys/types.h>
+#include <sys/param.h>
#define USE_PATHS_H
#define USE_FLOCK_LOCK
#define HAS_SUN_LEN
#define HAS_DB
#define HAS_SA_LEN
#define DEF_DB_TYPE "hash"
+#if (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 104250000)
+#define ALIAS_DB_MAP "hash:/etc/mail/aliases" /* sendmail 8.10 */
+#endif
#ifndef ALIAS_DB_MAP
#define ALIAS_DB_MAP "hash:/etc/aliases"
#endif
#endif
#if defined(NETBSD1)
+#define ANAL_CAST
#define USE_DOT_LOCK
#endif
#error "unsupported platform"
#endif
+#ifndef ANAL_CAST
+#define CAST_CHAR_PTR_TO_INT(cptr) ((int) (long) (cptr))
+#define CAST_INT_TO_CHAR_PTR(ival) ((char *) (long) (ival))
+#else
+#define CAST_CHAR_PTR_TO_INT(cptr) ((int) (cptr))
+#define CAST_INT_TO_CHAR_PTR(ival) ((char *) (ival))
+#endif
+
#ifdef DUP2_DUPS_CLOSE_ON_EXEC
/* dup2_pass_on_exec() can be found in util/sys_compat.c */
extern int dup2_pass_on_exec(int oldd, int newd);
/* int vbuf_eof(bp)
/* VBUF *bp;
/*
+/* int vbuf_timeout(bp)
+/* VBUF *bp;
+/*
/* int vbuf_clearerr(bp)
/* VBUF *bp;
/* DESCRIPTION
/* number of bytes transferred. A short count is returned in case of
/* an error.
/*
-/* vbuf_err() (vbuf_eof()) is a macro that returns non-zero if an error
-/* (end-of-file) condition was detected while reading or writing the
-/* buffer. The error status can be reset by calling vbuf_clearerr().
+/* vbuf_timeout() is a macro that returns non-zero if a timeout error
+/* condition was detected while reading or writing the buffer. The
+/* error status can be reset by calling vbuf_clearerr().
+/*
+/* vbuf_err() is a macro that returns non-zero if a non-EOF error
+/* (including timeout) condition was detected while reading or writing
+/* the buffer. The error status can be reset by calling vbuf_clearerr().
+/*
+/* vbuf_eof() is a macro that returns non-zero if an end-of-file
+/* condition was detected while reading or writing the buffer. The error
+/* status can be reset by calling vbuf_clearerr().
/* APPLICATION CALLBACK SYNOPSIS
/* int get_ready(bp)
/* VBUF *bp;
/* void longjmp(stream, val)
/* VSTREAM *stream;
/* int val;
+/*
+/* time_t vstream_ftime(stream)
+/* VSTREAM *stream;
/* DESCRIPTION
/* The \fIvstream\fR module implements light-weight buffered I/O
/* similar to the standard I/O routines.
/*
/* NB: non-local jumps such as vstream_longjmp() are not safe
/* for jumping out of any vstream routine.
+/*
+/* vstream_ftime() returns the time of initialization, the last buffer
+/* fill operation, or the last buffer flush operation for the specified
+/* stream. This information is maintained only when stream timeouts are
+/* enabled.
/* DIAGNOSTICS
/* Panics: interface violations. Fatal errors: out of memory.
/* SEE ALSO
#include <stddef.h>
#include <unistd.h>
#include <fcntl.h>
+#include <time.h>
#include <errno.h>
#include <string.h>
* any.
*/
for (data = (char *) bp->data, len = to_flush; len > 0; len -= n, data += n) {
+ if (stream->timeout)
+ stream->iotime = time((time_t *) 0);
if ((n = stream->write_fn(stream->fd, data, len, stream->timeout, stream->context)) <= 0) {
bp->flags |= VSTREAM_FLAG_ERR;
if (errno == ETIMEDOUT)
* data as is available right now, whichever is less. Update the cached
* file seek position, if any.
*/
+ if (stream->timeout)
+ stream->iotime = time((time_t *) 0);
switch (n = stream->read_fn(stream->fd, bp->data, bp->len, stream->timeout, stream->context)) {
case -1:
bp->flags |= VSTREAM_FLAG_ERR;
stream->timeout = 0;
stream->context = 0;
stream->jbuf = 0;
+ stream->iotime = 0;
return (stream);
}
stream->waitpid_fn = va_arg(ap, VSTREAM_WAITPID_FN);
break;
case VSTREAM_CTL_TIMEOUT:
+ if (stream->timeout == 0)
+ stream->iotime = time((time_t *) 0);
stream->timeout = va_arg(ap, int);
break;
case VSTREAM_CTL_EXCEPT:
/*
* System library.
*/
+#include <time.h>
#include <fcntl.h>
#include <stdarg.h>
#include <setjmp.h>
VSTREAM_WAITPID_FN waitpid_fn; /* vstream_popen/close() */
int timeout; /* read/write timout */
jmp_buf *jbuf; /* exception handling */
+ time_t iotime; /* time of last fill/flush */
} VSTREAM;
extern VSTREAM vstream_fstd[]; /* pre-defined streams */
#define vstream_ftimeout(vp) vbuf_timeout(&(vp)->buf)
#define vstream_clearerr(vp) vbuf_clearerr(&(vp)->buf)
#define VSTREAM_PATH(vp) ((vp)->path ? (vp)->path : "unknown_stream")
+#define vstream_ftime(vp) ((vp)->iotime)
extern void vstream_control(VSTREAM *, int,...);