X-Git-Url: https://git.exim.org/users/jgh/exim.git/blobdiff_plain/ddf1b11a732e293cd242c80bc63d459dda595bf4..ba86e143c7aeb0d70ea4c9d73a617a98f06f6baa:/src/src/receive.c diff --git a/src/src/receive.c b/src/src/receive.c index e53587619..31402925d 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -2,7 +2,7 @@ * 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. */ @@ -25,6 +25,7 @@ static FILE *data_file = NULL; static int data_fd = -1; static uschar *spool_name = US""; +enum CH_STATE {LF_SEEN, MID_LINE, CR_SEEN}; /************************************************* @@ -37,7 +38,7 @@ the file. (When SMTP input is occurring, different functions are used by changing the pointer variables.) */ int -stdin_getc(void) +stdin_getc(unsigned lim) { return getc(stdin); } @@ -83,12 +84,10 @@ receive_check_set_sender(uschar *newsender) { 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; } @@ -191,7 +190,7 @@ if (STATVFS(CS path, &statbuf) != 0) log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat " "%s directory %s: %s", name, path, strerror(errno)); smtp_closedown(US"spool or log directory problem"); - exim_exit(EXIT_FAILURE); + exim_exit(EXIT_FAILURE, NULL); } *inodeptr = (statbuf.F_FILES > 0)? statbuf.F_FAVAIL : -1; @@ -344,7 +343,7 @@ if (!already_bombing_out) /* Exit from the program (non-BSMTP cases) */ -exim_exit(EXIT_FAILURE); +exim_exit(EXIT_FAILURE, NULL); } @@ -626,7 +625,7 @@ if (!dot_ends) { register int last_ch = '\n'; - for (; (ch = (receive_getc)()) != EOF; last_ch = ch) + for (; (ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF; last_ch = ch) { if (ch == 0) body_zerocount++; if (last_ch == '\r' && ch != '\n') @@ -668,7 +667,7 @@ if (!dot_ends) ch_state = 1; -while ((ch = (receive_getc)()) != EOF) +while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF) { if (ch == 0) body_zerocount++; switch (ch_state) @@ -786,7 +785,7 @@ int ch_state = 0; int ch; int linelength = 0; -while ((ch = (receive_getc)()) != EOF) +while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF) { if (ch == 0) body_zerocount++; switch (ch_state) @@ -830,7 +829,7 @@ while ((ch = (receive_getc)()) != EOF) { 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; @@ -849,7 +848,7 @@ while ((ch = (receive_getc)()) != EOF) if (ch == '.') { uschar c= ch; - (void) cutthrough_puts(&c, 1); + cutthrough_data_puts(&c, 1); } ch_state = 1; break; @@ -859,7 +858,7 @@ while ((ch = (receive_getc)()) != EOF) 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; @@ -880,11 +879,11 @@ while ((ch = (receive_getc)()) != EOF) 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); } } @@ -898,11 +897,15 @@ return END_EOF; /* 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 */ @@ -910,43 +913,147 @@ Returns: One of the END_xxx values indicating why it stopped reading static int read_message_bdat_smtp(FILE *fout) { +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 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 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*/ +} + +static int +read_message_bdat_smtp_wire(FILE *fout) +{ int ch; -int linelength = 0; -for (;;) switch (ch = bdat_getc()) +/* Remember that this message uses wireformat. */ + +DEBUG(D_receive) debug_printf("CHUNKING: writing spoolfile in wire format\n"); +spool_file_wireformat = TRUE; + +for (;;) { - case EOF: return END_EOF; - case EOD: return END_DOT; - case ERR: return END_PROTOCOL; + if (chunking_data_left > 0) + { + unsigned len = MAX(chunking_data_left, thismessage_size_limit - message_size + 1); + uschar * buf = bdat_getbuf(&len); - case '\r': - body_linecount++; - if (linelength > max_received_linelength) - max_received_linelength = linelength; - linelength = -1; - break; + message_size += len; + if (fout && fwrite(buf, len, 1, fout) != 1) return END_WERROR; + } + else switch (ch = bdat_getc(GETC_BUFFER_UNLIMITED)) + { + case EOF: return END_EOF; + case EOD: return END_DOT; + case ERR: return END_PROTOCOL; - 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; + default: + message_size++; + /*XXX not done: + linelength + max_received_linelength + body_linecount + body_zerocount + */ + if (fout && fputc(ch, fout) == EOF) return END_WERROR; + break; + } + if (message_size > thismessage_size_limit) return END_SIZE; } /*NOTREACHED*/ } @@ -1035,7 +1142,7 @@ if (error_handling == ERRORS_SENDER) else fprintf(stderr, "exim: %s%s\n", text2, text1); /* Sic */ (void)fclose(f); -exim_exit(error_rc); +exim_exit(error_rc, US""); } @@ -1073,7 +1180,8 @@ switch(where) 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"); @@ -1081,11 +1189,11 @@ switch(where) } } -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 */ @@ -1096,17 +1204,17 @@ if (acl_removed_headers != NULL) 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; @@ -1115,7 +1223,7 @@ 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: @@ -1130,7 +1238,7 @@ for (h = acl_added_headers; h != NULL; h = next) } 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: @@ -1145,7 +1253,7 @@ for (h = acl_added_headers; h != NULL; h = next) 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: @@ -1165,11 +1273,11 @@ for (h = acl_added_headers; h != NULL; h = next) 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"); } @@ -1183,31 +1291,34 @@ the calling host to a string that is being built dynamically. Arguments: s the dynamic string - sizeptr points to the size variable - ptrptr points to the pointer variable Returns: the extended string */ -static uschar * -add_host_info_for_log(uschar * s, int * sizeptr, int * ptrptr) +static gstring * +add_host_info_for_log(gstring * g) { if (sender_fullhost) { if (LOGGING(dnssec) && sender_host_dnssec) /*XXX sender_helo_dnssec? */ - s = string_cat(s, sizeptr, ptrptr, US" DS"); - s = string_append(s, sizeptr, ptrptr, 2, US" H=", sender_fullhost); + g = string_catn(g, US" DS", 3); + g = string_append(g, 2, US" H=", sender_fullhost); if (LOGGING(incoming_interface) && interface_address != NULL) { - s = string_cat(s, sizeptr, ptrptr, + g = string_cat(g, string_sprintf(" I=[%s]:%d", interface_address, interface_port)); } } -if (sender_ident != NULL) - s = string_append(s, sizeptr, ptrptr, 2, US" U=", sender_ident); -if (received_protocol != NULL) - s = string_append(s, sizeptr, ptrptr, 2, US" P=", received_protocol); -return s; +if (tcp_in_fastopen && !tcp_in_fastopen_logged) + { + g = string_catn(g, US" TFO", 4); + tcp_in_fastopen_logged = TRUE; + } +if (sender_ident) + g = string_append(g, 2, US" U=", sender_ident); +if (received_protocol) + g = string_append(g, 2, US" P=", received_protocol); +return g; } @@ -1240,36 +1351,30 @@ unsigned long mbox_size; header_line *my_headerlist; uschar *user_msg, *log_msg; int mime_part_count_buffer = -1; +uschar * mbox_filename; int rc = OK; memset(CS rfc822_file_path,0,2048); /* check if it is a MIME message */ -my_headerlist = header_list; -while (my_headerlist != NULL) - { - /* skip deleted headers */ - if (my_headerlist->type == '*') - { - my_headerlist = my_headerlist->next; - continue; - } - if (strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0) + +for (my_headerlist = header_list; my_headerlist; my_headerlist = my_headerlist->next) + if ( my_headerlist->type != '*' /* skip deleted headers */ + && strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0 + ) { DEBUG(D_receive) debug_printf("Found Content-Type: header - executing acl_smtp_mime.\n"); goto DO_MIME_ACL; } - my_headerlist = my_headerlist->next; - } DEBUG(D_receive) debug_printf("No Content-Type: header - presumably not a MIME message.\n"); return TRUE; DO_MIME_ACL: + /* make sure the eml mbox file is spooled up */ -mbox_file = spool_mbox(&mbox_size, NULL); -if (mbox_file == NULL) { - /* error while spooling */ +if (!(mbox_file = spool_mbox(&mbox_size, NULL, &mbox_filename))) + { /* error while spooling */ log_write(0, LOG_MAIN|LOG_PANIC, "acl_smtp_mime: error while creating mbox spool file, message temporarily rejected."); Uunlink(spool_name); @@ -1281,7 +1386,7 @@ if (mbox_file == NULL) { message_id[0] = 0; /* Indicate no message accepted */ *smtp_reply_ptr = US""; /* Indicate reply already sent */ return FALSE; /* Indicate skip to end of receive function */ -}; + } mime_is_rfc822 = 0; @@ -1305,14 +1410,13 @@ if (Ustrlen(rfc822_file_path) > 0) /* check if we must check any message/rfc822 attachments */ if (rc == OK) { - uschar temp_path[1024]; + uschar * scandir; struct dirent * entry; DIR * tempdir; - (void) string_format(temp_path, sizeof(temp_path), "%s/scan/%s", - spool_directory, message_id); + scandir = string_copyn(mbox_filename, Ustrrchr(mbox_filename, '/') - mbox_filename); - tempdir = opendir(CS temp_path); + tempdir = opendir(CS scandir); for (;;) { if (!(entry = readdir(tempdir))) @@ -1320,8 +1424,8 @@ if (rc == OK) if (strncmpic(US entry->d_name, US"__rfc822_", 9) == 0) { (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", + "%s/%s", scandir, entry->d_name); + DEBUG(D_receive) debug_printf("RFC822 attachment detected: running MIME ACL for '%s'\n", rfc822_file_path); break; } @@ -1519,7 +1623,7 @@ int process_info_len = Ustrlen(process_info); int error_rc = (error_handling == ERRORS_SENDER)? errors_sender_rc : EXIT_FAILURE; int header_size = 256; -int start, end, domain, size, sptr; +int start, end, domain; int id_resolution; int had_zero = 0; int prevlines_length = 0; @@ -1544,7 +1648,8 @@ error_block *bad_addresses = NULL; uschar *frozen_by = NULL; uschar *queued_by = NULL; -uschar *errmsg, *s; +uschar *errmsg; +gstring * g; struct stat statbuf; /* Final message to give to SMTP caller, and messages from ACLs */ @@ -1586,7 +1691,7 @@ search_tidyup(); 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 @@ -1642,9 +1747,9 @@ message id creation below. */ /* For other uses of the received time we can operate with granularity of one second, and for that we use the global variable received_time. This is for -things like ultimate message timeouts. */ +things like ultimate message timeouts.XXX */ -received_time = message_id_tv.tv_sec; +received_time = message_id_tv; /* If SMTP input, set the special handler for timeouts. The alarm() calls happen in the smtp_getc() function when it refills its buffer. */ @@ -1682,7 +1787,7 @@ next->text. */ for (;;) { - int ch = (receive_getc)(); + int ch = (receive_getc)(GETC_BUFFER_UNLIMITED); /* If we hit EOF on a SMTP connection, it's an error, since incoming SMTP must have a correct "." terminator. */ @@ -1761,10 +1866,10 @@ for (;;) if (ptr == 0 && ch == '.' && (smtp_input || dot_ends)) { - ch = (receive_getc)(); + ch = (receive_getc)(GETC_BUFFER_UNLIMITED); if (ch == '\r') { - ch = (receive_getc)(); + ch = (receive_getc)(GETC_BUFFER_UNLIMITED); if (ch != '\n') { receive_ungetc(ch); @@ -1795,7 +1900,7 @@ for (;;) if (ch == '\r') { - ch = (receive_getc)(); + ch = (receive_getc)(GETC_BUFFER_UNLIMITED); if (ch == '\n') { if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE; @@ -1890,7 +1995,7 @@ for (;;) if (ch != EOF) { - int nextch = (receive_getc)(); + int nextch = (receive_getc)(GETC_BUFFER_UNLIMITED); if (nextch == ' ' || nextch == '\t') { next->text[ptr++] = nextch; @@ -2084,6 +2189,21 @@ for (;;) } } + /* 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", FALSE); + 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. */ @@ -2108,7 +2228,7 @@ normal case). */ 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"); } @@ -2135,7 +2255,7 @@ if (filter_test != FTEST_NONE && header_list->next == NULL) /* 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; @@ -2351,7 +2471,7 @@ if (extract_recip) /* 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)) @@ -2500,8 +2620,9 @@ letter and it is not used internally. NOTE: If ever the format of message ids is changed, the regular expression for checking that a string is in this format must be updated in a corresponding way. It appears in the initializing code in exim.c. The macro MESSAGE_ID_LENGTH -must also be changed to reflect the correct string length. Then, of course, -other programs that rely on the message id format will need updating too. */ +must also be changed to reflect the correct string length. The queue-sort code +needs to know the layout. Then, of course, other programs that rely on the +message id format will need updating too. */ Ustrncpy(message_id, string_base62((long int)(message_id_tv.tv_sec)), 6); message_id[6] = '-'; @@ -2845,11 +2966,11 @@ We start at the second header, skipping our own Received:. This rewriting is 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; } @@ -2906,26 +3027,24 @@ inbound is, but inbound chunking ought to be ok with outbound plain. 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 */ @@ -3005,9 +3124,11 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT) { if (smtp_input) { - message_ended = chunking_state > CHUNKING_OFFERED - ? read_message_bdat_smtp(data_file) - : read_message_data_smtp(data_file); + message_ended = chunking_state <= CHUNKING_OFFERED + ? read_message_data_smtp(data_file) + : spool_wireformat + ? read_message_bdat_smtp_wire(data_file) + : read_message_bdat_smtp(data_file); receive_linecount++; /* The terminating "." line */ } else message_ended = read_message_data(data_file); @@ -3023,7 +3144,7 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT) 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; @@ -3036,7 +3157,7 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT) 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: " @@ -3069,7 +3190,7 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT) 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 */ @@ -3102,7 +3223,7 @@ if (fflush(data_file) == EOF || ferror(data_file) || 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) { @@ -3203,7 +3324,7 @@ if (extract_recip && (bad_addresses != NULL || recipients_count == 0)) { Uunlink(spool_name); (void)fclose(data_file); - exim_exit(error_rc); + exim_exit(error_rc, US"receiving"); } } @@ -3286,9 +3407,8 @@ else int sep = 0; const uschar *ptr = dkim_verify_signers_expanded; uschar *item = NULL; - uschar *seen_items = NULL; - int seen_items_size = 0; - int seen_items_offset = 0; + gstring * seen_items = NULL; + /* Default to OK when no items are present */ rc = OK; while ((item = string_nextinlist(&ptr, &sep, NULL, 0))) @@ -3300,8 +3420,8 @@ else no matter how often it appears in the expanded list. */ if (seen_items) { - uschar *seen_item = NULL; - const uschar *seen_items_list = seen_items; + uschar *seen_item; + const uschar *seen_items_list = string_from_gstring(seen_items); BOOL seen_this_item = FALSE; while ((seen_item = string_nextinlist(&seen_items_list, &sep, @@ -3320,13 +3440,10 @@ else continue; } - seen_items = string_append(seen_items, &seen_items_size, - &seen_items_offset, 1, ":"); + seen_items = string_cat(seen_items, ":"); } - seen_items = string_append(seen_items, &seen_items_size, - &seen_items_offset, 1, item); - seen_items[seen_items_offset] = '\0'; + seen_items = string_cat(seen_items, item); DEBUG(D_receive) debug_printf("calling acl_smtp_dkim for dkim_cur_signer=%s\n", @@ -3341,7 +3458,7 @@ else 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; } } @@ -3385,7 +3502,7 @@ else int all_pass = OK; int all_fail = FAIL; - smtp_printf("353 PRDR content analysis beginning\r\n"); + smtp_printf("353 PRDR content analysis beginning\r\n", TRUE); /* Loop through recipients, responses must be in same order received */ for (c = 0; recipients_count > c; c++) { @@ -3460,14 +3577,14 @@ else { 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 @@ -3561,6 +3678,7 @@ dcc_ok = 0; version supplied with Exim always accepts, but this is a hook for sysadmins to supply their own checking code. The local_scan() function is run even when all the recipients have been discarded. */ +/*XXS could we avoid this for the standard case, given that few people will use it? */ lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET); @@ -3650,10 +3768,8 @@ multiline SMTP responses. */ else { uschar *istemp = US""; - uschar *s = NULL; uschar *smtp_code; - int size = 0; - int sptr = 0; + gstring * g; errmsg = local_scan_data; @@ -3686,13 +3802,12 @@ else break; } - s = string_append(s, &size, &sptr, 2, US"F=", - (sender_address[0] == 0)? US"<>" : sender_address); - s = add_host_info_for_log(s, &size, &sptr); - s[sptr] = 0; + g = string_append(g, 2, US"F=", + sender_address[0] == 0 ? US"<>" : sender_address); + g = add_host_info_for_log(g); log_write(0, LOG_MAIN|LOG_REJECT, "%s %srejected by local_scan(): %.256s", - s, istemp, string_printing(errmsg)); + string_from_gstring(g), istemp, string_printing(errmsg)); if (smtp_input) { @@ -3738,7 +3853,7 @@ if (bmi_run == 1) } #endif -/* Update the timstamp in our Received: header to account for any time taken by +/* Update the timestamp in our Received: header to account for any time taken by an ACL or by local_scan(). The new time is the time that all reception processing is complete. */ @@ -3823,61 +3938,56 @@ string as required. Since we commonly want to add two items at a time, use a macro to simplify the coding. We log the arrival of a new message while the file is still locked, just in case the machine is *really* fast, and delivers it first! Include any message id that is in the message - since the syntax of a -message id is actually an addr-spec, we can use the parse routine to canonicize +message id is actually an addr-spec, we can use the parse routine to canonicalize it. */ -size = 256; -sptr = 0; -s = store_get(size); +g = string_get(256); -s = string_append(s, &size, &sptr, 2, +g = string_append(g, 2, fake_response == FAIL ? US"(= " : US"<= ", sender_address[0] == 0 ? US"<>" : sender_address); if (message_reference) - s = string_append(s, &size, &sptr, 2, US" R=", message_reference); + g = string_append(g, 2, US" R=", message_reference); -s = add_host_info_for_log(s, &size, &sptr); +g = add_host_info_for_log(g); #ifdef SUPPORT_TLS if (LOGGING(tls_cipher) && tls_in.cipher) - s = string_append(s, &size, &sptr, 2, US" X=", tls_in.cipher); + g = string_append(g, 2, US" X=", tls_in.cipher); if (LOGGING(tls_certificate_verified) && tls_in.cipher) - s = string_append(s, &size, &sptr, 2, US" CV=", - tls_in.certificate_verified ? "yes":"no"); + g = string_append(g, 2, US" CV=", tls_in.certificate_verified ? "yes":"no"); if (LOGGING(tls_peerdn) && tls_in.peerdn) - s = string_append(s, &size, &sptr, 3, US" DN=\"", - string_printing(tls_in.peerdn), US"\""); + g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\""); if (LOGGING(tls_sni) && tls_in.sni) - s = string_append(s, &size, &sptr, 3, US" SNI=\"", - string_printing(tls_in.sni), US"\""); + g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\""); #endif if (sender_host_authenticated) { - s = string_append(s, &size, &sptr, 2, US" A=", sender_host_authenticated); + g = string_append(g, 2, US" A=", sender_host_authenticated); if (authenticated_id) { - s = string_append(s, &size, &sptr, 2, US":", authenticated_id); + g = string_append(g, 2, US":", authenticated_id); if (LOGGING(smtp_mailauth) && authenticated_sender) - s = string_append(s, &size, &sptr, 2, US":", authenticated_sender); + g = string_append(g, 2, US":", authenticated_sender); } } #ifndef DISABLE_PRDR if (prdr_requested) - s = string_catn(s, &size, &sptr, US" PRDR", 5); + g = string_catn(g, US" PRDR", 5); #endif #ifdef SUPPORT_PROXY if (proxy_session && LOGGING(proxy)) - s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_local_address); + g = string_append(g, 2, US" PRX=", proxy_local_address); #endif if (chunking_state > CHUNKING_OFFERED) - s = string_catn(s, &size, &sptr, US" K", 2); + g = string_catn(g, US" K", 2); sprintf(CS big_buffer, "%d", msg_size); -s = string_append(s, &size, &sptr, 2, US" S=", big_buffer); +g = string_append(g, 2, US" S=", big_buffer); /* log 8BITMIME mode announced in MAIL_FROM 0 ... no BODY= used @@ -3886,11 +3996,11 @@ s = string_append(s, &size, &sptr, 2, US" S=", big_buffer); if (LOGGING(8bitmime)) { sprintf(CS big_buffer, "%d", body_8bitmime); - s = string_append(s, &size, &sptr, 2, US" M8S=", big_buffer); + g = string_append(g, 2, US" M8S=", big_buffer); } if (*queue_name) - s = string_append(s, &size, &sptr, 2, US" Q=", queue_name); + g = string_append(g, 2, US" Q=", queue_name); /* If an addr-spec in a message-id contains a quoted string, it can contain any characters except " \ and CR and so in particular it can contain NL! @@ -3906,7 +4016,7 @@ if (msgid_header) &errmsg, &start, &end, &domain, FALSE); allow_domain_literals = save_allow_domain_literals; if (old_id != NULL) - s = string_append(s, &size, &sptr, 2, US" id=", string_printing(old_id)); + g = string_append(g, 2, US" id=", string_printing(old_id)); } /* If subject logging is turned on, create suitable printing-character @@ -3929,20 +4039,20 @@ if (LOGGING(subject) && subject_header != NULL) } *p++ = '\"'; *p = 0; - s = string_append(s, &size, &sptr, 2, US" T=", string_printing(big_buffer)); + g = string_append(g, 2, US" T=", string_printing(big_buffer)); } /* Terminate the string: string_cat() and string_append() leave room, but do not put the zero in. */ -s[sptr] = 0; +(void) string_from_gstring(g); /* Create a message log file if message logs are being used and this message is not blackholed. Write the reception stuff to it. We used to leave message log creation until the first delivery, but this has proved confusing for some people. */ -if (message_logs && blackholed_by == NULL) +if (message_logs && !blackholed_by) { int fd; @@ -3959,11 +4069,8 @@ if (message_logs && blackholed_by == NULL) } if (fd < 0) - { log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't open message log %s: %s", spool_name, strerror(errno)); - } - else { FILE *message_log = fdopen(fd, "a"); @@ -3976,7 +4083,7 @@ if (message_logs && blackholed_by == NULL) else { uschar *now = tod_stamp(tod_log); - fprintf(message_log, "%s Received from %s\n", now, s+3); + fprintf(message_log, "%s Received from %s\n", now, g->s+3); if (deliver_freeze) fprintf(message_log, "%s frozen by %s\n", now, frozen_by); if (queue_only_policy) fprintf(message_log, @@ -4024,7 +4131,7 @@ if (smtp_input && sender_host_address != NULL && !sender_host_notsocket && if (select(fileno(smtp_in) + 1, &select_check, NULL, NULL, &tv) != 0) { - int c = (receive_getc)(); + int c = (receive_getc)(GETC_BUFFER_UNLIMITED); if (c != EOF) (receive_ungetc)(c); else { smtp_notquit_exit(US"connection-lost", NULL, NULL); @@ -4033,11 +4140,10 @@ if (smtp_input && sender_host_address != NULL && !sender_host_notsocket && /* Re-use the log line workspace */ - sptr = 0; - s = string_cat(s, &size, &sptr, US"SMTP connection lost after final dot"); - s = add_host_info_for_log(s, &size, &sptr); - s[sptr] = 0; - log_write(0, LOG_MAIN, "%s", s); + g->ptr = 0; + g = string_cat(g, US"SMTP connection lost after final dot"); + g = add_host_info_for_log(g); + log_write(0, LOG_MAIN, "%s", string_from_gstring(g)); /* Delete the files for this aborted message. */ @@ -4058,7 +4164,7 @@ for this message. */ Send dot onward. If accepted, wipe the spooled files, log as delivered and accept the sender's dot (below). - If rejected: copy response to sender, wipe the spooled files, log approriately. + If rejected: copy response to sender, wipe the spooled files, log appropriately. If temp-reject: normally accept to sender, keep the spooled file - unless defer=pass in which case pass temp-reject back to initiator and dump the files. @@ -4066,9 +4172,9 @@ for this message. */ 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]) { @@ -4101,7 +4207,7 @@ if(!smtp_reply) log_write(0, LOG_MAIN | (LOGGING(received_recipients)? LOG_RECIPIENTS : 0) | (LOGGING(received_sender)? LOG_SENDER : 0), - "%s", s); + "%s", g->s); /* Log any control actions taken by an ACL or local_scan(). */ @@ -4113,7 +4219,7 @@ if(!smtp_reply) } receive_call_bombout = FALSE; -store_reset(s); /* The store for the main log message can be reused */ +store_reset(g); /* The store for the main log message can be reused */ /* If the message is frozen, and freeze_tell is set, do the telling. */ @@ -4185,12 +4291,12 @@ if (smtp_input) else if (chunking_state > CHUNKING_OFFERED) { - smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n", + smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n", FALSE, chunking_datasize, message_size+message_linecount, message_id); chunking_state = CHUNKING_OFFERED; } else - smtp_printf("250 OK id=%s\r\n", message_id); + smtp_printf("250 OK id=%s\r\n", FALSE, message_id); if (host_checking) fprintf(stdout, @@ -4204,7 +4310,7 @@ if (smtp_input) smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE, fake_response_text); else - smtp_printf("%.1024s\r\n", smtp_reply); + smtp_printf("%.1024s\r\n", FALSE, smtp_reply); switch (cutthrough_done) { @@ -4215,7 +4321,6 @@ if (smtp_input) 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: @@ -4225,12 +4330,15 @@ if (smtp_input) 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