X-Git-Url: https://vcs.fsf.org/?p=exim.git;a=blobdiff_plain;f=src%2Fsrc%2Freceive.c;h=f27dc425ebb9fc64f13970537c2c8070c3898101;hp=e4c82d2fa7e7087e31fdaf6e822f7e78f3f24f1f;hb=a3ef73105c3539e9d29c27095573f9d437752f7f;hpb=563b63fa3e09d67239f51483e5dcec5c91251522 diff --git a/src/src/receive.c b/src/src/receive.c index e4c82d2fa..f27dc425e 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -1,24 +1,22 @@ -/* $Cambridge: exim/src/src/receive.c,v 1.37 2007/04/16 10:31:58 ph10 Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2007 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Code for receiving a message and setting up spool files. */ #include "exim.h" -#ifdef EXPERIMENTAL_DOMAINKEYS -#define RECEIVE_GETC dk_receive_getc -#define RECEIVE_UNGETC dk_receive_ungetc -#else -#define RECEIVE_GETC receive_getc -#define RECEIVE_UNGETC receive_ungetc +#ifdef EXPERIMENTAL_DCC +extern int dcc_ok; #endif +#ifdef EXPERIMENTAL_DMARC +# include "dmarc.h" +#endif /* EXPERIMENTAL_DMARC */ + /************************************************* * Local static variables * *************************************************/ @@ -178,7 +176,7 @@ else } } -/* We now have the patch; do the business */ +/* We now have the path; do the business */ memset(&statbuf, 0, sizeof(statbuf)); @@ -283,43 +281,59 @@ that case is done by setting a flag to cause the log functions to call this function if there is an ultimate disaster. That is why it is globally accessible. -Arguments: SMTP response to give if in an SMTP session +Arguments: + reason text reason to pass to the not-quit ACL + msg default SMTP response to give if in an SMTP session Returns: it doesn't */ void -receive_bomb_out(uschar *msg) +receive_bomb_out(uschar *reason, uschar *msg) { + static BOOL already_bombing_out; +/* The smtp_notquit_exit() below can call ACLs which can trigger recursive +timeouts, if someone has something slow in their quit ACL. Since the only +things we should be doing are to close down cleanly ASAP, on the second +pass we also close down stuff that might be opened again, before bypassing +the ACL call and exiting. */ + /* If spool_name is set, it contains the name of the data file that is being written. Unlink it before closing so that it cannot be picked up by a delivery process. Ensure that any header file is also removed. */ -if (spool_name[0] != 0) +if (spool_name[0] != '\0') { Uunlink(spool_name); spool_name[Ustrlen(spool_name) - 1] = 'H'; Uunlink(spool_name); + spool_name[0] = '\0'; } /* Now close the file if it is open, either as a fd or a stream. */ -if (data_file != NULL) (void)fclose(data_file); - else if (data_fd >= 0) (void)close(data_fd); +if (data_file != NULL) + { + (void)fclose(data_file); + data_file = NULL; +} else if (data_fd >= 0) { + (void)close(data_fd); + data_fd = -1; + } -/* Attempt to close down an SMTP connection tidily. */ +/* Attempt to close down an SMTP connection tidily. For non-batched SMTP, call +smtp_notquit_exit(), which runs the NOTQUIT ACL, if present, and handles the +SMTP response. */ -if (smtp_input) +if (!already_bombing_out) { - if (!smtp_batched_input) + already_bombing_out = TRUE; + if (smtp_input) { - smtp_printf("421 %s %s - closing connection.\r\n", smtp_active_hostname, - msg); - mac_smtp_fflush(); + if (smtp_batched_input) + moan_smtp_batch(NULL, "421 %s - message abandoned", msg); /* No return */ + smtp_notquit_exit(reason, US"421", US"%s %s - closing connection.", + smtp_active_hostname, msg); } - - /* Control does not return from moan_smtp_batch(). */ - - else moan_smtp_batch(NULL, "421 %s - message abandoned", msg); } /* Exit from the program (non-BSMTP cases) */ @@ -362,7 +376,7 @@ else LOG_MAIN, "timed out while reading local message"); } -receive_bomb_out(msg); /* Does not return */ +receive_bomb_out(US"data-timeout", msg); /* Does not return */ } @@ -384,7 +398,8 @@ local_scan_timeout_handler(int sig) sig = sig; /* Keep picky compilers happy */ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function timed out - " "message temporarily rejected (size %d)", message_size); -receive_bomb_out(US"local verification problem"); /* Does not return */ +/* Does not return */ +receive_bomb_out(US"local-scan-timeout", US"local verification problem"); } @@ -405,7 +420,8 @@ local_scan_crash_handler(int sig) { log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function crashed with " "signal %d - message temporarily rejected (size %d)", sig, message_size); -receive_bomb_out(US"local verification problem"); /* Does not return */ +/* Does not return */ +receive_bomb_out(US"local-scan-error", US"local verification problem"); } @@ -442,7 +458,7 @@ else } } -receive_bomb_out(msg); /* Does not return */ +receive_bomb_out(US"signal-exit", msg); /* Does not return */ } @@ -481,12 +497,46 @@ recipients_list[recipients_count].bmi_optin = bmi_current_optin; /* reset optin string pointer for next recipient */ bmi_current_optin = NULL; #endif +#ifdef EXPERIMENTAL_DSN +recipients_list[recipients_count].orcpt = NULL; +recipients_list[recipients_count].dsn_flags = 0; +#endif recipients_list[recipients_count++].errors_to = NULL; } +/************************************************* +* Send user response message * +*************************************************/ + +/* This function is passed a default response code and a user message. It calls +smtp_message_code() to check and possibly modify the response code, and then +calls smtp_respond() to transmit the response. I put this into a function +just to avoid a lot of repetition. + +Arguments: + code the response code + user_msg the user message + +Returns: nothing +*/ + +#ifndef DISABLE_PRDR +static void +smtp_user_msg(uschar *code, uschar *user_msg) +{ +int len = 3; +smtp_message_code(&code, &len, &user_msg, NULL); +smtp_respond(code, len, TRUE, user_msg); +} +#endif + + + + + /************************************************* * Remove a recipient from the list * *************************************************/ @@ -564,6 +614,7 @@ read_message_data(FILE *fout) { int ch_state; register int ch; +register int linelength = 0; /* Handle the case when only EOF terminates the message */ @@ -571,11 +622,14 @@ if (!dot_ends) { register int last_ch = '\n'; - for (; (ch = (RECEIVE_GETC)()) != EOF; last_ch = ch) + for (; (ch = (receive_getc)()) != EOF; last_ch = ch) { if (ch == 0) body_zerocount++; if (last_ch == '\r' && ch != '\n') { + if (linelength > max_received_linelength) + max_received_linelength = linelength; + linelength = 0; if (fputc('\n', fout) == EOF) return END_WERROR; message_size++; body_linecount++; @@ -583,12 +637,21 @@ if (!dot_ends) if (ch == '\r') continue; if (fputc(ch, fout) == EOF) return END_WERROR; - if (ch == '\n') body_linecount++; + if (ch == '\n') + { + if (linelength > max_received_linelength) + max_received_linelength = linelength; + linelength = 0; + body_linecount++; + } + else linelength++; if (++message_size > thismessage_size_limit) return END_SIZE; } if (last_ch != '\n') { + if (linelength > max_received_linelength) + max_received_linelength = linelength; if (fputc('\n', fout) == EOF) return END_WERROR; message_size++; body_linecount++; @@ -601,32 +664,45 @@ if (!dot_ends) ch_state = 1; -while ((ch = (RECEIVE_GETC)()) != EOF) +while ((ch = (receive_getc)()) != EOF) { if (ch == 0) body_zerocount++; switch (ch_state) { case 0: /* Normal state (previous char written) */ if (ch == '\n') - { body_linecount++; ch_state = 1; } + { + body_linecount++; + if (linelength > max_received_linelength) + max_received_linelength = linelength; + linelength = -1; + ch_state = 1; + } else if (ch == '\r') { ch_state = 2; continue; } break; case 1: /* After written "\n" */ if (ch == '.') { ch_state = 3; continue; } - if (ch != '\n') ch_state = 0; + if (ch == '\r') { ch_state = 2; continue; } + if (ch != '\n') ch_state = 0; else linelength = -1; break; case 2: body_linecount++; /* After unwritten "\r" */ + if (linelength > max_received_linelength) + max_received_linelength = linelength; if (ch == '\n') - { ch_state = 1; } + { + ch_state = 1; + linelength = -1; + } else { if (message_size++, fputc('\n', fout) == EOF) return END_WERROR; if (ch == '\r') continue; ch_state = 0; + linelength = 0; } break; @@ -634,6 +710,7 @@ while ((ch = (RECEIVE_GETC)()) != EOF) if (ch == '\n') return END_DOT; if (ch == '\r') { ch_state = 4; continue; } message_size++; + linelength++; if (fputc('.', fout) == EOF) return END_WERROR; ch_state = 0; break; @@ -648,6 +725,7 @@ while ((ch = (RECEIVE_GETC)()) != EOF) break; } + linelength++; if (fputc(ch, fout) == EOF) return END_WERROR; if (++message_size > thismessage_size_limit) return END_SIZE; } @@ -700,9 +778,10 @@ 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) +while ((ch = (receive_getc)()) != EOF) { if (ch == 0) body_zerocount++; switch (ch_state) @@ -722,6 +801,9 @@ while ((ch = (RECEIVE_GETC)()) != EOF) { ch_state = 0; body_linecount++; + if (linelength > max_received_linelength) + max_received_linelength = linelength; + linelength = -1; } else if (ch == '\r') { @@ -732,6 +814,9 @@ while ((ch = (RECEIVE_GETC)()) != EOF) case 2: /* After (unwritten) CR */ body_linecount++; + if (linelength > max_received_linelength) + max_received_linelength = linelength; + linelength = -1; if (ch == '\n') { ch_state = 0; @@ -740,6 +825,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; @@ -760,6 +846,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; @@ -773,11 +860,19 @@ while ((ch = (RECEIVE_GETC)()) != EOF) next. */ message_size++; + linelength++; if (fout != NULL) { 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, @@ -894,11 +989,56 @@ Returns: nothing */ static void -add_acl_headers(uschar *acl_name) +add_acl_headers(int where, uschar *acl_name) { header_line *h, *next; header_line *last_received = NULL; +switch(where) + { + case ACL_WHERE_DKIM: + case ACL_WHERE_MIME: + case ACL_WHERE_DATA: + if (cutthrough_fd >= 0 && (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"); + return; + } + } + +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) + { + 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); @@ -1036,7 +1176,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); @@ -1063,13 +1203,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 */ @@ -1139,7 +1282,7 @@ if (rc == OK) } END_MIME_ACL: -add_acl_headers(US"MIME"); +add_acl_headers(ACL_WHERE_MIME, US"MIME"); if (rc == DISCARD) { recipients_count = 0; @@ -1149,9 +1292,13 @@ else if (rc != OK) { Uunlink(spool_name); unspool_mbox(); - if (smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0) +#ifdef EXPERIMENTAL_DCC + dcc_ok = 0; +#endif + if (smtp_input && 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 */ + *smtp_reply_ptr = US""; /* Indicate reply already sent */ + } message_id[0] = 0; /* Indicate no message accepted */ return FALSE; /* Cause skip to end of receive function */ } @@ -1162,6 +1309,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 * *************************************************/ @@ -1171,7 +1364,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. @@ -1254,7 +1448,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)? @@ -1263,6 +1458,7 @@ int header_size = 256; int start, end, domain, size, sptr; int id_resolution; int had_zero = 0; +int prevlines_length = 0; register int ptr = 0; @@ -1276,6 +1472,7 @@ BOOL resents_exist = FALSE; uschar *resent_prefix = US""; uschar *blackholed_by = NULL; uschar *blackhole_log_msg = US""; +enum {NOT_TRIED, TMP_REJ, PERM_REJ, ACCEPTED} cutthrough_done = NOT_TRIED; flock_t lock_data; error_block *bad_addresses = NULL; @@ -1306,9 +1503,12 @@ header_line *subject_header = NULL; header_line *msgid_header = NULL; header_line *received_header; +#ifdef EXPERIMENTAL_DMARC +int dmarc_up = 0; +#endif /* EXPERIMENTAL_DMARC */ + /* Variables for use when building the Received: header. */ -uschar *received; uschar *timestamp; int tslen; @@ -1318,6 +1518,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. */ @@ -1343,18 +1549,23 @@ data_fd = -1; spool_name[0] = 0; message_size = 0; warning_count = 0; -received_count = 1; /* For the one we will add */ +received_count = 1; /* For the one we will add */ if (thismessage_size_limit <= 0) thismessage_size_limit = INT_MAX; /* While reading the message, the following counts are computed. */ -message_linecount = body_linecount = body_zerocount = 0; +message_linecount = body_linecount = body_zerocount = + max_received_linelength = 0; -#ifdef EXPERIMENTAL_DOMAINKEYS -/* Call into DK to set up the context. Check if DK is to be run are carried out - inside dk_exim_verify_init(). */ -dk_exim_verify_init(); +#ifndef DISABLE_DKIM +/* Call into DKIM to set up the context. */ +if (smtp_input && !smtp_batched_input && !dkim_disable_verify) dkim_exim_verify_init(); +#endif + +#ifdef EXPERIMENTAL_DMARC +/* initialize libopendmarc */ +dmarc_up = dmarc_init(); #endif /* Remember the time of reception. Exim uses time+pid for uniqueness of message @@ -1405,7 +1616,7 @@ next->text. */ for (;;) { - int ch = (RECEIVE_GETC)(); + int ch = (receive_getc)(); /* If we hit EOF on a SMTP connection, it's an error, since incoming SMTP must have a correct "." terminator. */ @@ -1469,7 +1680,7 @@ for (;;) if (ch == '\n') { if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = FALSE; - else if (first_line_ended_crlf) RECEIVE_UNGETC(' '); + else if (first_line_ended_crlf) receive_ungetc(' '); goto EOL; } @@ -1484,13 +1695,13 @@ for (;;) if (ptr == 0 && ch == '.' && (smtp_input || dot_ends)) { - ch = (RECEIVE_GETC)(); + ch = (receive_getc)(); if (ch == '\r') { - ch = (RECEIVE_GETC)(); + ch = (receive_getc)(); if (ch != '\n') { - RECEIVE_UNGETC(ch); + receive_ungetc(ch); ch = '\r'; /* Revert to CR */ } } @@ -1518,7 +1729,7 @@ for (;;) if (ch == '\r') { - ch = (RECEIVE_GETC)(); + ch = (receive_getc)(); if (ch == '\n') { if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE; @@ -1528,7 +1739,7 @@ for (;;) /* Otherwise, put back the character after CR, and turn the bare CR into LF SP. */ - ch = (RECEIVE_UNGETC)(ch); + ch = (receive_ungetc)(ch); next->text[ptr++] = '\n'; message_size++; ch = ' '; @@ -1585,6 +1796,12 @@ for (;;) receive_linecount++; message_linecount++; + /* Keep track of maximum line length */ + + if (ptr - prevlines_length > max_received_linelength) + max_received_linelength = ptr - prevlines_length; + prevlines_length = ptr + 1; + /* Now put in the terminating newline. There is always space for at least two more characters. */ @@ -1607,14 +1824,14 @@ for (;;) if (ch != EOF) { - int nextch = (RECEIVE_GETC)(); + int nextch = (receive_getc)(); if (nextch == ' ' || nextch == '\t') { next->text[ptr++] = nextch; message_size++; continue; /* Iterate the loop */ } - else if (nextch != EOF) (RECEIVE_UNGETC)(nextch); /* For next time */ + else if (nextch != EOF) (receive_ungetc)(nextch); /* For next time */ else ch = EOF; /* Cause main loop to exit at end */ } @@ -1813,6 +2030,7 @@ for (;;) next->text = store_get(header_size); ptr = 0; had_zero = 0; + prevlines_length = 0; } /* Continue, starting to read the next header */ /* At this point, we have read all the headers into a data structure in main @@ -1869,7 +2087,7 @@ for (h = header_list->next; h != NULL; h = h->next) /* Record whether a Date: or Resent-Date: header exists, as appropriate. */ case htype_date: - date_header_exists = !resents_exist || is_resent; + if (!resents_exist || is_resent) date_header_exists = TRUE; break; /* Same comments as about Return-Path: below. */ @@ -1897,9 +2115,12 @@ for (h = header_list->next; h != NULL; h = h->next) from_header = h; if (!smtp_input) { + int len; uschar *s = Ustrchr(h->text, ':') + 1; while (isspace(*s)) s++; - if (strncmpic(s, originator_login, h->slen - (s - h->text) - 1) == 0) + len = h->slen - (s - h->text) - 1; + if (Ustrlen(originator_login) == len && + strncmpic(s, originator_login, len) == 0) { uschar *name = is_resent? US"Resent-From" : US"From"; header_add(htype_from, "%s: %s <%s@%s>\n", name, originator_name, @@ -2296,10 +2517,13 @@ 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(htype_id, "%sMessage-Id: <%s%s%s@%s>\n", resent_prefix, - message_id_external, (*id_text == 0)? "" : ".", id_text, id_domain); + 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); } /* If we are to log recipients, keep a copy of the raw ones before any possible @@ -2507,13 +2731,13 @@ if (from_header != NULL && if (sender_address_unrewritten == NULL) sender_address_unrewritten = sender_address; sender_address = generated_sender_address; - log_write(L_address_rewrite, LOG_MAIN, - "\"%s\" from env-from rewritten as \"%s\" by submission mode", - sender_address_unrewritten, generated_sender_address); + if (Ustrcmp(sender_address_unrewritten, generated_sender_address) != 0) + log_write(L_address_rewrite, LOG_MAIN, + "\"%s\" from env-from rewritten as \"%s\" by submission mode", + sender_address_unrewritten, generated_sender_address); } } - /* If there are any rewriting rules, apply them to the sender address, unless it has already been rewritten as part of verification for SMTP input. */ @@ -2563,12 +2787,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(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 */ @@ -2593,6 +2820,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(ACL_WHERE_RCPT, 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 @@ -2619,7 +2875,10 @@ if (data_fd < 0) /* Make sure the file's group is the Exim gid, and double-check the mode because the group setting doesn't always get set automatically. */ -(void)fchown(data_fd, exim_uid, exim_gid); +if (fchown(data_fd, exim_uid, exim_gid)) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "Failed setting ownership on spool file %s: %s", + spool_name, strerror(errno)); (void)fchmod(data_fd, SPOOL_MODE); /* We now have data file open. Build a stream for it and lock it. We lock only @@ -2649,7 +2908,7 @@ if (next != NULL) { uschar *s = next->text; int len = next->slen; - (void)fwrite(s, 1, len, data_file); + len = fwrite(s, 1, len, data_file); len = len; /* compiler quietening */ body_linecount++; /* Assumes only 1 line */ } @@ -2674,6 +2933,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; @@ -2686,6 +2946,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: " @@ -2741,6 +3002,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) { @@ -2860,50 +3122,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) - { - 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) +if (received_header->text == NULL) /* Non-cutthrough case */ { - 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_gen(); -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); + /* 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(ACL_WHERE_RCPT, 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 @@ -2928,35 +3165,218 @@ else if (smtp_input && !smtp_batched_input) { -#ifdef EXPERIMENTAL_DOMAINKEYS - dk_exim_verify_finish(); -#endif +#ifndef DISABLE_DKIM + if (!dkim_disable_verify) + { + /* Finish verification, this will log individual signature results to + the mainlog */ + dkim_exim_verify_finish(); + + /* Check if we must run the DKIM ACL */ + if ((acl_smtp_dkim != NULL) && + (dkim_verify_signers != NULL) && + (dkim_verify_signers[0] != '\0')) + { + uschar *dkim_verify_signers_expanded = + expand_string(dkim_verify_signers); + if (dkim_verify_signers_expanded == NULL) + { + log_write(0, LOG_MAIN|LOG_PANIC, + "expansion of dkim_verify_signers option failed: %s", + expand_string_message); + } + else + { + int sep = 0; + uschar *ptr = dkim_verify_signers_expanded; + uschar *item = NULL; + uschar *seen_items = NULL; + 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) + { + /* Prevent running ACL for an empty item */ + if (!item || (item[0] == '\0')) continue; + /* Only run ACL once for each domain or identity, no matter how often it + appears in the expanded list. */ + if (seen_items != NULL) + { + uschar *seen_item = NULL; + uschar seen_item_buf[256]; + uschar *seen_items_list = seen_items; + 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,":"); + } + + seen_items = string_append(seen_items,&seen_items_size,&seen_items_offset,1,item); + seen_items[seen_items_offset] = '\0'; + + DEBUG(D_receive) + debug_printf("calling acl_smtp_dkim for dkim_cur_signer=%s\n", item); + + dkim_exim_acl_setup(item); + rc = acl_check(ACL_WHERE_DKIM, NULL, acl_smtp_dkim, &user_msg, &log_msg); + + if (rc != OK) + { + 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; + } + } + add_acl_headers(ACL_WHERE_DKIM, US"DKIM"); + if (rc == DISCARD) + { + recipients_count = 0; + blackholed_by = US"DKIM ACL"; + if (log_msg != NULL) + blackhole_log_msg = string_sprintf(": %s", log_msg); + } + else if (rc != OK) + { + Uunlink(spool_name); + if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0) + smtp_yield = FALSE; /* No more messsages after dropped connection */ + smtp_reply = US""; /* Indicate reply already sent */ + message_id[0] = 0; /* Indicate no message accepted */ + goto TIDYUP; /* Skip to end of function */ + } + } + } + } +#endif /* DISABLE_DKIM */ #ifdef WITH_CONTENT_SCAN - if (acl_smtp_mime != NULL && + if (recipients_count > 0 && + acl_smtp_mime != NULL && !run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by)) goto TIDYUP; #endif /* WITH_CONTENT_SCAN */ +#ifdef EXPERIMENTAL_DMARC + dmarc_up = dmarc_store_data(from_header); +#endif /* EXPERIMENTAL_DMARC */ + +#ifndef DISABLE_PRDR + if (prdr_requested && recipients_count > 1 && acl_smtp_data_prdr) + { + unsigned int c; + int all_pass = OK; + int all_fail = FAIL; + + smtp_printf("353 PRDR content analysis beginning\r\n"); + /* Loop through recipients, responses must be in same order received */ + for (c = 0; recipients_count > c; c++) + { + uschar * addr= recipients_list[c].address; + uschar * msg= US"PRDR R=<%s> %s"; + uschar * code; + DEBUG(D_receive) + debug_printf("PRDR processing recipient %s (%d of %d)\n", + addr, c+1, recipients_count); + rc = acl_check(ACL_WHERE_PRDR, addr, + acl_smtp_data_prdr, &user_msg, &log_msg); + + /* If any recipient rejected content, indicate it in final message */ + all_pass |= rc; + /* If all recipients rejected, indicate in final message */ + all_fail &= rc; + + switch (rc) + { + case OK: case DISCARD: code = US"250"; break; + case DEFER: code = US"450"; break; + default: code = US"550"; break; + } + if (user_msg != NULL) + smtp_user_msg(code, user_msg); + else + { + switch (rc) + { + case OK: case DISCARD: + msg = string_sprintf(CS msg, addr, "acceptance"); break; + case DEFER: + msg = string_sprintf(CS msg, addr, "temporary refusal"); break; + default: + msg = string_sprintf(CS msg, addr, "refusal"); break; + } + smtp_user_msg(code, msg); + } + if (log_msg) log_write(0, LOG_MAIN, "PRDR %s %s", addr, log_msg); + else if (user_msg) log_write(0, LOG_MAIN, "PRDR %s %s", addr, user_msg); + else log_write(0, LOG_MAIN, "%s", CS msg); + + if (rc != OK) { receive_remove_recipient(addr); c--; } + } + /* Set up final message, used if data acl gives OK */ + smtp_reply = string_sprintf("%s id=%s message %s", + all_fail == FAIL ? US"550" : US"250", + message_id, + all_fail == FAIL + ? US"rejected for all recipients" + : all_pass == OK + ? US"accepted" + : US"accepted for some recipients"); + if (recipients_count == 0) + { + message_id[0] = 0; /* Indicate no message accepted */ + goto TIDYUP; + } + } + else + prdr_requested = FALSE; +#endif /* !DISABLE_PRDR */ + /* Check the recipients count again, as the MIME ACL might have changed them. */ if (acl_smtp_data != NULL && recipients_count > 0) { rc = acl_check(ACL_WHERE_DATA, NULL, acl_smtp_data, &user_msg, &log_msg); - add_acl_headers(US"DATA"); + add_acl_headers(ACL_WHERE_DATA, US"DATA"); if (rc == DISCARD) { recipients_count = 0; 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 */ @@ -2996,6 +3416,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. */ @@ -3019,7 +3442,7 @@ else /* Does not return */ } } - add_acl_headers(US"non-SMTP"); + add_acl_headers(ACL_WHERE_NOTSMTP, US"non-SMTP"); } } @@ -3033,6 +3456,11 @@ else unspool_mbox(); #endif +#ifdef EXPERIMENTAL_DCC +dcc_ok = 0; +#endif + + /* The final check on the message is to run the scan_local() function. The 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 @@ -3201,6 +3629,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; @@ -3315,26 +3744,53 @@ 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_certificate_verified) != 0 && - tls_cipher != NULL) +if (log_extra_selector & LX_tls_cipher && tls_in.cipher) + s = string_append(s, &size, &sptr, 2, US" X=", tls_in.cipher); +if (log_extra_selector & LX_tls_certificate_verified && tls_in.cipher) 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) - s = string_append(s, &size, &sptr, 3, US" DN=\"", tls_peerdn, US"\""); + tls_in.certificate_verified? "yes":"no"); +if (log_extra_selector & LX_tls_peerdn && tls_in.peerdn) + s = string_append(s, &size, &sptr, 3, US" DN=\"", + string_printing(tls_in.peerdn), US"\""); +if (log_extra_selector & LX_tls_sni && tls_in.sni) + s = string_append(s, &size, &sptr, 3, US" SNI=\"", + string_printing(tls_in.sni), US"\""); #endif -if (sender_host_authenticated != NULL) +if (sender_host_authenticated) { s = string_append(s, &size, &sptr, 2, US" A=", sender_host_authenticated); if (authenticated_id != NULL) + { s = string_append(s, &size, &sptr, 2, US":", authenticated_id); + if (log_extra_selector & LX_smtp_mailauth && authenticated_sender != NULL) + s = string_append(s, &size, &sptr, 2, US":", authenticated_sender); + } } +#ifndef DISABLE_PRDR +if (prdr_requested) + s = string_append(s, &size, &sptr, 1, US" PRDR"); +#endif + +#ifdef EXPERIMENTAL_PROXY +if (proxy_session && log_extra_selector & LX_proxy) + s = string_append(s, &size, &sptr, 2, US" PRX=", proxy_host_address); +#endif + 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. @@ -3382,7 +3838,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) @@ -3466,8 +3922,8 @@ 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)(); - if (c != EOF) (RECEIVE_UNGETC)(c); else + int c = (receive_getc)(); + if (c != EOF) (receive_ungetc)(c); else { uschar *msg = US"SMTP connection lost after final dot"; smtp_reply = US""; /* No attempt to send a response */ @@ -3503,17 +3959,58 @@ 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. -/* Log any control actions taken by an ACL or local_scan(). */ + 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. -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); + Having the normal spool files lets us do data-filtering, and store/forward on temp-reject. + + 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 = ACCEPTED; + 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 = TMP_REJ; /* 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 = PERM_REJ; + break; + } + } + +#ifndef DISABLE_PRDR +if(!smtp_reply || prdr_requested) +#else +if(!smtp_reply) +#endif + { + 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 */ @@ -3541,6 +4038,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 */ @@ -3601,6 +4099,25 @@ if (smtp_input) else smtp_printf("%.1024s\r\n", smtp_reply); } + + switch (cutthrough_done) + { + case ACCEPTED: log_write(0, LOG_MAIN, "Completed");/* Delivery was done */ + case PERM_REJ: { /* 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 TMP_REJ: 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