mantools/postlink, proto/postconf.proto, global/mail_params.h,
global/smtp_stream.c, global/smtp_stream.h, smtpd/smtpd.c.
-20231222
+20231226
- Workaround: smtpd_forbid_bare_newline broke the BDAT command.
- File: smtpd/smtpd.c.
-
-20231123
-
- Bugfix: smtpd_forbid_bare_newline_exclusions should respect
- XCLIENT overrides. File: smtpd/smtpd.c.
-
-20231224
-
- Reverted change 20231221 and adopted a fix by Viktor. File:
- smtpd/smtpd.c.
+ Cleanup: a nicer implementation of smtpd_forbid_bare_newline
+ that does not hang up the the middle of a BDAT or DATA
+ command, and that optionally includes the offending command
+ sequence in a postmaster 'protocol' notification. Files:
+ smtpd/smtpd.c, global/stp_stream.[hc], global/cleanup_user.h,
+ global/cleanup_strerror.c.
CLEANUP_STAT_CONT, 550, "5.7.1", "message content rejected",
CLEANUP_STAT_WRITE, 451, "4.3.0", "queue file write error",
CLEANUP_STAT_NOPERM, 550, "5.7.1", "service denied",
+ CLEANUP_STAT_BARE_LF, 521, "5.5.2", "bare <LF> received",
};
static CLEANUP_STAT_DETAIL cleanup_stat_success = {
#define CLEANUP_STAT_DEFER (1<<8) /* Temporary reject */
#define CLEANUP_STAT_NOPERM (1<<9) /* Denied by non-content policy */
+ /*
+ * Non-cleanup errors that live in the same bitmask space, to centralize
+ * error handling.
+ */
+#define CLEANUP_STAT_BARE_LF (1<<16) /* Bare <LF> received */
+
/*
* These are set when we can't bounce even if we were asked to.
*/
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20231224"
+#define MAIL_RELEASE_DATE "20231226"
#define MAIL_VERSION_NUMBER "3.9"
#ifdef SNAPSHOT
/* va_list ap;
/*
/* int smtp_forbid_bare_lf;
+/* int smtp_seen_bare_lf;
/* AUXILIARY API
/* int smtp_get_noexcept(vp, stream, maxlen, flags)
/* VSTRING *vp;
/* smtp_vprintf() is the machine underneath smtp_printf().
/*
/* smtp_get_noexcept() implements the subset of smtp_get()
-/* without long jumps for timeout or EOF errors. Instead,
+/* without timeouts and without making long jumps. Instead
/* query the stream status with vstream_feof() etc.
-/* This function will make a VSTREAM long jump (error code
-/* SMTP_ERR_LF) when rejecting input with a bare newline byte.
+/* This function will set smtp_forbid_bare_lf when flagging
+/* input with a bare newline byte.
/*
/* smtp_timeout_setup() is a backwards-compatibility interface
/* for programs that don't require deadline or data-rate support.
/*
/* smtp_forbid_bare_lf controls whether smtp_get_noexcept()
-/* will reject input with a bare newline byte.
+/* will set smtp_seen_bare_lf when the line that was read last
+/* ended with a bare newline byte.
/* DIAGNOSTICS
/* .fi
/* .ad
* body content one line at a time.
*/
int smtp_forbid_bare_lf;
+int smtp_seen_bare_lf;
/* smtp_timeout_reset - reset per-stream error flags */
int last_char;
int next_char;
+ smtp_seen_bare_lf = 0;
+
/*
* It's painful to do I/O with records that may span multiple buffers.
* Allow for partial long lines (we will read the remainder later) and
vstring_truncate(vp, VSTRING_LEN(vp) - 1);
if (smtp_forbid_bare_lf
&& (VSTRING_LEN(vp) == 0 || vstring_end(vp)[-1] != '\r'))
- vstream_longjmp(stream, SMTP_ERR_LF);
+ smtp_seen_bare_lf = 1;
while (VSTRING_LEN(vp) > 0 && vstring_end(vp)[-1] == '\r')
vstring_truncate(vp, VSTRING_LEN(vp) - 1);
VSTRING_TERMINATE(vp);
#define SMTP_ERR_QUIET 3 /* silent cleanup (application) */
#define SMTP_ERR_NONE 4 /* non-error case */
#define SMTP_ERR_DATA 5 /* application data error */
-#define SMTP_ERR_LF 6 /* bare <LF> protocol error */
extern void smtp_stream_setup(VSTREAM *, int, int, int);
extern void PRINTFLIKE(2, 3) smtp_printf(VSTREAM *, const char *,...);
extern void smtp_fread_buf(VSTRING *, ssize_t len, VSTREAM *);
extern void smtp_fputc(int, VSTREAM *);
extern int smtp_forbid_bare_lf;
+extern int smtp_seen_bare_lf;
extern void smtp_vprintf(VSTREAM *, const char *, va_list);
#define REASON_TIMEOUT "timeout"
#define REASON_LOST_CONNECTION "lost connection"
#define REASON_ERROR_LIMIT "too many errors"
-#define REASON_BARE_LF "bare <LF> received"
#ifdef USE_TLS
curr_rec_type = REC_TYPE_NORM;
else
curr_rec_type = REC_TYPE_CONT;
+ if (smtp_seen_bare_lf)
+ state->err |= CLEANUP_STAT_BARE_LF;
start = vstring_str(state->buffer);
len = VSTRING_LEN(state->buffer);
if (first) {
else
smtpd_chat_reply(state,
"250 2.0.0 Ok: queued as %s", state->queue_id);
+ } else if ((state->err & CLEANUP_STAT_BARE_LF) != 0) {
+ /* Disconnect immediately. */
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ msg_info("disconnect: bare <LF> received from %s", state->namaddr);
+ smtpd_chat_reply(state, "521 5.5.2 %s Error: bare <LF> received",
+ var_myhostname);
} else if (why && IS_SMTP_REJECT(STR(why))) {
state->error_mask |= MAIL_ERROR_POLICY;
smtpd_chat_reply(state, "%s", STR(why));
*/
done = 0;
do {
- int payload_err;
/*
* Do not skip the smtp_fread_buf() call if read_len == 0. We still
smtp_fread_buf(state->buffer, read_len, state->client);
state->bdat_get_stream = vstream_memreopen(
state->bdat_get_stream, state->buffer, O_RDONLY);
- vstream_control(state->bdat_get_stream, CA_VSTREAM_CTL_EXCEPT,
- CA_VSTREAM_CTL_END);
- if ((payload_err = vstream_setjmp(state->bdat_get_stream)) != 0)
- vstream_longjmp(state->client, payload_err);
/*
* Read lines from the fragment. The last line may continue in the
*/
do {
if (smtp_get_noexcept(state->bdat_get_buffer,
- state->bdat_get_stream,
- var_line_limit,
- SMTP_GET_FLAG_APPEND) == '\n') {
+ state->bdat_get_stream,
+ var_line_limit,
+ SMTP_GET_FLAG_APPEND) == '\n') {
/* Stopped at end-of-line. */
curr_rec_type = REC_TYPE_NORM;
} else if (!vstream_feof(state->bdat_get_stream)) {
/* Skip the out_record() and VSTRING_RESET() calls below. */
break;
}
+ if (smtp_seen_bare_lf)
+ state->err |= CLEANUP_STAT_BARE_LF;
start = vstring_str(state->bdat_get_buffer);
len = VSTRING_LEN(state->bdat_get_buffer);
if (state->err == CLEANUP_STAT_OK) {
var_myhostname);
break;
- case SMTP_ERR_LF:
- state->reason = REASON_BARE_LF;
- if (vstream_setjmp(state->client) == 0)
- smtpd_chat_reply(state, "521 5.5.2 %s Error: bare <LF> received",
- var_myhostname);
- break;
-
case 0:
/*
}
watchdog_pat();
smtpd_chat_query(state);
+ if (smtp_seen_bare_lf) {
+ msg_info("disconnect: bare <LF> received from %s",
+ state->namaddr);
+ state->error_mask |= MAIL_ERROR_PROTOCOL;
+ smtpd_chat_reply(state,
+ "521 5.5.2 %s Error: bare <LF> received",
+ var_myhostname);
+ break;
+ }
/* Safety: protect internal interfaces against malformed UTF-8. */
if (var_smtputf8_enable
&& valid_utf8_stringz(STR(state->buffer)) == 0) {