X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/5eb690a173fdc71d5326c05a2a6a9d87d159187a..3c0a92dcf8312d3071769e5a36946c651330e0e4:/src/src/receive.c diff --git a/src/src/receive.c b/src/src/receive.c index 3c307b07b..8ac381add 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -1,10 +1,8 @@ -/* $Cambridge: exim/src/src/receive.c,v 1.50 2009/10/16 12:33:09 nm4 Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2007 */ +/* Copyright (c) University of Cambridge 1995 - 2012 */ /* See the file NOTICE for conditions of use and distribution. */ /* Code for receiving a message and setting up spool files. */ @@ -723,7 +721,7 @@ static int read_message_data_smtp(FILE *fout) { int ch_state = 0; -register int ch; +int ch; register int linelength = 0; while ((ch = (receive_getc)()) != EOF) @@ -770,6 +768,7 @@ while ((ch = (receive_getc)()) != EOF) { message_size++; if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR; + (void) cutthrough_put_nl(); if (ch != '\r') ch_state = 1; else continue; } break; @@ -790,6 +789,7 @@ while ((ch = (receive_getc)()) != EOF) message_size++; body_linecount++; if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR; + (void) cutthrough_put_nl(); if (ch == '\r') { ch_state = 2; @@ -809,6 +809,13 @@ while ((ch = (receive_getc)()) != EOF) 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); + } } /* Fall through here if EOF encountered. This indicates some kind of error, @@ -929,6 +936,40 @@ add_acl_headers(uschar *acl_name) { header_line *h, *next; header_line *last_received = NULL; +int sep = ':'; + +if (acl_removed_headers != NULL) + { + DEBUG(D_receive|D_acl) debug_printf(">>Headers removed by %s ACL:\n", acl_name); + + for (h = header_list; h != NULL; h = h->next) + { + int i; + uschar *list; + BOOL include_header; + + if (h->type == htype_old) continue; + + include_header = TRUE; + list = acl_removed_headers; + + int sep = ':'; /* This is specified as a colon-separated list */ + uschar *s; + uschar buffer[128]; + while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) + != NULL) + { + int len = Ustrlen(s); + if (header_testname(h, s, len, FALSE)) + { + h->type = htype_old; + DEBUG(D_receive|D_acl) debug_printf(" %s", h->text); + } + } + } + acl_removed_headers = NULL; + DEBUG(D_receive|D_acl) debug_printf(">>\n"); + } if (acl_added_headers == NULL) return; DEBUG(D_receive|D_acl) debug_printf(">>Headers added by %s ACL:\n", acl_name); @@ -1067,7 +1108,7 @@ unsigned long mbox_size; header_line *my_headerlist; uschar *user_msg, *log_msg; int mime_part_count_buffer = -1; -int rc; +int rc = OK; memset(CS rfc822_file_path,0,2048); @@ -1094,13 +1135,16 @@ return TRUE; DO_MIME_ACL: /* make sure the eml mbox file is spooled up */ -mbox_file = spool_mbox(&mbox_size); +mbox_file = spool_mbox(&mbox_size, NULL); if (mbox_file == NULL) { /* 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); unspool_mbox(); +#ifdef EXPERIMENTAL_DCC + dcc_ok = 0; +#endif smtp_respond(US"451", 3, TRUE, US"temporary local problem"); message_id[0] = 0; /* Indicate no message accepted */ *smtp_reply_ptr = US""; /* Indicate reply already sent */ @@ -1180,6 +1224,9 @@ else if (rc != OK) { Uunlink(spool_name); unspool_mbox(); +#ifdef EXPERIMENTAL_DCC + dcc_ok = 0; +#endif if (smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0) *smtp_yield_ptr = FALSE; /* No more messsages after dropped connection */ *smtp_reply_ptr = US""; /* Indicate reply already sent */ @@ -1193,6 +1240,52 @@ return TRUE; #endif /* WITH_CONTENT_SCAN */ + +void +received_header_gen(void) +{ +uschar *received; +uschar *timestamp; +header_line *received_header= header_list; + +timestamp = expand_string(US"${tod_full}"); +if (recipients_count == 1) received_for = recipients_list[0].address; +received = expand_string(received_header_text); +received_for = NULL; + +if (received == NULL) + { + if(spool_name[0] != 0) + Uunlink(spool_name); /* Lose the data file */ + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" " + "(received_header_text) failed: %s", string_printing(received_header_text), + expand_string_message); + } + +/* The first element on the header chain is reserved for the Received header, +so all we have to do is fill in the text pointer, and set the type. However, if +the result of the expansion is an empty string, we leave the header marked as +"old" so as to refrain from adding a Received header. */ + +if (received[0] == 0) + { + received_header->text = string_sprintf("Received: ; %s\n", timestamp); + received_header->type = htype_old; + } +else + { + received_header->text = string_sprintf("%s; %s\n", received, timestamp); + received_header->type = htype_received; + } + +received_header->slen = Ustrlen(received_header->text); + +DEBUG(D_receive) debug_printf(">>Generated Received: header line\n%c %s", + received_header->type, received_header->text); +} + + + /************************************************* * Receive message * *************************************************/ @@ -1202,7 +1295,8 @@ Either a non-null list of recipients, or the extract flag will be true, or both. The flag sender_local is true for locally generated messages. The flag submission_mode is true if an ACL has obeyed "control = submission". The flag suppress_local_fixups is true if an ACL has obeyed "control = -suppress_local_fixups". The flag smtp_input is true if the message is to be +suppress_local_fixups" or -G was passed on the command-line. +The flag smtp_input is true if the message is to be handled using SMTP conventions about termination and lines starting with dots. For non-SMTP messages, dot_ends is true for dot-terminated messages. @@ -1285,7 +1379,8 @@ not. */ BOOL receive_msg(BOOL extract_recip) { -int i, rc; +int i; +int rc = FAIL; int msg_size = 0; int process_info_len = Ustrlen(process_info); int error_rc = (error_handling == ERRORS_SENDER)? @@ -1308,6 +1403,7 @@ BOOL resents_exist = FALSE; uschar *resent_prefix = US""; uschar *blackholed_by = NULL; uschar *blackhole_log_msg = US""; +int cutthrough_done; flock_t lock_data; error_block *bad_addresses = NULL; @@ -1340,7 +1436,6 @@ header_line *received_header; /* Variables for use when building the Received: header. */ -uschar *received; uschar *timestamp; int tslen; @@ -1350,6 +1445,12 @@ might take a fair bit of real time. */ search_tidyup(); +/* Extracting the recipient list from an input file is incompatible with +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"); + /* 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 pointing to the end of the chain to make adding headers simple. */ @@ -2335,9 +2436,11 @@ if (msgid_header == NULL && } } - /* Add the header line */ + /* Add the header line + * Resent-* headers are prepended, per RFC 5322 3.6.6. Non-Resent-* are + * appended, to preserve classical expectations of header ordering. */ - header_add_at_position(FALSE, NULL, FALSE, htype_id, + header_add_at_position(!resents_exist, NULL, FALSE, htype_id, "%sMessage-Id: <%s%s%s@%s>\n", resent_prefix, message_id_external, (*id_text == 0)? "" : ".", id_text, id_domain); } @@ -2604,13 +2707,15 @@ changes in RFC 2822, this was dropped in November 2003. */ /* If there is no date header, generate one if the message originates locally (i.e. not over TCP/IP) and suppress_local_fixups is not set, or if the submission mode flag is set. Messages without Date: are not valid, but it seems -to be more confusing if Exim adds one to all remotely-originated messages. */ +to be more confusing if Exim adds one to all remotely-originated messages. +As per Message-Id, we prepend if resending, else append. +*/ if (!date_header_exists && ((sender_host_address == NULL && !suppress_local_fixups) || submission_mode)) - header_add_at_position(FALSE, NULL, FALSE, htype_other, "%sDate: %s\n", - resent_prefix, tod_stamp(tod_full)); + header_add_at_position(!resents_exist, NULL, FALSE, htype_other, + "%sDate: %s\n", resent_prefix, tod_stamp(tod_full)); search_tidyup(); /* Free any cached resources */ @@ -2635,6 +2740,35 @@ if (filter_test != FTEST_NONE) return message_ended == END_DOT; } +/* 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 (received_count > received_headers_max) + { + cancel_cutthrough_connection("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); + 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 */ + } + received_header_gen(); + add_acl_headers(US"MAIL or RCPT"); + (void) cutthrough_headers_send(); + } + + /* Open a new spool file for the data portion of the message. We need to access it both via a file descriptor and a stream. Try to make the directory if it isn't there. Note re use of sprintf: spool_directory @@ -2716,6 +2850,7 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT) if (smtp_input && message_ended == END_EOF) { Uunlink(spool_name); /* Lose data file when closed */ + cancel_cutthrough_connection("sender closed connection"); message_id[0] = 0; /* Indicate no message accepted */ smtp_reply = handle_lost_connection(US""); smtp_yield = FALSE; @@ -2728,6 +2863,7 @@ if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT) if (message_ended == END_SIZE) { Uunlink(spool_name); /* Lose the data file when closed */ + cancel_cutthrough_connection("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: " @@ -2783,6 +2919,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"); if (smtp_input) { @@ -2902,50 +3039,25 @@ for use when we generate the Received: header. Note: the checking for too many Received: headers is handled by the delivery code. */ +/*XXX eventually add excess Received: check for cutthrough case back when classifying them */ -timestamp = expand_string(US"${tod_full}"); -if (recipients_count == 1) received_for = recipients_list[0].address; -received = expand_string(received_header_text); -received_for = NULL; - -if (received == NULL) +if (received_header->text == NULL) /* Non-cutthrough case */ { - Uunlink(spool_name); /* Lose the data file */ - log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" " - "(received_header_text) failed: %s", string_printing(received_header_text), - expand_string_message); - } - -/* The first element on the header chain is reserved for the Received header, -so all we have to do is fill in the text pointer, and set the type. However, if -the result of the expansion is an empty string, we leave the header marked as -"old" so as to refrain from adding a Received header. */ - -if (received[0] == 0) - { - received_header->text = string_sprintf("Received: ; %s\n", timestamp); - received_header->type = htype_old; - } -else - { - received_header->text = string_sprintf("%s; %s\n", received, timestamp); - received_header->type = htype_received; - } - -received_header->slen = Ustrlen(received_header->text); + received_header_gen(); -DEBUG(D_receive) debug_printf(">>Generated Received: header line\n%c %s", - received_header->type, received_header->text); + /* Set the value of message_body_size for the DATA ACL and for local_scan() */ -/* Set the value of message_body_size for the DATA ACL and for local_scan() */ + message_body_size = (fstat(data_fd, &statbuf) == 0)? + statbuf.st_size - SPOOL_DATA_START_OFFSET : -1; -message_body_size = (fstat(data_fd, &statbuf) == 0)? - statbuf.st_size - SPOOL_DATA_START_OFFSET : -1; + /* If an ACL from any RCPT commands set up any warning headers to add, do so + now, before running the DATA ACL. */ -/* If an ACL from any RCPT commands set up any warning headers to add, do so -now, before running the DATA ACL. */ - -add_acl_headers(US"MAIL or RCPT"); + add_acl_headers(US"MAIL or RCPT"); + } +else + message_body_size = (fstat(data_fd, &statbuf) == 0)? + statbuf.st_size - SPOOL_DATA_START_OFFSET : -1; /* If an ACL is specified for checking things at this stage of reception of a message, run it, unless all the recipients were removed by "discard" in earlier @@ -2999,6 +3111,8 @@ else int seen_items_size = 0; int seen_items_offset = 0; uschar itembuf[256]; + /* Default to OK when no items are present */ + rc = OK; while ((item = string_nextinlist(&ptr, &sep, itembuf, sizeof(itembuf))) != NULL) @@ -3009,14 +3123,29 @@ else appears in the expanded list. */ if (seen_items != NULL) { + uschar *seen_item = NULL; + uschar seen_item_buf[256]; uschar *seen_items_list = seen_items; - if (match_isinlist(item, - &seen_items_list,0,NULL,NULL,MCL_STRING,TRUE,NULL) == OK) + int seen_this_item = 0; + + while ((seen_item = string_nextinlist(&seen_items_list, &sep, + seen_item_buf, + sizeof(seen_item_buf))) != NULL) + { + if (Ustrcmp(seen_item,item) == 0) + { + seen_this_item = 1; + break; + } + } + + if (seen_this_item > 0) { DEBUG(D_receive) debug_printf("acl_smtp_dkim: skipping signer %s, already seen\n", item); continue; } + seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,":"); } @@ -3033,6 +3162,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"); break; } } @@ -3078,12 +3208,17 @@ else blackholed_by = US"DATA ACL"; if (log_msg != NULL) blackhole_log_msg = string_sprintf(": %s", log_msg); + cancel_cutthrough_connection("data acl discard"); } else if (rc != OK) { Uunlink(spool_name); + cancel_cutthrough_connection("data acl not ok"); #ifdef WITH_CONTENT_SCAN unspool_mbox(); +#endif +#ifdef EXPERIMENTAL_DCC + dcc_ok = 0; #endif if (smtp_handle_acl_fail(ACL_WHERE_DATA, rc, user_msg, log_msg) != 0) smtp_yield = FALSE; /* No more messsages after dropped connection */ @@ -3123,6 +3258,9 @@ else Uunlink(spool_name); #ifdef WITH_CONTENT_SCAN unspool_mbox(); +#endif +#ifdef EXPERIMENTAL_DCC + dcc_ok = 0; #endif /* The ACL can specify where rejections are to be logged, possibly nowhere. The default is main and reject logs. */ @@ -3333,6 +3471,7 @@ the message to be abandoned. */ signal(SIGTERM, SIG_IGN); signal(SIGINT, SIG_IGN); + /* Ensure the first time flag is set in the newly-received message. */ deliver_firsttime = TRUE; @@ -3447,15 +3586,18 @@ if (message_reference != NULL) s = add_host_info_for_log(s, &size, &sptr); #ifdef SUPPORT_TLS -if ((log_extra_selector & LX_tls_cipher) != 0 && tls_cipher != NULL) - s = string_append(s, &size, &sptr, 2, US" X=", tls_cipher); +if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL) + s = string_append(s, &size, &sptr, 2, US" X=", tls_in.cipher); if ((log_extra_selector & LX_tls_certificate_verified) != 0 && - tls_cipher != NULL) + tls_in.cipher != NULL) s = string_append(s, &size, &sptr, 2, US" CV=", - tls_certificate_verified? "yes":"no"); -if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL) + tls_in.certificate_verified? "yes":"no"); +if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL) s = string_append(s, &size, &sptr, 3, US" DN=\"", - string_printing(tls_peerdn), US"\""); + string_printing(tls_in.peerdn), US"\""); +if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL) + s = string_append(s, &size, &sptr, 3, US" SNI=\"", + string_printing(tls_in.sni), US"\""); #endif if (sender_host_authenticated != NULL) @@ -3468,6 +3610,15 @@ if (sender_host_authenticated != NULL) sprintf(CS big_buffer, "%d", msg_size); s = string_append(s, &size, &sptr, 2, US" S=", big_buffer); +/* log 8BITMIME mode announced in MAIL_FROM + 0 ... no BODY= used + 7 ... 7BIT + 8 ... 8BITMIME */ +if (log_extra_selector & LX_8bitmime) { + sprintf(CS big_buffer, "%d", body_8bitmime); + s = string_append(s, &size, &sptr, 2, US" M8S=", big_buffer); +} + /* 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! Therefore, make sure we use a printing-characters only version for the log. @@ -3515,7 +3666,7 @@ s[sptr] = 0; /* 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 somep +creation until the first delivery, but this has proved confusing for some people. */ if (message_logs && blackholed_by == NULL) @@ -3636,17 +3787,56 @@ if (smtp_input && sender_host_address != NULL && !sender_host_notsocket && /* The connection has not gone away; we really are going to take responsibility for this message. */ -log_write(0, LOG_MAIN | - (((log_extra_selector & LX_received_recipients) != 0)? LOG_RECIPIENTS : 0) | - (((log_extra_selector & LX_received_sender) != 0)? LOG_SENDER : 0), - "%s", s); -receive_call_bombout = FALSE; +/* Cutthrough - had sender last-dot; assume we've sent (or bufferred) all + data onward by now. + + 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 temp-reject: accept to sender, keep the spooled files. -/* Log any control actions taken by an ACL or local_scan(). */ + Having the normal spool files lets us do data-filtering, and store/forward on temp-reject. -if (deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by); -if (queue_only_policy) log_write(L_delay_delivery, LOG_MAIN, - "no immediate delivery: queued by %s", queued_by); + XXX We do not handle queue-only, freezing, or blackholes. +*/ +if(cutthrough_fd >= 0) + { + uschar * msg= cutthrough_finaldot(); /* Ask the target system to accept the messsage */ + /* Logging was done in finaldot() */ + switch(msg[0]) + { + case '2': /* Accept. Do the same to the source; dump any spoolfiles. */ + cutthrough_done = 3; + break; /* message_id needed for SMTP accept below */ + + default: /* Unknown response, or error. Treat as temp-reject. */ + case '4': /* Temp-reject. Keep spoolfiles and accept. */ + cutthrough_done = 1; /* Avoid the usual immediate delivery attempt */ + break; /* message_id needed for SMTP accept below */ + + case '5': /* Perm-reject. Do the same to the source. Dump any spoolfiles */ + smtp_reply= msg; /* Pass on the exact error */ + cutthrough_done = 2; + break; + } + } +else + cutthrough_done = 0; + +if(smtp_reply == NULL) + { + log_write(0, LOG_MAIN | + (((log_extra_selector & LX_received_recipients) != 0)? LOG_RECIPIENTS : 0) | + (((log_extra_selector & LX_received_sender) != 0)? LOG_SENDER : 0), + "%s", s); + + /* Log any control actions taken by an ACL or local_scan(). */ + + if (deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by); + if (queue_only_policy) log_write(L_delay_delivery, LOG_MAIN, + "no immediate delivery: queued by %s", queued_by); + } +receive_call_bombout = FALSE; store_reset(s); /* The store for the main log message can be reused */ @@ -3674,6 +3864,7 @@ data file will be flushed(!) out thereby. Nevertheless, it is theoretically possible for fclose() to fail - but what to do? What has happened to the lock if this happens? */ + TIDYUP: process_info[process_info_len] = 0; /* Remove message id */ if (data_file != NULL) (void)fclose(data_file); /* Frees the lock */ @@ -3734,6 +3925,25 @@ if (smtp_input) else smtp_printf("%.1024s\r\n", smtp_reply); } + + switch (cutthrough_done) + { + case 3: log_write(0, LOG_MAIN, "Completed"); /* Delivery was done */ + case 2: { /* Delete spool files */ + sprintf(CS spool_name, "%s/input/%s/%s-D", spool_directory, + message_subdir, message_id); + Uunlink(spool_name); + sprintf(CS spool_name, "%s/input/%s/%s-H", spool_directory, + message_subdir, message_id); + Uunlink(spool_name); + sprintf(CS spool_name, "%s/msglog/%s/%s", spool_directory, + message_subdir, message_id); + Uunlink(spool_name); + } + case 1: message_id[0] = 0; /* Prevent a delivery from starting */ + default:break; + } + cutthrough_delivery = FALSE; } /* For batched SMTP, generate an error message on failure, and do