* 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};
/*************************************************
{
uschar *qnewsender;
if (trusted_caller) return TRUE;
-if (newsender == NULL || untrusted_set_sender == NULL) return FALSE;
-qnewsender = (Ustrchr(newsender, '@') != NULL)?
- newsender : string_sprintf("%s@%s", newsender, qualify_domain_sender);
-return
- match_address_list(qnewsender, TRUE, TRUE, CUSS &untrusted_set_sender, NULL, -1,
- 0, NULL) == OK;
+if (!newsender || !untrusted_set_sender) return FALSE;
+qnewsender = Ustrchr(newsender, '@')
+ ? newsender : string_sprintf("%s@%s", newsender, qualify_domain_sender);
+return match_address_list_basic(qnewsender, CUSS &untrusted_set_sender, 0) == OK;
}
{
message_size++;
if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
- (void) cutthrough_put_nl();
+ cutthrough_data_put_nl();
if (ch != '\r') ch_state = 1; else continue;
}
break;
if (ch == '.')
{
uschar c= ch;
- (void) cutthrough_puts(&c, 1);
+ cutthrough_data_puts(&c, 1);
}
ch_state = 1;
break;
message_size++;
body_linecount++;
if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
- (void) cutthrough_put_nl();
+ cutthrough_data_put_nl();
if (ch == '\r')
{
ch_state = 2;
if (message_size > thismessage_size_limit) return END_SIZE;
}
if(ch == '\n')
- (void) cutthrough_put_nl();
+ cutthrough_data_put_nl();
else
{
uschar c = ch;
- (void) cutthrough_puts(&c, 1);
+ cutthrough_data_puts(&c, 1);
}
}
/* Variant of the above read_message_data_smtp() specialised for RFC 3030
-CHUNKING. We assume that the incoming has proper CRLF, so only have to scan
-for and strip CR. On the downside there are more protocol reasons to stop.
+CHUNKING. Accept input lines separated by either CRLF or CR or LF and write
+LF-delimited spoolfile. Until we have wireformat spoolfiles, we need the
+body_linecount accounting for proper re-expansion for the wire, so use
+a cut-down version of the state-machine above; we don't need to do leading-dot
+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;
-int linelength = 0;
+int linelength = 0, ch;
+enum CH_STATE ch_state = LF_SEEN;
+BOOL fix_nl = FALSE;
-for (;;) switch (ch = bdat_getc(GETC_BUFFER_UNLIMITED))
+for(;;)
{
- case EOF: return END_EOF;
- case EOD: return END_DOT;
- case ERR: return END_PROTOCOL;
+ switch ((ch = (bdat_getc)(GETC_BUFFER_UNLIMITED)))
+ {
+ case EOF: return END_EOF;
+ 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;
+ }
- case '\r':
- body_linecount++;
- if (linelength > max_received_linelength)
- max_received_linelength = linelength;
- linelength = -1;
- break;
+ 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;
- case 0:
- body_zerocount++;
- /*FALLTHROUGH*/
- default:
- message_size++;
- linelength++;
- if (fout)
- {
- if (fputc(ch, fout) == EOF) return END_WERROR;
- if (message_size > thismessage_size_limit) return END_SIZE;
- }
-#ifdef notyet
- if(ch == '\n')
- (void) cutthrough_put_nl();
- else
- {
- uschar c = ch;
- (void) cutthrough_puts(&c, 1);
- }
-#endif
- break;
+ continue;
+ case '\0': body_zerocount++; break;
+ }
+ switch (ch_state)
+ {
+ case LF_SEEN: /* After LF or CRLF */
+ ch_state = MID_LINE;
+ /* fall through to handle as normal uschar. */
+
+ case MID_LINE: /* Mid-line state */
+ if (ch == '\n')
+ {
+ ch_state = LF_SEEN;
+ body_linecount++;
+ if (linelength > max_received_linelength)
+ max_received_linelength = linelength;
+ linelength = -1;
+ }
+ else if (ch == '\r')
+ {
+ ch_state = CR_SEEN;
+ if (fix_nl) bdat_ungetc('\n');
+ continue; /* don't write CR */
+ }
+ break;
+
+ case CR_SEEN: /* After (unwritten) CR */
+ body_linecount++;
+ if (linelength > max_received_linelength)
+ max_received_linelength = linelength;
+ linelength = -1;
+ if (ch == '\n')
+ ch_state = LF_SEEN;
+ else
+ {
+ message_size++;
+ if (fout && fputc('\n', fout) == EOF) return END_WERROR;
+ cutthrough_data_put_nl();
+ if (ch == '\r') continue; /* don't write CR */
+ ch_state = MID_LINE;
+ }
+ break;
+ }
+
+ /* Add the character to the spool file, unless skipping */
+
+ message_size++;
+ linelength++;
+ if (fout)
+ {
+ if (fputc(ch, fout) == EOF) return END_WERROR;
+ if (message_size > thismessage_size_limit) return END_SIZE;
+ }
+ if(ch == '\n')
+ cutthrough_data_put_nl();
+ else
+ {
+ uschar c = ch;
+ cutthrough_data_puts(&c, 1);
+ }
}
/*NOTREACHED*/
}
case ACL_WHERE_DKIM:
case ACL_WHERE_MIME:
case ACL_WHERE_DATA:
- if (cutthrough.fd >= 0 && (acl_removed_headers || acl_added_headers))
+ if ( cutthrough.fd >= 0 && cutthrough.delivery
+ && (acl_removed_headers || acl_added_headers))
{
log_write(0, LOG_MAIN|LOG_PANIC, "Header modification in data ACLs"
" will not take effect on cutthrough deliveries");
}
}
-if (acl_removed_headers != NULL)
+if (acl_removed_headers)
{
- 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)
+ for (h = header_list; h; h = h->next) if (h->type != htype_old)
{
const uschar * list = acl_removed_headers;
int sep = ':'; /* This is specified as a colon-separated list */
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);
+if (!acl_added_headers) return;
+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)
+for (h = acl_added_headers; h; h = next)
{
next = 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;
}
cutthrough delivery with the no-spool option. It shouldn't be possible
to set up the combination, but just in case kill any ongoing connection. */
if (extract_recip || !smtp_input)
- cancel_cutthrough_connection("not smtp input");
+ cancel_cutthrough_connection(TRUE, US"not smtp input");
/* Initialize the chain of headers by setting up a place-holder for Received:
header. Temporarily mark it as "old", i.e. not to be used. We keep header_last
}
}
+ /* Reject CHUNKING messages that do not CRLF their first header line */
+
+ if (!first_line_ended_crlf && chunking_state > CHUNKING_OFFERED)
+ {
+ log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
+ "Non-CRLF-terminated header, under CHUNKING: message abandoned",
+ sender_address,
+ sender_fullhost ? " H=" : "", sender_fullhost ? sender_fullhost : US"",
+ sender_ident ? " U=" : "", sender_ident ? sender_ident : US"");
+ smtp_printf("552 Message header not CRLF terminated\r\n");
+ bdat_flush_data();
+ smtp_reply = US"";
+ goto TIDYUP; /* Skip to end of function */
+ }
+
/* The line has been handled. If we have hit EOF, break out of the loop,
indicating no pending data line. */
DEBUG(D_receive)
{
debug_printf(">>Headers received:\n");
- for (h = header_list->next; h != NULL; h = h->next)
+ for (h = header_list->next; h; h = h->next)
debug_printf("%s", h->text);
debug_printf("\n");
}
/* Scan the headers to identify them. Some are merely marked for later
processing; some are dealt with here. */
-for (h = header_list->next; h != NULL; h = h->next)
+for (h = header_list->next; h; h = h->next)
{
BOOL is_resent = strncmpic(h->text, US"resent-", 7) == 0;
if (is_resent) contains_resent_headers = TRUE;
/* Now scan the headers */
- for (h = header_list->next; h != NULL; h = h->next)
+ for (h = header_list->next; h; h = h->next)
{
if ((h->type == htype_to || h->type == htype_cc || h->type == htype_bcc) &&
(!contains_resent_headers || strncmpic(h->text, US"resent-", 7) == 0))
documented as happening *after* recipient addresses are taken from the headers
by the -t command line option. An added Sender: gets rewritten here. */
-for (h = header_list->next; h != NULL; h = h->next)
+for (h = header_list->next; h; h = h->next)
{
header_line *newh = rewrite_header(h, NULL, NULL, global_rewrite_rules,
rewrite_existflags, TRUE);
- if (newh != NULL) h = newh;
+ if (newh) h = newh;
}
Could we do onward CHUNKING given inbound CHUNKING?
*/
if (chunking_state > CHUNKING_OFFERED)
- cancel_cutthrough_connection("chunking active");
+ cancel_cutthrough_connection(FALSE, US"chunking active");
/* Cutthrough delivery:
We have to create the Received header now rather than at the end of reception,
so the timestamp behaviour is a change to the normal case.
XXX Ensure this gets documented XXX.
Having created it, send the headers to the destination. */
-if (cutthrough.fd >= 0)
+
+if (cutthrough.fd >= 0 && cutthrough.delivery)
{
if (received_count > received_headers_max)
{
- cancel_cutthrough_connection("too many headers");
+ cancel_cutthrough_connection(TRUE, US"too many headers");
if (smtp_input) receive_swallow_smtp(); /* Swallow incoming SMTP */
log_write(0, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
"Too many \"Received\" headers",
sender_address,
- (sender_fullhost == NULL)? "" : " H=",
- (sender_fullhost == NULL)? US"" : sender_fullhost,
- (sender_ident == NULL)? "" : " U=",
- (sender_ident == NULL)? US"" : sender_ident);
+ sender_fullhost ? "H=" : "", sender_fullhost ? sender_fullhost : US"",
+ sender_ident ? "U=" : "", sender_ident ? sender_ident : US"");
message_id[0] = 0; /* Indicate no message accepted */
smtp_reply = US"550 Too many \"Received\" headers - suspected mail loop";
goto TIDYUP; /* Skip to end of function */
if (smtp_input)
{
Uunlink(spool_name); /* Lose data file when closed */
- cancel_cutthrough_connection("sender closed connection");
+ cancel_cutthrough_connection(TRUE, US"sender closed connection");
message_id[0] = 0; /* Indicate no message accepted */
smtp_reply = handle_lost_connection(US"");
smtp_yield = FALSE;
case END_SIZE:
Uunlink(spool_name); /* Lose the data file when closed */
- cancel_cutthrough_connection("mail too big");
+ cancel_cutthrough_connection(TRUE, US"mail too big");
if (smtp_input) receive_swallow_smtp(); /* Swallow incoming SMTP */
log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected from <%s>%s%s%s%s: "
case END_PROTOCOL:
Uunlink(spool_name); /* Lose the data file when closed */
- cancel_cutthrough_connection("sender protocol error");
+ cancel_cutthrough_connection(TRUE, US"sender protocol error");
smtp_reply = US""; /* Response already sent */
message_id[0] = 0; /* Indicate no message accepted */
goto TIDYUP; /* Skip to end of function */
log_write(0, LOG_MAIN, "Message abandoned: %s", msg);
Uunlink(spool_name); /* Lose the data file */
- cancel_cutthrough_connection("error writing spoolfile");
+ cancel_cutthrough_connection(TRUE, US"error writing spoolfile");
if (smtp_input)
{
DEBUG(D_receive)
debug_printf("acl_smtp_dkim: acl_check returned %d on %s, "
"skipping remaining items\n", rc, item);
- cancel_cutthrough_connection("dkim acl not ok");
+ cancel_cutthrough_connection(TRUE, US"dkim acl not ok");
break;
}
}
{
recipients_count = 0;
blackholed_by = US"DATA ACL";
- if (log_msg != NULL)
+ if (log_msg)
blackhole_log_msg = string_sprintf(": %s", log_msg);
- cancel_cutthrough_connection("data acl discard");
+ cancel_cutthrough_connection(TRUE, US"data acl discard");
}
else if (rc != OK)
{
Uunlink(spool_name);
- cancel_cutthrough_connection("data acl not ok");
+ cancel_cutthrough_connection(TRUE, US"data acl not ok");
#ifdef WITH_CONTENT_SCAN
unspool_mbox();
#endif
XXX We do not handle queue-only, freezing, or blackholes.
*/
-if(cutthrough.fd >= 0)
+if(cutthrough.fd >= 0 && cutthrough.delivery)
{
- uschar * msg= cutthrough_finaldot(); /* Ask the target system to accept the message */
+ uschar * msg = cutthrough_finaldot(); /* Ask the target system to accept the message */
/* Logging was done in finaldot() */
switch(msg[0])
{
Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D"));
Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
- message_id[0] = 0; /* Prevent a delivery from starting */
break;
case TMP_REJ:
Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
}
- message_id[0] = 0; /* Prevent a delivery from starting */
default:
break;
}
- cutthrough.delivery = FALSE;
- cutthrough.defer_pass = FALSE;
+ if (cutthrough_done != NOT_TRIED)
+ {
+ message_id[0] = 0; /* Prevent a delivery from starting */
+ cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
+ cutthrough.defer_pass = FALSE;
+ }
}
/* For batched SMTP, generate an error message on failure, and do