#include <string.h>
#include <stdarg.h>
+#ifndef SHUT_RDWR
+#define SHUT_RDWR 2
+#endif
+
/* Sendmail 8 Milter protocol. */
#ifdef USE_LIBMILTER_INCLUDES
{
const char *reply;
+ /*
+ * XXX When the cleanup server closes its end of the Milter socket while
+ * editing a queue file, the SMTP server is left out of sync with the
+ * Milter. Sending an ABORT to the Milters will not restore
+ * synchronization, because there may be any number of Milter replies
+ * already in flight. Workaround: poison the socket and force the SMTP
+ * server to abandon it.
+ */
if (milter->fp != 0) {
+ (void) shutdown(vstream_fileno(milter->fp), SHUT_RDWR);
(void) vstream_fclose(milter->fp);
milter->fp = 0;
}
{
const char *reply;
+ /*
+ * XXX When the cleanup server closes its end of the Milter socket while
+ * editing a queue file, the SMTP server is left out of sync with the
+ * Milter. Sending an ABORT to the Milters will not restore
+ * synchronization, because there may be any number of Milter replies
+ * already in flight. Workaround: poison the socket and force the SMTP
+ * server to abandon it.
+ */
if (milter->fp != 0) {
+ (void) shutdown(vstream_fileno(milter->fp), SHUT_RDWR);
(void) vstream_fclose(milter->fp);
milter->fp = 0;
}
const char *smfir_name;
MILTERS *parent;
UINT32_TYPE index;
- const char *edit_resp;
+ const char *edit_resp = 0;
+ const char *retval = 0;
+ int done = 0;
#define DONT_SKIP_REPLY 0
/*
* Receive the reply or replies.
*
+ * Intercept all loop exits so that we can do post-(queue file edit)
+ * processing.
+ *
* XXX Bound the loop iteration count.
+ *
+ * In the end-of-body stage, the Milter may reply with one or more queue
+ * file edit requests before it replies with its final decision: accept,
+ * reject, etc. After a local queue file edit error (file too big, media
+ * write error), do not close the Milter socket in the cleanup server.
+ * Instead skip all further Milter replies until the final decision. This
+ * way the Postfix SMTP server stays in sync with the Milter, and Postfix
+ * doesn't have to lose the ability to handle multiple deliveries within
+ * the same SMTP session.
*/
#define IN_CONNECT_EVENT(e) ((e) == SMFIC_CONNECT || (e) == SMFIC_HELO)
- for (;;) {
+ /*
+ * XXX Don't evaluate this macro's argument multiple times. Since we use
+ * "continue" the macro can't be enclosed in do .. while (0).
+ */
+#define MILTER8_EVENT_BREAK(s) { \
+ retval = (s); \
+ done = 1; \
+ continue; \
+ }
+
+ while (done == 0) {
char *cp;
char *rp;
char ch;
if (milter8_read_resp(milter, event, &cmd, &data_size) != 0)
- return (milter->def_reply);
+ MILTER8_EVENT_BREAK(milter->def_reply);
if (msg_verbose)
msg_info("reply: %s data %ld bytes",
(smfir_name = str_name_code(smfir_table, cmd)) != 0 ?
case SMFIR_CONTINUE:
if (data_size != 0)
break;
- return (milter->def_reply);
+ MILTER8_EVENT_BREAK(milter->def_reply);
/*
* Decision: accept this message, or accept all further commands
/* No more events for this message. */
milter->state = MILTER8_STAT_ACCEPT_MSG;
}
- return (milter->def_reply);
+ MILTER8_EVENT_BREAK(milter->def_reply);
/*
* Decision: accept and silently discard this message. According
if (IN_CONNECT_EVENT(event)) {
msg_warn("milter %s: DISCARD action is not allowed "
"for connect or helo", milter->m.name);
- milter8_conf_error(milter);
- return (milter->def_reply);
+ MILTER8_EVENT_BREAK(milter->def_reply);
} else {
/* No more events for this message. */
milter->state = MILTER8_STAT_ACCEPT_MSG;
- return ("D");
+ MILTER8_EVENT_BREAK("D");
}
/*
milter8_close_stream(milter);
#endif
milter->state = MILTER8_STAT_REJECT_CON;
- return (milter8_def_reply(milter, "550 5.7.1 Command rejected"));
+ MILTER8_EVENT_BREAK(milter8_def_reply(milter, "550 5.7.1 Command rejected"));
} else {
- return ("550 5.7.1 Command rejected");
+ MILTER8_EVENT_BREAK("550 5.7.1 Command rejected");
}
/*
milter8_close_stream(milter);
#endif
milter->state = MILTER8_STAT_REJECT_CON;
- return (milter8_def_reply(milter,
+ MILTER8_EVENT_BREAK(milter8_def_reply(milter,
"451 4.7.1 Service unavailable - try again later"));
} else {
- return ("451 4.7.1 Service unavailable - try again later");
+ MILTER8_EVENT_BREAK("451 4.7.1 Service unavailable - try again later");
}
/*
milter8_close_stream(milter);
#endif
milter->state = MILTER8_STAT_REJECT_CON;
- return (milter8_def_reply(milter, "S"));
+ MILTER8_EVENT_BREAK(milter8_def_reply(milter, "S"));
#endif
/*
if (milter8_read_data(milter, data_size,
MILTER8_DATA_BUFFER, milter->buf,
MILTER8_DATA_END) != 0)
- return (milter->def_reply);
+ MILTER8_EVENT_BREAK(milter->def_reply);
if ((STR(milter->buf)[0] != '4' && STR(milter->buf)[0] != '5')
|| !ISDIGIT(STR(milter->buf)[1])
|| !ISDIGIT(STR(milter->buf)[2])
msg_warn("milter %s: malformed reply: %s",
milter->m.name, STR(milter->buf));
milter8_conf_error(milter);
- return (milter->def_reply);
+ MILTER8_EVENT_BREAK(milter->def_reply);
}
if ((rp = cp = strchr(STR(milter->buf), '%')) != 0) {
for (;;) {
milter8_close_stream(milter);
#endif
milter->state = MILTER8_STAT_REJECT_CON;
- return (milter8_def_reply(milter, STR(milter->buf)));
+ MILTER8_EVENT_BREAK(milter8_def_reply(milter, STR(milter->buf)));
} else {
- return (STR(milter->buf));
+ MILTER8_EVENT_BREAK(STR(milter->buf));
}
/*
if (milter8_read_data(milter, data_size,
MILTER8_DATA_BUFFER, milter->buf,
MILTER8_DATA_END) != 0)
- return (milter->def_reply);
- return ("H");
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ MILTER8_EVENT_BREAK("H");
#endif
/*
MILTER8_DATA_STRING, milter->buf,
MILTER8_DATA_STRING, milter->body,
MILTER8_DATA_END) != 0)
- return (milter->def_reply);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
parent = milter->m.parent;
/* XXX Sendmail 8 compatibility. */
if (index == 0)
msg_warn("milter %s: bad change header index: %ld",
milter->m.name, (long) index);
milter8_conf_error(milter);
- return (milter->def_reply);
+ MILTER8_EVENT_BREAK(milter->def_reply);
}
if (LEN(milter->buf) == 0) {
msg_warn("milter %s: null change header name",
milter->m.name);
milter8_conf_error(milter);
- return (milter->def_reply);
+ MILTER8_EVENT_BREAK(milter->def_reply);
}
if (STR(milter->body)[0])
edit_resp = parent->upd_header(parent->chg_context,
edit_resp = parent->del_header(parent->chg_context,
(ssize_t) index,
STR(milter->buf));
- if (edit_resp)
- return (edit_resp);
continue;
#endif
MILTER8_DATA_STRING, milter->buf,
MILTER8_DATA_STRING, milter->body,
MILTER8_DATA_END) != 0)
- return (milter->def_reply);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
parent = milter->m.parent;
edit_resp = parent->add_header(parent->chg_context,
STR(milter->buf),
STR(milter->body));
- if (edit_resp)
- return (edit_resp);
continue;
/*
MILTER8_DATA_STRING, milter->buf,
MILTER8_DATA_STRING, milter->body,
MILTER8_DATA_END) != 0)
- return (milter->def_reply);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
if ((ssize_t) index + 1 < 1) {
msg_warn("milter %s: bad insert header index: %ld",
milter->m.name, (long) index);
milter8_conf_error(milter);
- return (milter->def_reply);
+ MILTER8_EVENT_BREAK(milter->def_reply);
}
parent = milter->m.parent;
edit_resp = parent->ins_header(parent->chg_context,
(ssize_t) index + 1,
STR(milter->buf),
STR(milter->body));
- if (edit_resp)
- return (edit_resp);
continue;
#endif
if (milter8_read_data(milter, data_size,
MILTER8_DATA_STRING, milter->buf,
MILTER8_DATA_END) != 0)
- return (milter->def_reply);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
parent = milter->m.parent;
edit_resp = parent->add_rcpt(parent->chg_context,
STR(milter->buf));
- if (edit_resp)
- return (edit_resp);
continue;
/*
if (milter8_read_data(milter, data_size,
MILTER8_DATA_STRING, milter->buf,
MILTER8_DATA_END) != 0)
- return (milter->def_reply);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
parent = milter->m.parent;
edit_resp = parent->del_rcpt(parent->chg_context,
STR(milter->buf));
- if (edit_resp)
- return (edit_resp);
continue;
/*
if (milter8_read_data(milter, data_size,
MILTER8_DATA_BUFFER, milter->body,
MILTER8_DATA_END) != 0)
- return (milter->def_reply);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
parent = milter->m.parent;
edit_resp = parent->repl_body(parent->chg_context,
milter->body);
- if (edit_resp)
- return (edit_resp);
continue;
#endif
}
(smfic_name = str_name_code(smfic_table, event)) != 0 ?
smfic_name : "(unknown MTA event)");
milter8_comm_error(milter);
- return (milter->def_reply);
+ MILTER8_EVENT_BREAK(milter->def_reply);
}
/*
milter->m.name, (smfir_name = str_name_code(smfir_table, cmd)) != 0 ?
smfir_name : "unknown", (long) data_len);
milter8_comm_error(milter);
- return (milter->def_reply);
+ MILTER8_EVENT_BREAK(milter->def_reply);
}
+
+ /*
+ * XXX Some cleanup clients ask the cleanup server to bounce mail for
+ * them. In that case we must override a hard reject retval result after
+ * queue file update failure. This is not a big problem; the odds are
+ * small that a Milter application sends a hard reject after replacing
+ * the message body.
+ */
+ if (edit_resp && (retval == 0 || strchr("DS4", retval[0]) == 0))
+ retval = edit_resp;
+ return (retval);
}
/* milter8_connect - connect to filter */
VSTREAM_CTL_DOUBLE,
VSTREAM_CTL_TIMEOUT, milter->cmd_timeout,
VSTREAM_CTL_END);
+ /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */
+ if (connect_fn == inet_connect)
+ vstream_tweak_tcp(milter->fp);
/*
* Open the negotiations by sending what actions the Milter may request
msg_timeout, NO_PROTOCOL, STR(act_buf), parent);
milter->fp = vstream_fdopen(fd, O_RDWR);
vstream_control(milter->fp, VSTREAM_CTL_DOUBLE, VSTREAM_CTL_END);
+ /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */
+ vstream_tweak_sock(milter->fp);
milter->version = version;
milter->rq_mask = rq_mask;
milter->ev_mask = ev_mask;
--- /dev/null
+/*++
+/* NAME
+/* vstream_tweak 3
+/* SUMMARY
+/* performance tweaks
+/* SYNOPSIS
+/* #include <vstream.h>
+/*
+/* VSTREAM *vstream_tweak_sock(stream)
+/* VSTREAM *stream;
+/*
+/* VSTREAM *vstream_tweak_tcp(stream)
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* vstream_tweak_sock() does a best effort to boost your
+/* network performance on the specified generic stream.
+/*
+/* vstream_tweak_tcp() does a best effort to boost your
+/* Internet performance on the specified TCP stream.
+/*
+/* Arguments:
+/* .IP stream
+/* The stream being boosted.
+/* DIAGNOSTICS
+/* Panics: interface violations.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+
+/* Application-specific. */
+
+#ifdef HAS_IPV6
+#define SOCKADDR_STORAGE struct sockaddr_storage
+#else
+#define SOCKADDR_STORAGE struct sockaddr
+#endif
+
+/* vstream_tweak_sock - boost your generic network performance */
+
+int vstream_tweak_sock(VSTREAM *fp)
+{
+ SOCKADDR_STORAGE ss;
+ struct sockaddr *sa = (struct sockaddr *) & ss;
+ SOCKADDR_SIZE sa_length = sizeof(ss);
+ int ret;
+
+ /*
+ * If the caller doesn't know if this socket is AF_LOCAL, AF_INET, etc.,
+ * figure it out for them.
+ */
+ if ((ret = getsockname(vstream_fileno(fp), sa, &sa_length)) >= 0) {
+ switch (sa->sa_family) {
+#ifdef AF_INET6
+ case AF_INET6:
+#endif
+ case AF_INET:
+ ret = vstream_tweak_tcp(fp);
+ break;
+ }
+ }
+ return (ret);
+}
+
+/* vstream_tweak_tcp - boost your TCP performance */
+
+int vstream_tweak_tcp(VSTREAM *fp)
+{
+ const char *myname = "vstream_tweak_tcp";
+ int mss;
+ SOCKOPT_SIZE mss_len = sizeof(mss);
+ int err;
+
+ /*
+ * Avoid Nagle delays when VSTREAM buffers are smaller than the MSS.
+ *
+ * Forcing TCP_NODELAY to be "always on" would hurt performance in the
+ * common case where VSTREAM buffers are larger than the MSS.
+ *
+ * Instead we ask the kernel what the current MSS is, and take appropriate
+ * action. Linux <= 2.2 getsockopt(TCP_MAXSEG) always returns zero (or
+ * whatever value was stored last with setsockopt()).
+ */
+ if ((err = getsockopt(vstream_fileno(fp), IPPROTO_TCP, TCP_MAXSEG,
+ (char *) &mss, &mss_len)) < 0) {
+ msg_warn("%s: getsockopt TCP_MAXSEG: %m", myname);
+ return (err);
+ }
+ if (msg_verbose)
+ msg_info("%s: TCP_MAXSEG %d", myname, mss);
+
+ /*
+ * Fix for recent Postfix versions: increase the VSTREAM buffer size if
+ * the VSTREAM buffer is smaller than the MSS. Note: the MSS may change
+ * when the route changes and IP path MTU discovery is turned on, so we
+ * choose a somewhat larger buffer.
+ */
+#ifdef VSTREAM_CTL_BUFSIZE
+ if (mss > 0) {
+ if (mss < __MAXINT__(ssize_t) /2)
+ mss *= 2;
+ vstream_control(fp,
+ VSTREAM_CTL_BUFSIZE, (ssize_t) mss,
+ VSTREAM_CTL_END);
+ }
+
+ /*
+ * Workaround for older Postfix versions: turn on TCP_NODELAY if the
+ * VSTREAM buffer size is smaller than the MSS.
+ */
+#else
+ if (mss > VSTREAM_BUFSIZE) {
+ int nodelay = 1;
+
+ if ((err = setsockopt(vstream_fileno(fp), IPPROTO_TCP, TCP_NODELAY,
+ (char *) &nodelay, sizeof(nodelay))) < 0)
+ msg_warn("%s: setsockopt TCP_NODELAY: %m", myname);
+ }
+#endif
+ return (err);
+}