#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;
+ /*
+ * While reading the following, keep in mind that a client-side Milter
+ * socket is shared between the Postfix SMTP server and the cleanup
+ * server. The SMTP server reports only the SMTP events to the Milter.
+ * The cleanup server reports the headers and body to the Milter, and
+ * receives the header or body modification requests from the Milter.
+ *
+ * XXX When the cleanup server closes its end of the Milter socket after
+ * some local/remote configuration error, 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;
+ /*
+ * While reading the following, keep in mind that a client-side Milter
+ * socket is shared between the Postfix SMTP server and the cleanup
+ * server. The SMTP server reports only the SMTP events to the Milter.
+ * The cleanup server reports the headers and body to the Milter, and
+ * receives the header or body modification requests from the Milter.
+ *
+ * XXX When the cleanup server closes its end of the Milter socket after
+ * some local or remote remote protocol error, 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;
}
return (milter->state = MILTER8_STAT_ERROR);
}
-/* milter8_edit_error - local queue file update error */
-
-static void milter8_edit_error(MILTER8 *milter, const char *reply)
-{
-
- /*
- * Close the socket, so we don't have to skip pending replies from this
- * Milter instance.
- */
- if (milter->fp != 0) {
- (void) vstream_fclose(milter->fp);
- milter->fp = 0;
- }
-
- /*
- * Set the socket state to ERROR, so we don't try to send further MTA
- * events to this Milter instance.
- */
- milter8_def_reply(milter, reply);
- milter->state = MILTER8_STAT_ERROR;
-}
-
/* milter8_close_stream - close stream to milter application */
static void milter8_close_stream(MILTER8 *milter)
const char *retval = 0;
VSTRING *body_line_buf = 0;
int done = 0;
+ int body_edit_lockout = 0;
#define DONT_SKIP_REPLY 0
* processing.
*
* XXX Bound the loop iteration count.
+ *
+ * While reading the following, keep in mind that a client-side Milter
+ * socket is shared between the Postfix SMTP server and the cleanup
+ * server. The SMTP server reports only the SMTP events to the Milter.
+ * The cleanup server reports the headers and body to the Milter, and
+ * receives the header or body modification requests from the Milter.
+ *
+ * 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, 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.
+ * This requires that the Postfix SMTP server uses something other than
+ * CLEANUP_STAT_WRITE when it loses contact with the cleanup server.
*/
#define IN_CONNECT_EVENT(e) ((e) == SMFIC_CONNECT || (e) == SMFIC_HELO)
/*
* Handle unfinished message body replacement first.
+ *
+ * XXX When SMFIR_REPLBODY is followed by some different request, we
+ * assume that the body replacement operation is complete. The queue
+ * file editing implementation currently does not support sending
+ * part 1 of the body replacement text, doing some other queue file
+ * updates, and then sending part 2 of the body replacement text. To
+ * avoid loss of data, we log an error when SMFIR_REPLBODY requests
+ * are alternated with other requests.
*/
if (body_line_buf != 0 && cmd != SMFIR_REPLBODY) {
/* In case the last body replacement line didn't end in CRLF. */
- if (LEN(body_line_buf) > 0)
+ if (edit_resp == 0 && LEN(body_line_buf) > 0)
edit_resp = parent->repl_body(parent->chg_context,
MILTER_BODY_LINE,
body_line_buf);
edit_resp = parent->repl_body(parent->chg_context,
MILTER_BODY_END,
(VSTRING *) 0);
- if (edit_resp) {
- milter8_edit_error(milter, edit_resp);
- MILTER8_EVENT_BREAK(milter->def_reply);
- }
+ body_edit_lockout = 1;
vstring_free(body_line_buf);
body_line_buf = 0;
}
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);
MILTER8_EVENT_BREAK(milter->def_reply);
} else {
/* No more events for this message. */
MILTER8_DATA_STRING, milter->body,
MILTER8_DATA_END) != 0)
MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
/* XXX Sendmail 8 compatibility. */
if (index == 0)
index = 1;
edit_resp = parent->del_header(parent->chg_context,
(ssize_t) index,
STR(milter->buf));
- if (edit_resp) {
- milter8_edit_error(milter, edit_resp);
- MILTER8_EVENT_BREAK(milter->def_reply);
- }
continue;
#endif
MILTER8_DATA_STRING, milter->body,
MILTER8_DATA_END) != 0)
MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
edit_resp = parent->add_header(parent->chg_context,
STR(milter->buf),
STR(milter->body));
- if (edit_resp) {
- milter8_edit_error(milter, edit_resp);
- MILTER8_EVENT_BREAK(milter->def_reply);
- }
continue;
/*
MILTER8_DATA_STRING, milter->body,
MILTER8_DATA_END) != 0)
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);
(ssize_t) index + 1,
STR(milter->buf),
STR(milter->body));
- if (edit_resp) {
- milter8_edit_error(milter, edit_resp);
- MILTER8_EVENT_BREAK(milter->def_reply);
- }
continue;
#endif
MILTER8_DATA_STRING, milter->buf,
MILTER8_DATA_END) != 0)
MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
edit_resp = parent->add_rcpt(parent->chg_context,
STR(milter->buf));
- if (edit_resp) {
- milter8_edit_error(milter, edit_resp);
- MILTER8_EVENT_BREAK(milter->def_reply);
- }
continue;
/*
MILTER8_DATA_STRING, milter->buf,
MILTER8_DATA_END) != 0)
MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
edit_resp = parent->del_rcpt(parent->chg_context,
STR(milter->buf));
- if (edit_resp) {
- milter8_edit_error(milter, edit_resp);
- MILTER8_EVENT_BREAK(milter->def_reply);
- }
continue;
/*
* update the message size.
*/
case SMFIR_REPLBODY:
+ if (body_edit_lockout) {
+ msg_warn("milter %s: body replacement requests can't "
+ "currently be mixed with other requests",
+ milter->m.name);
+ milter8_conf_error(milter);
+ MILTER8_EVENT_BREAK(milter->def_reply);
+ }
if (milter8_read_data(milter, data_size,
MILTER8_DATA_BUFFER, milter->body,
MILTER8_DATA_END) != 0)
MILTER8_EVENT_BREAK(milter->def_reply);
+ /* Skip to the next request after previous edit error. */
+ if (edit_resp)
+ continue;
/* Start body replacement. */
if (body_line_buf == 0) {
body_line_buf = vstring_alloc(var_line_limit);
VSTRING_ADDCH(body_line_buf, ch);
}
}
- if (edit_resp) {
- milter8_edit_error(milter, edit_resp);
- MILTER8_EVENT_BREAK(milter->def_reply);
- }
continue;
}
}
if (body_line_buf)
vstring_free(body_line_buf);
+ /*
+ * 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);
}