X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/ddf1b11a732e293cd242c80bc63d459dda595bf4..4804c62909a62a3ac12ec4777ebd48c541028965:/src/src/receive.c diff --git a/src/src/receive.c b/src/src/receive.c index e53587619..2812ea2c8 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); } @@ -626,7 +627,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 +669,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 +787,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) @@ -898,11 +899,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 +915,105 @@ 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()) +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; + (void) cutthrough_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') + (void) cutthrough_put_nl(); + else + { + uschar c = ch; + (void) cutthrough_puts(&c, 1); + } } /*NOTREACHED*/ } @@ -1083,7 +1150,7 @@ switch(where) 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) { @@ -1096,15 +1163,15 @@ 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); +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) { @@ -1115,7 +1182,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 +1197,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 +1212,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 +1232,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"); } @@ -1321,7 +1388,7 @@ if (rc == OK) { (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; } @@ -1682,7 +1749,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. */ @@ -1705,8 +1772,8 @@ for (;;) (and sometimes lunatic messages can have ones that are 100s of K long) we call store_release() for strings that have been copied - if the string is at the start of a block (and therefore the only thing in it, because we aren't - doing any other gets), the block gets freed. We can only do this because we - know there are no other calls to store_get() going on. */ + doing any other gets), the block gets freed. We can only do this release if + there were no allocations since the once that we want to free. */ if (ptr >= header_size - 4) { @@ -1715,9 +1782,10 @@ for (;;) header_size *= 2; if (!store_extend(next->text, oldsize, header_size)) { + BOOL release_ok = store_last_get[store_pool] == next->text; uschar *newtext = store_get(header_size); memcpy(newtext, next->text, ptr); - store_release(next->text); + if (release_ok) store_release(next->text); next->text = newtext; } } @@ -1759,12 +1827,12 @@ for (;;) prevent further reading), and break out of the loop, having freed the empty header, and set next = NULL to indicate no data line. */ - if (ptr == 0 && ch == '.' && (smtp_input || dot_ends)) + if (ptr == 0 && ch == '.' && 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 +1863,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 +1958,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 +2152,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"); + 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 +2191,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 +2218,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 +2434,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 +2583,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 +2929,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; } @@ -3738,7 +3822,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,7 +3907,7 @@ 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; @@ -4024,7 +4108,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); @@ -4058,7 +4142,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.