diff --git a/postfix/HISTORY b/postfix/HISTORY index 380b87765..b16cdc2ed 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -27644,17 +27644,11 @@ Apologies for any names omitted. 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. diff --git a/postfix/src/global/cleanup_strerror.c b/postfix/src/global/cleanup_strerror.c index 74a9406da..09a06663e 100644 --- a/postfix/src/global/cleanup_strerror.c +++ b/postfix/src/global/cleanup_strerror.c @@ -73,6 +73,7 @@ static const CLEANUP_STAT_DETAIL cleanup_stat_map[] = { 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 received", }; static CLEANUP_STAT_DETAIL cleanup_stat_success = { diff --git a/postfix/src/global/cleanup_user.h b/postfix/src/global/cleanup_user.h index d02e9cbfe..9bc0b9db4 100644 --- a/postfix/src/global/cleanup_user.h +++ b/postfix/src/global/cleanup_user.h @@ -64,6 +64,12 @@ #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 received */ + /* * These are set when we can't bounce even if we were asked to. */ diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 97edb7eec..013508eac 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 "20231224" +#define MAIL_RELEASE_DATE "20231226" #define MAIL_VERSION_NUMBER "3.9" #ifdef SNAPSHOT diff --git a/postfix/src/global/smtp_stream.c b/postfix/src/global/smtp_stream.c index b7209009d..dccf279a7 100644 --- a/postfix/src/global/smtp_stream.c +++ b/postfix/src/global/smtp_stream.c @@ -54,6 +54,7 @@ /* 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; @@ -133,16 +134,17 @@ /* 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 @@ -222,6 +224,7 @@ * 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 */ @@ -384,6 +387,8 @@ int smtp_get_noexcept(VSTRING *vp, VSTREAM *stream, ssize_t bound, int 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 @@ -428,7 +433,7 @@ int smtp_get_noexcept(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags 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); diff --git a/postfix/src/global/smtp_stream.h b/postfix/src/global/smtp_stream.h index 4bcd6b59b..1a894685c 100644 --- a/postfix/src/global/smtp_stream.h +++ b/postfix/src/global/smtp_stream.h @@ -32,7 +32,6 @@ #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 protocol error */ extern void smtp_stream_setup(VSTREAM *, int, int, int); extern void PRINTFLIKE(2, 3) smtp_printf(VSTREAM *, const char *,...); @@ -45,6 +44,7 @@ extern void smtp_fwrite(const char *, ssize_t len, VSTREAM *); 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); diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c index f40070cc4..f3ab0305d 100644 --- a/postfix/src/smtpd/smtpd.c +++ b/postfix/src/smtpd/smtpd.c @@ -1620,7 +1620,6 @@ static void tls_reset(SMTPD_STATE *); #define REASON_TIMEOUT "timeout" #define REASON_LOST_CONNECTION "lost connection" #define REASON_ERROR_LIMIT "too many errors" -#define REASON_BARE_LF "bare received" #ifdef USE_TLS @@ -3625,6 +3624,8 @@ static void receive_data_message(SMTPD_STATE *state, 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) { @@ -3792,6 +3793,12 @@ static int common_post_message_handling(SMTPD_STATE *state) 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 received from %s", state->namaddr); + smtpd_chat_reply(state, "521 5.5.2 %s Error: bare received", + var_myhostname); } else if (why && IS_SMTP_REJECT(STR(why))) { state->error_mask |= MAIL_ERROR_POLICY; smtpd_chat_reply(state, "%s", STR(why)); @@ -4079,7 +4086,6 @@ static int bdat_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) */ done = 0; do { - int payload_err; /* * Do not skip the smtp_fread_buf() call if read_len == 0. We still @@ -4093,10 +4099,6 @@ static int bdat_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) 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 @@ -4104,9 +4106,9 @@ static int bdat_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) */ 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)) { @@ -4121,6 +4123,8 @@ static int bdat_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) /* 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) { @@ -5597,13 +5601,6 @@ static void smtpd_proto(SMTPD_STATE *state) 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 received", - var_myhostname); - break; - case 0: /* @@ -5823,6 +5820,15 @@ static void smtpd_proto(SMTPD_STATE *state) } watchdog_pat(); smtpd_chat_query(state); + if (smtp_seen_bare_lf) { + msg_info("disconnect: bare received from %s", + state->namaddr); + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, + "521 5.5.2 %s Error: bare received", + var_myhostname); + break; + } /* Safety: protect internal interfaces against malformed UTF-8. */ if (var_smtputf8_enable && valid_utf8_stringz(STR(state->buffer)) == 0) {