X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/eae427c645a7d0e2051b0600d2e4235789740132..f49d9ed0b8cbf4b87e9c8d9007767ba48f440332:/src/src/receive.c diff --git a/src/src/receive.c b/src/src/receive.c index b0dacbd68..f4b829659 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -3,7 +3,7 @@ *************************************************/ /* Copyright (c) University of Cambridge 1995 - 2018 */ -/* Copyright (c) The Exim Maintainers 2020 */ +/* Copyright (c) The Exim Maintainers 2020 - 2021 */ /* See the file NOTICE for conditions of use and distribution. */ /* Code for receiving a message and setting up spool files. */ @@ -44,42 +44,71 @@ receive_getc initially. They just call the standard functions, passing stdin as the file. (When SMTP input is occurring, different functions are used by changing the pointer variables.) */ -int -stdin_getc(unsigned lim) -{ -int c = getc(stdin); +uschar stdin_buf[4096]; +uschar * stdin_inptr = stdin_buf; +uschar * stdin_inend = stdin_buf; -if (had_data_timeout) - { - fprintf(stderr, "exim: timed out while reading - message abandoned\n"); - log_write(L_lost_incoming_connection, - LOG_MAIN, "timed out while reading local message"); - receive_bomb_out(US"data-timeout", NULL); /* Does not return */ - } -if (had_data_sigint) +static BOOL +stdin_refill(void) +{ +size_t rc = fread(stdin_buf, 1, sizeof(stdin_buf), stdin); +if (rc <= 0) { - if (filter_test == FTEST_NONE) + if (had_data_timeout) { - fprintf(stderr, "\nexim: %s received - message abandoned\n", - had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT"); - log_write(0, LOG_MAIN, "%s received while reading local message", - had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT"); + fprintf(stderr, "exim: timed out while reading - message abandoned\n"); + log_write(L_lost_incoming_connection, + LOG_MAIN, "timed out while reading local message"); + receive_bomb_out(US"data-timeout", NULL); /* Does not return */ } - receive_bomb_out(US"signal-exit", NULL); /* Does not return */ + if (had_data_sigint) + { + if (filter_test == FTEST_NONE) + { + fprintf(stderr, "\nexim: %s received - message abandoned\n", + had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT"); + log_write(0, LOG_MAIN, "%s received while reading local message", + had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT"); + } + receive_bomb_out(US"signal-exit", NULL); /* Does not return */ + } + return FALSE; } -return c; +stdin_inend = stdin_buf + rc; +stdin_inptr = stdin_buf; +return TRUE; +} + +int +stdin_getc(unsigned lim) +{ +if (stdin_inptr >= stdin_inend) + if (!stdin_refill()) + return EOF; +return *stdin_inptr++; +} + + +BOOL +stdin_hasc(void) +{ +return stdin_inptr < stdin_inend; } int stdin_ungetc(int c) { -return ungetc(c, stdin); +if (stdin_inptr <= stdin_buf) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in stdin_ungetc"); + +*--stdin_inptr = c; +return c; } int stdin_feof(void) { -return feof(stdin); +return stdin_hasc() ? FALSE : feof(stdin); } int @@ -490,6 +519,13 @@ if (recipients_count >= recipients_list_max) { recipient_item *oldlist = recipients_list; int oldmax = recipients_list_max; + + const int safe_recipients_limit = INT_MAX / 2 / sizeof(recipient_item); + if (recipients_list_max < 0 || recipients_list_max >= safe_recipients_limit) + { + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Too many recipients: %d", recipients_list_max); + } + recipients_list_max = recipients_list_max ? 2*recipients_list_max : 50; recipients_list = store_get(recipients_list_max * sizeof(recipient_item), FALSE); if (oldlist) @@ -581,19 +617,15 @@ the file copy. */ static void log_close_chk(void) { -if (!receive_timeout) +if (!receive_timeout && !receive_hasc()) { struct timeval t; timesince(&t, &received_time); if (t.tv_sec > 30*60) mainlog_close(); else - { - fd_set r; - FD_ZERO(&r); FD_SET(0, &r); - t.tv_sec = 30*60 - t.tv_sec; t.tv_usec = 0; - if (select(1, &r, NULL, NULL, &t) == 0) mainlog_close(); - } + if (poll_one_fd(0, POLLIN, (30*60 - t.tv_sec) * 1000) == 0) + mainlog_close(); } } @@ -647,11 +679,6 @@ if (!f.dot_ends) { int last_ch = '\n'; -/*XXX we do a gettimeofday before checking for every received char, -which is hardly clever. The function-indirection doesn't help, but -an additional function to check for nonempty read buffer would help. -See stdin_getc() / smtp_getc() / tls_getc() / bdat_getc(). */ - for ( ; log_close_chk(), (ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF; last_ch = ch) @@ -1656,10 +1683,9 @@ 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; -int id_resolution = 0; int had_zero = 0; int prevlines_length = 0; +const int id_resolution = BASE_62 == 62 ? 5000 : 10000; int ptr = 0; @@ -1713,6 +1739,10 @@ BOOL msgid_header_newly_created = FALSE; uschar *timestamp; int tslen; +/* Time of creation of message_id */ + +static struct timeval message_id_tv = { 0, 0 }; + /* Release any open files that might have been cached while preparing to accept the message - e.g. by verifying addresses - because reading a message @@ -1779,17 +1809,40 @@ if (smtp_input && !smtp_batched_input && !f.dkim_disable_verify) if (sender_host_address) dmarc_init(); /* initialize libopendmarc */ #endif +/* In SMTP sessions we may receive several messages in one connection. Before +each subsequent one, we wait for the clock to tick at the level of message-id +granularity. +This is so that the combination of time+pid is unique, even on systems where the +pid can be re-used within our time interval. We can't shorten the interval +without re-designing the message-id. See comments above where the message id is +created. This is Something For The Future. +Do this wait any time we have previously created a message-id, even if we +rejected the message. This gives unique IDs for logging done by ACLs. +The initial timestamp must have been obtained via exim_gettime() to avoid +issues on Linux with suspend/resume. */ + +if (message_id_tv.tv_sec) + { + message_id_tv.tv_usec = (message_id_tv.tv_usec/id_resolution) * id_resolution; + exim_wait_tick(&message_id_tv, id_resolution); + } + /* Remember the time of reception. Exim uses time+pid for uniqueness of message ids, and fractions of a second are required. See the comments that precede the -message id creation below. */ +message id creation below. +We use a routine that if possible uses a monotonic clock, and can be used again +after reception for the tick-wait even under the Linux non-Posix behaviour. */ -exim_gettime(&message_id_tv); +else + exim_gettime(&message_id_tv); /* 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. +For this we do not care about the Linux suspend/resume problem, so rather than +use exim_gettime() everywhere we use a plain gettimeofday() here. */ -received_time = message_id_tv; +gettimeofday(&received_time, NULL); /* If SMTP input, set the special handler for timeouts. The alarm() calls happen in the smtp_getc() function when it refills its buffer. */ @@ -2122,7 +2175,8 @@ OVERSIZE: if (newsender) { if (domain == 0 && newsender[0] != 0) - newsender = rewrite_address_qualify(newsender, FALSE); + /* deconst ok as newsender was not const */ + newsender = US rewrite_address_qualify(newsender, FALSE); if (filter_test != FTEST_NONE || receive_check_set_sender(newsender)) { @@ -2502,7 +2556,7 @@ if (extract_recip) { while (recipients_count-- > 0) { - uschar *s = rewrite_address(recipients_list[recipients_count].address, + const uschar * s = rewrite_address(recipients_list[recipients_count].address, TRUE, TRUE, global_rewrite_rules, rewrite_existflags); tree_add_nonrecipient(s); } @@ -2553,11 +2607,12 @@ if (extract_recip) &domain, FALSE); #ifdef SUPPORT_I18N - if (string_is_utf8(recipient)) - message_smtputf8 = TRUE; - else - allow_utf8_domains = b; + if (recipient) + if (string_is_utf8(recipient)) message_smtputf8 = TRUE; + else allow_utf8_domains = b; } +#else + ; #endif /* Keep a list of all the bad addresses so we can send a single @@ -2668,28 +2723,20 @@ message_id[6] = '-'; Ustrncpy(message_id + 7, string_base62((long int)getpid()), 6); /* Deal with the case where the host number is set. The value of the number was -checked when it was read, to ensure it isn't too big. The timing granularity is -left in id_resolution so that an appropriate wait can be done after receiving -the message, if necessary (we hope it won't be). */ +checked when it was read, to ensure it isn't too big. */ if (host_number_string) - { - id_resolution = BASE_62 == 62 ? 5000 : 10000; sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s", string_base62((long int)( host_number * (1000000/id_resolution) + message_id_tv.tv_usec/id_resolution)) + 4); - } /* Host number not set: final field is just the fractional time at an appropriate resolution. */ else - { - id_resolution = BASE_62 == 62 ? 500 : 1000; sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s", string_base62((long int)(message_id_tv.tv_usec/id_resolution)) + 4); - } /* Add the current message id onto the current process info string if it will fit. */ @@ -2789,8 +2836,8 @@ recipients will get here only if the conditions were right (allow_unqualified_ recipient is TRUE). */ for (int i = 0; i < recipients_count; i++) - recipients_list[i].address = - rewrite_address(recipients_list[i].address, TRUE, TRUE, + recipients_list[i].address = /* deconst ok as src was not cont */ + US rewrite_address(recipients_list[i].address, TRUE, TRUE, global_rewrite_rules, rewrite_existflags); /* If there is no From: header, generate one for local (without @@ -2965,7 +3012,8 @@ it has already been rewritten as part of verification for SMTP input. */ if (global_rewrite_rules && !sender_address_unrewritten && *sender_address) { - sender_address = rewrite_address(sender_address, FALSE, TRUE, + /* deconst ok as src was not const */ + sender_address = US rewrite_address(sender_address, FALSE, TRUE, global_rewrite_rules, rewrite_existflags); DEBUG(D_receive|D_rewrite) debug_printf("rewritten sender = %s\n", sender_address); @@ -4070,6 +4118,8 @@ if ( LOGGING(msg_id) && msgid_header uschar * old_id; BOOL save_allow_domain_literals = allow_domain_literals; allow_domain_literals = TRUE; + int start, end, domain; + old_id = parse_extract_address(Ustrchr(msgid_header->text, ':') + 1, &errmsg, &start, &end, &domain, FALSE); allow_domain_literals = save_allow_domain_literals; @@ -4162,7 +4212,7 @@ f.receive_call_bombout = TRUE; /* Before sending an SMTP response in a TCP/IP session, we check to see if the connection has gone away. This can only be done if there is no unconsumed input waiting in the local input buffer. We can test for this by calling -receive_smtp_buffered(). RFC 2920 (pipelining) explicitly allows for additional +receive_hasc(). RFC 2920 (pipelining) explicitly allows for additional input to be sent following the final dot, so the presence of following input is not an error. @@ -4177,15 +4227,10 @@ Of course, since TCP/IP is asynchronous, there is always a chance that the connection will vanish between the time of this test and the sending of the response, but the chance of this happening should be small. */ -if (smtp_input && sender_host_address && !f.sender_host_notsocket && - !receive_smtp_buffered()) +if ( smtp_input && sender_host_address && !f.sender_host_notsocket + && !receive_hasc()) { - struct timeval tv = {.tv_sec = 0, .tv_usec = 0}; - fd_set select_check; - FD_ZERO(&select_check); - FD_SET(fileno(smtp_in), &select_check); - - if (select(fileno(smtp_in) + 1, &select_check, NULL, NULL, &tv) != 0) + if (poll_one_fd(fileno(smtp_in), POLLIN, 0) != 0) { int c = (receive_getc)(GETC_BUFFER_UNLIMITED); if (c != EOF) (receive_ungetc)(c); else @@ -4309,23 +4354,6 @@ then we can think about properly declaring the message not-received. */ TIDYUP: -/* In SMTP sessions we may receive several messages in one connection. After -each one, we wait for the clock to tick at the level of message-id granularity. -This is so that the combination of time+pid is unique, even on systems where the -pid can be re-used within our time interval. We can't shorten the interval -without re-designing the message-id. See comments above where the message id is -created. This is Something For The Future. -Do this wait any time we have created a message-id, even if we rejected the -message. This gives unique IDs for logging done by ACLs. */ - -if (id_resolution != 0) - { - message_id_tv.tv_usec = (message_id_tv.tv_usec/id_resolution) * id_resolution; - exim_wait_tick(&message_id_tv, id_resolution); - id_resolution = 0; - } - - process_info[process_info_len] = 0; /* Remove message id */ if (spool_data_file && cutthrough_done == NOT_TRIED) { @@ -4381,12 +4409,12 @@ if (smtp_input) the socket. */ smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n", - receive_smtp_buffered(), + receive_hasc(), chunking_datasize, message_size+message_linecount, message_id); chunking_state = CHUNKING_OFFERED; } else - smtp_printf("250 OK id=%s\r\n", receive_smtp_buffered(), message_id); + smtp_printf("250 OK id=%s\r\n", receive_hasc(), message_id); if (host_checking) fprintf(stdout,