* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
/* See the file NOTICE for conditions of use and distribution. */
/* Code for receiving a message and setting up spool files. */
static int data_fd = -1;
static uschar *spool_name = US"";
+enum CH_STATE {LF_SEEN, MID_LINE, CR_SEEN};
/*************************************************
detection and unstuffing.
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;
+ must be open for both writing and reading.
Returns: One of the END_xxx values indicating why it stopped reading
*/
static int
read_message_bdat_smtp(FILE *fout)
{
-int ch_state = 0, linelength = 0, ch;
+int linelength = 0, ch;
+enum CH_STATE ch_state = LF_SEEN;
+BOOL fix_nl = FALSE;
for(;;)
{
switch ((ch = (bdat_getc)(GETC_BUFFER_UNLIMITED)))
{
case EOF: return END_EOF;
- case EOD: return END_DOT; /* normal exit */
case ERR: return END_PROTOCOL;
+ case EOD:
+ /* Nothing to get from the sender anymore. We check the last
+ character written to the spool.
+
+ RFC 3030 states, that BDAT chunks are normal text, terminated by CRLF.
+ If we would be strict, we would refuse such broken messages.
+ But we are liberal, so we fix it. It would be easy just to append
+ the "\n" to the spool.
+
+ But there are some more things (line counting, message size calculation and such),
+ that would need to be duplicated here. So we simply do some ungetc
+ trickery.
+ */
+ if (fout)
+ {
+ if (fseek(fout, -1, SEEK_CUR) < 0) return END_PROTOCOL;
+ if (fgetc(fout) == '\n') return END_DOT;
+ }
+
+ if (linelength == -1) /* \r already seen (see below) */
+ {
+ DEBUG(D_receive) debug_printf("Add missing LF\n");
+ bdat_ungetc('\n');
+ continue;
+ }
+ DEBUG(D_receive) debug_printf("Add missing CRLF\n");
+ bdat_ungetc('\r'); /* not even \r was seen */
+ fix_nl = TRUE;
+
+ continue;
case '\0': body_zerocount++; break;
}
switch (ch_state)
{
- case 0: /* After LF or CRLF */
- ch_state = 1;
+ case LF_SEEN: /* After LF or CRLF */
+ ch_state = MID_LINE;
/* fall through to handle as normal uschar. */
- case 1: /* Mid-line state */
+ case MID_LINE: /* Mid-line state */
if (ch == '\n')
{
- ch_state = 0;
+ ch_state = LF_SEEN;
body_linecount++;
if (linelength > max_received_linelength)
max_received_linelength = linelength;
}
else if (ch == '\r')
{
- ch_state = 2;
+ ch_state = CR_SEEN;
+ if (fix_nl) bdat_ungetc('\n');
continue; /* don't write CR */
}
break;
- case 2: /* After (unwritten) CR */
+ case CR_SEEN: /* After (unwritten) CR */
body_linecount++;
if (linelength > max_received_linelength)
max_received_linelength = linelength;
linelength = -1;
if (ch == '\n')
- ch_state = 0;
+ ch_state = LF_SEEN;
else
{
message_size++;
- if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
+ if (fout && fputc('\n', fout) == EOF) return END_WERROR;
(void) cutthrough_put_nl();
if (ch == '\r') continue; /* don't write CR */
- ch_state = 1;
+ ch_state = MID_LINE;
}
break;
}
if (acl_removed_headers != NULL)
{
- DEBUG(D_receive|D_acl) debug_printf(">>Headers removed by %s ACL:\n", acl_name);
+ DEBUG(D_receive|D_acl) debug_printf_indent(">>Headers removed by %s ACL:\n", acl_name);
for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old)
{
if (header_testname(h, s, Ustrlen(s), FALSE))
{
h->type = htype_old;
- DEBUG(D_receive|D_acl) debug_printf(" %s", h->text);
+ DEBUG(D_receive|D_acl) debug_printf_indent(" %s", h->text);
}
}
acl_removed_headers = NULL;
- DEBUG(D_receive|D_acl) debug_printf(">>\n");
+ DEBUG(D_receive|D_acl) debug_printf_indent(">>\n");
}
if (acl_added_headers == NULL) return;
-DEBUG(D_receive|D_acl) debug_printf(">>Headers added by %s ACL:\n", acl_name);
+DEBUG(D_receive|D_acl) debug_printf_indent(">>Headers added by %s ACL:\n", acl_name);
for (h = acl_added_headers; h != NULL; h = next)
{
case htype_add_top:
h->next = header_list;
header_list = h;
- DEBUG(D_receive|D_acl) debug_printf(" (at top)");
+ DEBUG(D_receive|D_acl) debug_printf_indent(" (at top)");
break;
case htype_add_rec:
}
h->next = last_received->next;
last_received->next = h;
- DEBUG(D_receive|D_acl) debug_printf(" (after Received:)");
+ DEBUG(D_receive|D_acl) debug_printf_indent(" (after Received:)");
break;
case htype_add_rfc:
of all headers. Our current header must follow it. */
h->next = last_received->next;
last_received->next = h;
- DEBUG(D_receive|D_acl) debug_printf(" (before any non-Received: or Resent-*: header)");
+ DEBUG(D_receive|D_acl) debug_printf_indent(" (before any non-Received: or Resent-*: header)");
break;
default:
h->type = header_checkname(h, FALSE);
if (h->type >= 'a') h->type = htype_other;
- DEBUG(D_receive|D_acl) debug_printf(" %s", header_last->text);
+ DEBUG(D_receive|D_acl) debug_printf_indent(" %s", header_last->text);
}
acl_added_headers = NULL;
-DEBUG(D_receive|D_acl) debug_printf(">>\n");
+DEBUG(D_receive|D_acl) debug_printf_indent(">>\n");
}
{
(void) string_format(rfc822_file_path, sizeof(rfc822_file_path),
"%s/scan/%s/%s", spool_directory, message_id, entry->d_name);
- debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n",
+ DEBUG(D_receive) debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n",
rfc822_file_path);
break;
}