well, so that there are no CRs in spooled messages. However, the message
terminating dot is not recognized between two bare CRs.
+Dec 2023: getting a site to send a body including an "LF . LF" sequence
+followed by SMTP commands is a possible "smtp smuggling" attack. If
+the first (header) line for the message has a proper CRLF then enforce
+that for the body: convert bare LF to a space.
+
Arguments:
- fout a FILE to which to write the message; NULL if skipping
+ fout a FILE to which to write the message; NULL if skipping
+ strict_crlf require full CRLF sequence as a line ending
Returns: One of the END_xxx values indicating why it stopped reading
*/
static int
-read_message_data_smtp(FILE * fout)
+read_message_data_smtp(FILE * fout, BOOL strict_crlf)
{
enum { s_linestart, s_normal, s_had_cr, s_had_nl_dot, s_had_dot_cr } ch_state =
s_linestart;
ch_state = s_had_cr;
continue; /* Don't write the CR */
}
- if (ch == '\n') /* Bare NL ends line */
- {
- ch_state = s_linestart;
- body_linecount++;
- if (linelength > max_received_linelength)
- max_received_linelength = linelength;
- linelength = -1;
- }
+ if (ch == '\n') /* Bare LF at end of line */
+ if (strict_crlf)
+ ch = ' '; /* replace LF with space */
+ else
+ { /* treat as line ending */
+ ch_state = s_linestart;
+ body_linecount++;
+ if (linelength > max_received_linelength)
+ max_received_linelength = linelength;
+ linelength = -1;
+ }
break;
case s_had_cr: /* After (unwritten) CR */
case s_had_nl_dot: /* After [CR] LF . */
if (ch == '\n') /* [CR] LF . LF */
- return END_DOT;
- if (ch == '\r') /* [CR] LF . CR */
+ if (strict_crlf)
+ ch = ' '; /* replace LF with space */
+ else
+ return END_DOT;
+ else if (ch == '\r') /* [CR] LF . CR */
{
ch_state = s_had_dot_cr;
continue; /* Don't write the CR */
/* The dot was removed on reaching s_had_nl_dot. For a doubled dot, here,
reinstate it to cutthrough. The current ch, dot or not, is passed both to
cutthrough and to file below. */
- if (ch == '.')
+ else if (ch == '.')
{
uschar c = ch;
cutthrough_data_puts(&c, 1);
{
if (message_ended >= END_NOTENDED)
message_ended = chunking_state <= CHUNKING_OFFERED
- ? read_message_data_smtp(NULL)
+ ? read_message_data_smtp(NULL, FALSE)
: read_message_bdat_smtp_wire(NULL);
}
give_local_error(int errcode, uschar *text1, uschar *text2, int error_rc,
FILE *f, header_line *hptr)
{
+DEBUG(D_all) debug_printf("%s%s\n", text2, text1);
+
if (error_handling == ERRORS_SENDER)
{
error_block eblock;
if ((h->type == htype_to || h->type == htype_cc || h->type == htype_bcc) &&
(!contains_resent_headers || strncmpic(h->text, US"resent-", 7) == 0))
{
- uschar *s = Ustrchr(h->text, ':') + 1;
+ uschar * s = Ustrchr(h->text, ':') + 1;
while (isspace(*s)) s++;
f.parse_allow_group = TRUE; /* Allow address group syntax */
- while (*s != 0)
+ while (*s)
{
uschar *ss = parse_find_address_end(s, FALSE);
uschar *recipient, *errmess, *pp;
/* Check on maximum */
- if (recipients_max > 0 && ++rcount > recipients_max)
+ if (recipients_max_expanded > 0 && ++rcount > recipients_max_expanded)
give_local_error(ERRMESS_TOOMANYRECIP, US"too many recipients",
US"message rejected: ", error_rc, stdin, NULL);
/* Does not return */
it has already been rewritten as part of verification for SMTP input. */
DEBUG(D_rewrite)
- { debug_printf("global rewrite rules\n"); acl_level++; }
+ { debug_printf("rewrite rules on sender address\n"); acl_level++; }
if (global_rewrite_rules && !sender_address_unrewritten && *sender_address)
{
/* deconst ok as src was not const */
by the -t command line option. An added Sender: gets rewritten here. */
DEBUG(D_rewrite)
- { debug_printf("rewrite headers\n"); acl_level++; }
+ { debug_printf("qualify and rewrite headers\n"); acl_level++; }
for (header_line * h = header_list->next, * newh; h; h = h->next)
if ((newh = rewrite_header(h, NULL, NULL, global_rewrite_rules,
rewrite_existflags, TRUE)))
DEBUG(D_receive)
{
debug_printf(">>Headers after rewriting and local additions:\n");
+ acl_level++;
for (header_line * h = header_list->next; h; h = h->next)
- debug_printf("%c %s", h->type, h->text);
+ debug_printf_indent("%c %s", h->type, h->text);
debug_printf("\n");
+ acl_level--;
}
/* The headers are now complete in store. If we are running in filter
if (smtp_input)
{
message_ended = chunking_state <= CHUNKING_OFFERED
- ? read_message_data_smtp(spool_data_file)
+ ? read_message_data_smtp(spool_data_file, first_line_ended_crlf)
: spool_wireformat
? read_message_bdat_smtp_wire(spool_data_file)
: read_message_bdat_smtp(spool_data_file);
receive_messagecount++;
-if (fflush(spool_data_file))
+if ( fflush(spool_data_file)
+#if _POSIX_C_SOURCE >= 199309L || _XOPEN_SOURCE >= 500
+# ifdef ENABLE_DISABLE_FSYNC
+ || !disable_fsync && fdatasync(data_fd)
+# else
+ || fdatasync(data_fd)
+# endif
+#endif
+ )
{
errmsg = string_sprintf("Spool write error: %s", strerror(errno));
log_write(0, LOG_MAIN, "%s\n", errmsg);