X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/51894f20e0c03113b4c4e07898ac5e955b21ec41..HEAD:/src/src/receive.c diff --git a/src/src/receive.c b/src/src/receive.c index 8190c5941..ae4e1ff7e 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -17,7 +17,7 @@ extern int dcc_ok; #endif #ifdef SUPPORT_DMARC -# include "dmarc.h" +# include "miscmods/dmarc.h" #endif /************************************************* @@ -137,9 +137,9 @@ Returns: TRUE for a trusted caller */ BOOL -receive_check_set_sender(uschar *newsender) +receive_check_set_sender(const uschar * newsender) { -uschar *qnewsender; +const uschar * qnewsender; if (f.trusted_caller) return TRUE; if (!newsender || !untrusted_set_sender) return FALSE; qnewsender = Ustrchr(newsender, '@') @@ -290,12 +290,11 @@ Returns: FALSE if there isn't enough space, or if the information cannot BOOL receive_check_fs(int msg_size) { -int_eximarith_t space; int inodes; if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0) { - space = receive_statvfs(TRUE, &inodes); + int_eximarith_t space = receive_statvfs(TRUE, &inodes); DEBUG(D_receive) debug_printf("spool directory space = " PR_EXIM_ARITH "K inodes = %d " @@ -313,7 +312,7 @@ if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0) if (check_log_space > 0 || check_log_inodes > 0) { - space = receive_statvfs(FALSE, &inodes); + int_eximarith_t space = receive_statvfs(FALSE, &inodes); DEBUG(D_receive) debug_printf("log directory space = " PR_EXIM_ARITH "K inodes = %d " @@ -514,7 +513,7 @@ Returns: nothing */ void -receive_add_recipient(uschar * recipient, int pno) +receive_add_recipient(const uschar * recipient, int pno) { if (recipients_count >= recipients_list_max) { @@ -591,7 +590,7 @@ Returns: TRUE if it did remove something; FALSE otherwise */ BOOL -receive_remove_recipient(uschar *recipient) +receive_remove_recipient(const uschar * recipient) { DEBUG(D_receive) debug_printf("receive_remove_recipient(\"%s\") called\n", recipient); @@ -829,100 +828,114 @@ July 2003: Bare CRs cause trouble. We now treat them as line terminators as well, so that there are no CRs in spooled messages. However, the message terminating dot is not recognized between two bare CRs. +Dec 2023: getting a site to send a body including an "LF . LF" sequence +followed by SMTP commands is a possible "smtp smuggling" attack. If +the first (header) line for the message has a proper CRLF then enforce +that for the body: convert bare LF to a space. + 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 + strict_crlf require full CRLF sequence as a line ending Returns: One of the END_xxx values indicating why it stopped reading */ static int -read_message_data_smtp(FILE *fout) +read_message_data_smtp(FILE * fout, BOOL strict_crlf) { -int ch_state = 0; -int ch; -int linelength = 0; +enum { s_linestart, s_normal, s_had_cr, s_had_nl_dot, s_had_dot_cr } ch_state = + s_linestart; +int linelength = 0, ch; while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF) { if (ch == 0) body_zerocount++; switch (ch_state) { - case 0: /* After LF or CRLF */ - if (ch == '.') - { - ch_state = 3; - continue; /* Don't ever write . after LF */ - } - ch_state = 1; + case s_linestart: /* After LF or CRLF */ + if (ch == '.') + { + ch_state = s_had_nl_dot; + continue; /* Don't ever write . after LF */ + } + ch_state = s_normal; - /* Else fall through to handle as normal uschar. */ + /* Else fall through to handle as normal uschar. */ - case 1: /* Normal state */ - if (ch == '\n') - { - ch_state = 0; - body_linecount++; + case s_normal: /* Normal state */ + if (ch == '\r') + { + ch_state = s_had_cr; + continue; /* Don't write the CR */ + } + if (ch == '\n') /* Bare LF at end of line */ + if (strict_crlf) + ch = ' '; /* replace LF with space */ + else + { /* treat as line ending */ + ch_state = s_linestart; + body_linecount++; + if (linelength > max_received_linelength) + max_received_linelength = linelength; + linelength = -1; + } + break; + + case s_had_cr: /* After (unwritten) CR */ + body_linecount++; /* Any char ends line */ if (linelength > max_received_linelength) - max_received_linelength = linelength; + max_received_linelength = linelength; linelength = -1; - } - else if (ch == '\r') - { - ch_state = 2; - continue; - } - break; + if (ch == '\n') /* proper CRLF */ + ch_state = s_linestart; + else + { + message_size++; /* convert the dropped CR to a stored NL */ + if (fout && fputc('\n', fout) == EOF) return END_WERROR; + cutthrough_data_put_nl(); + if (ch == '\r') /* CR; do not write */ + continue; + ch_state = s_normal; /* not LF or CR; process as standard */ + } + break; - case 2: /* After (unwritten) CR */ - body_linecount++; - if (linelength > max_received_linelength) - max_received_linelength = linelength; - linelength = -1; - if (ch == '\n') - { - ch_state = 0; - } - else - { - message_size++; - if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR; - cutthrough_data_put_nl(); - if (ch != '\r') ch_state = 1; else continue; - } - break; + case s_had_nl_dot: /* After [CR] LF . */ + if (ch == '\n') /* [CR] LF . LF */ + if (strict_crlf) + ch = ' '; /* replace LF with space */ + else + return END_DOT; + else if (ch == '\r') /* [CR] LF . CR */ + { + ch_state = s_had_dot_cr; + continue; /* Don't write the CR */ + } + /* The dot was removed on reaching s_had_nl_dot. For a doubled dot, here, + reinstate it to cutthrough. The current ch, dot or not, is passed both to + cutthrough and to file below. */ + else if (ch == '.') + { + uschar c = ch; + cutthrough_data_puts(&c, 1); + } + ch_state = s_normal; + break; - case 3: /* After [CR] LF . */ - if (ch == '\n') - return END_DOT; - if (ch == '\r') - { - ch_state = 4; - continue; - } - /* The dot was removed at state 3. For a doubled dot, here, reinstate - it to cutthrough. The current ch, dot or not, is passed both to cutthrough - and to file below. */ - if (ch == '.') - { - uschar c= ch; - cutthrough_data_puts(&c, 1); - } - ch_state = 1; - break; + case s_had_dot_cr: /* After [CR] LF . CR */ + if (ch == '\n') + return END_DOT; /* Preferred termination */ - case 4: /* After [CR] LF . CR */ - if (ch == '\n') return END_DOT; - message_size++; - body_linecount++; - if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR; - cutthrough_data_put_nl(); - if (ch == '\r') - { - ch_state = 2; - continue; - } - ch_state = 1; - break; + message_size++; /* convert the dropped CR to a stored NL */ + body_linecount++; + if (fout && fputc('\n', fout) == EOF) return END_WERROR; + cutthrough_data_put_nl(); + if (ch == '\r') + { + ch_state = s_had_cr; + continue; /* CR; do not write */ + } + ch_state = s_normal; + break; } /* Add the character to the spool file, unless skipping; then loop for the @@ -1138,7 +1151,7 @@ receive_swallow_smtp(void) { if (message_ended >= END_NOTENDED) message_ended = chunking_state <= CHUNKING_OFFERED - ? read_message_data_smtp(NULL) + ? read_message_data_smtp(NULL, FALSE) : read_message_bdat_smtp_wire(NULL); } @@ -1190,6 +1203,8 @@ static void give_local_error(int errcode, uschar *text1, uschar *text2, int error_rc, FILE *f, header_line *hptr) { +DEBUG(D_all) debug_printf("%s%s\n", text2, text1); + if (error_handling == ERRORS_SENDER) { error_block eblock; @@ -1380,7 +1395,7 @@ if (f.tcp_in_fastopen && !f.tcp_in_fastopen_logged) if (sender_ident) g = string_append(g, 2, US" U=", sender_ident); if (LOGGING(connection_id)) - g = string_fmt_append(g, " Ci=%lu", connection_id); + g = string_fmt_append(g, " Ci=%s", connection_id); if (received_protocol) g = string_append(g, 2, US" P=", received_protocol); if (LOGGING(pipelining) && f.smtp_in_pipelining_advertised) @@ -1558,6 +1573,7 @@ uschar * timestamp = expand_string(US"${tod_full}"); header_line * received_header= header_list; if (recipients_count == 1) received_for = recipients_list[0].address; +GET_OPTION("received_header_text"); received = expand_string(received_header_text); received_for = NULL; @@ -1732,7 +1748,7 @@ uschar *user_msg, *log_msg; /* Working header pointers */ rmark reset_point; -header_line *next; +header_line * next; /* Flags for noting the existence of certain headers (only one left) */ @@ -1740,15 +1756,16 @@ BOOL date_header_exists = FALSE; /* Pointers to receive the addresses of headers whose contents we need. */ -header_line *from_header = NULL; -header_line *subject_header = NULL; -header_line *msgid_header = NULL; -header_line *received_header; +header_line * from_header = NULL; +#ifdef SUPPORT_DMARC +header_line * dmarc_from_header = NULL; +#endif +header_line * subject_header = NULL, * msgid_header = NULL, * received_header; BOOL msgid_header_newly_created = FALSE; /* Variables for use when building the Received: header. */ -uschar *timestamp; +uschar * timestamp; int tslen; /* Time of creation of message_id */ @@ -1811,16 +1828,8 @@ mime_is_rfc822 = 0; mime_part_count = -1; #endif -#ifndef DISABLE_DKIM -/* Call into DKIM to set up the context. In CHUNKING mode -we clear the dot-stuffing flag */ -if (smtp_input && !smtp_batched_input && !f.dkim_disable_verify) - dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED); -#endif - -#ifdef SUPPORT_DMARC -if (sender_host_address) dmarc_init(); /* initialize libopendmarc */ -#endif +if (misc_mod_msg_init() != OK) + goto TIDYUP; /* 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 @@ -1960,8 +1969,10 @@ for (;;) if (ch == '\n') { - if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = FALSE; - else if (first_line_ended_crlf) receive_ungetc(' '); + if (first_line_ended_crlf == TRUE_UNSET) + first_line_ended_crlf = FALSE; + else if (first_line_ended_crlf) + receive_ungetc(' '); goto EOL; } @@ -1970,14 +1981,20 @@ for (;;) This implements the dot-doubling rule, though header lines starting with dots aren't exactly common. They are legal in RFC 822, though. If the following is CRLF or LF, this is the line that that terminates the + entire message. We set message_ended to indicate this has happened (to prevent further reading), and break out of the loop, having freed the empty header, and set next = NULL to indicate no data line. */ if (f.dot_ends && ptr == 0 && ch == '.') { + /* leading dot while in headers-read mode */ ch = (receive_getc)(GETC_BUFFER_UNLIMITED); - if (ch == '\r') + if (ch == '\n' && first_line_ended_crlf == TRUE /* and not TRUE_UNSET */ ) + /* dot, LF but we are in CRLF mode. Attack? */ + ch = ' '; /* replace the LF with a space */ + + else if (ch == '\r') { ch = (receive_getc)(GETC_BUFFER_UNLIMITED); if (ch != '\n') @@ -2013,7 +2030,8 @@ for (;;) ch = (receive_getc)(GETC_BUFFER_UNLIMITED); if (ch == '\n') { - if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE; + if (first_line_ended_crlf == TRUE_UNSET) + first_line_ended_crlf = TRUE; goto EOL; } @@ -2176,8 +2194,9 @@ OVERSIZE: { if (!f.sender_address_forced) { - uschar *uucp_sender = expand_string(uucp_from_sender); - if (!uucp_sender) + uschar * uucp_sender; + GET_OPTION("uucp_from_sender"); + if (!(uucp_sender = expand_string(uucp_from_sender))) log_write(0, LOG_MAIN|LOG_PANIC, "expansion of \"%s\" failed after matching " "\"From \" line: %s", uucp_from_sender, expand_string_message); @@ -2225,8 +2244,7 @@ OVERSIZE: if (isspace(*p)) break; while (mac_isgraph(*p) && *p != ':') p++; - while (isspace(*p)) p++; - if (*p != ':') + if (Uskip_whitespace(&p) != ':') { body_zerocount = had_zero; break; @@ -2421,19 +2439,23 @@ for (header_line * h = header_list->next; h; h = h->next) case htype_from: h->type = htype_from; +#ifdef SUPPORT_DMARC + if (!is_resent) dmarc_from_header = h; +#endif if (!resents_exist || is_resent) { from_header = h; if (!smtp_input) { int len; - uschar *s = Ustrchr(h->text, ':') + 1; - while (isspace(*s)) s++; + uschar * s = Ustrchr(h->text, ':') + 1; + + Uskip_whitespace(&s); 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"; + uschar * name = is_resent ? US"Resent-From" : US"From"; header_add(htype_from, "%s: %s <%s@%s>\n", name, originator_name, originator_login, qualify_domain_sender); from_header = header_last; @@ -2488,15 +2510,13 @@ for (header_line * h = header_list->next; h; h = h->next) if (filter_test != FTEST_NONE) { - uschar *start = h->text + 12; - uschar *end = start + Ustrlen(start); - while (isspace(*start)) start++; + uschar * start = h->text + 12; + uschar * end = start + Ustrlen(start); + + Uskip_whitespace(&start); while (end > start && isspace(end[-1])) end--; if (*start == '<' && end[-1] == '>') - { - start++; - end--; - } + { start++; end--; } return_path = string_copyn(start, end - start); printf("Return-path taken from \"Return-path:\" header line\n"); } @@ -2599,12 +2619,12 @@ if (extract_recip) if ((h->type == htype_to || h->type == htype_cc || h->type == htype_bcc) && (!contains_resent_headers || strncmpic(h->text, US"resent-", 7) == 0)) { - uschar *s = Ustrchr(h->text, ':') + 1; - while (isspace(*s)) s++; + uschar * s = Ustrchr(h->text, ':') + 1; + Uskip_whitespace(&s); f.parse_allow_group = TRUE; /* Allow address group syntax */ - while (*s != 0) + while (*s) { uschar *ss = parse_find_address_end(s, FALSE); uschar *recipient, *errmess, *pp; @@ -2612,7 +2632,7 @@ if (extract_recip) /* Check on maximum */ - if (recipients_max > 0 && ++rcount > recipients_max) + if (recipients_max_expanded > 0 && ++rcount > recipients_max_expanded) give_local_error(ERRMESS_TOOMANYRECIP, US"too many recipients", US"message rejected: ", error_rc, stdin, NULL); /* Does not return */ @@ -2680,7 +2700,7 @@ if (extract_recip) /* Move on past this address */ s = ss + (*ss ? 1 : 0); - while (isspace(*s)) s++; + Uskip_whitespace(&s); } /* Next address */ f.parse_allow_group = FALSE; /* Reset group syntax flags */ @@ -2796,6 +2816,7 @@ if ( !msgid_header /* Permit only letters, digits, dots, and hyphens in the domain */ + GET_OPTION("message_id_header_domain"); if (message_id_domain) { uschar *new_id_domain = expand_string(message_id_domain); @@ -2817,6 +2838,7 @@ if ( !msgid_header /* Permit all characters except controls and RFC 2822 specials in the additional text part. */ + GET_OPTION("message_id_header_text"); if (message_id_text) { uschar *new_id_text = expand_string(message_id_text); @@ -3046,7 +3068,7 @@ if ( from_header it has already been rewritten as part of verification for SMTP input. */ DEBUG(D_rewrite) - { debug_printf("global rewrite rules\n"); acl_level++; } + { debug_printf("rewrite rules on sender address\n"); acl_level++; } if (global_rewrite_rules && !sender_address_unrewritten && *sender_address) { /* deconst ok as src was not const */ @@ -3073,7 +3095,7 @@ documented as happening *after* recipient addresses are taken from the headers by the -t command line option. An added Sender: gets rewritten here. */ DEBUG(D_rewrite) - { debug_printf("rewrite headers\n"); acl_level++; } + { debug_printf("qualify and rewrite headers\n"); acl_level++; } for (header_line * h = header_list->next, * newh; h; h = h->next) if ((newh = rewrite_header(h, NULL, NULL, global_rewrite_rules, rewrite_existflags, TRUE))) @@ -3112,9 +3134,11 @@ new Received:) has not yet been set. */ DEBUG(D_receive) { debug_printf(">>Headers after rewriting and local additions:\n"); + acl_level++; for (header_line * h = header_list->next; h; h = h->next) - debug_printf("%c %s", h->type, h->text); + debug_printf_indent("%c %s", h->type, h->text); debug_printf("\n"); + acl_level--; } /* The headers are now complete in store. If we are running in filter @@ -3161,7 +3185,7 @@ if (cutthrough.cctx.sock >= 0 && cutthrough.delivery) /* 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 +to access it both via a file descriptor and a stdio stream. Try to make the directory if it isn't there. */ spool_name = spool_fname(US"input", message_subdir, message_id, US"-D"); @@ -3230,7 +3254,7 @@ if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT) if (smtp_input) { message_ended = chunking_state <= CHUNKING_OFFERED - ? read_message_data_smtp(spool_data_file) + ? read_message_data_smtp(spool_data_file, first_line_ended_crlf) : spool_wireformat ? read_message_bdat_smtp_wire(spool_data_file) : read_message_bdat_smtp(spool_data_file); @@ -3380,8 +3404,8 @@ if (extract_recip && (bad_addresses || recipients_count == 0)) } } - log_write(0, LOG_MAIN|LOG_PANIC, "%s %s found in headers", - message_id, bad_addresses ? "bad addresses" : "no recipients"); + log_write(0, LOG_MAIN|LOG_PANIC, "%s found in headers", + bad_addresses ? "bad addresses" : "no recipients"); fseek(spool_data_file, (long int)spool_data_start_offset(message_id), SEEK_SET); @@ -3486,172 +3510,140 @@ else #ifndef DISABLE_DKIM if (!f.dkim_disable_verify) { - /* Finish verification */ - dkim_exim_verify_finish(); + misc_module_info * mi = misc_mod_findonly(US"dkim"); + if (mi) + { + typedef void (*vfin_fn_t)(void); + typedef int (*vacl_fn_t)(uschar **, uschar**); + typedef void (*vlog_fn_t)(void); - /* Check if we must run the DKIM ACL */ - if (acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers) - { - uschar * dkim_verify_signers_expanded = - expand_string(dkim_verify_signers); - gstring * results = NULL; - int signer_sep = 0; - const uschar * ptr; - uschar * item; - gstring * seen_items = NULL; - int old_pool = store_pool; - - store_pool = POOL_PERM; /* Allow created variables to live to data ACL */ - - if (!(ptr = dkim_verify_signers_expanded)) - log_write(0, LOG_MAIN|LOG_PANIC, - "expansion of dkim_verify_signers option failed: %s", - expand_string_message); - - /* Default to OK when no items are present */ - rc = OK; - while ((item = string_nextinlist(&ptr, &signer_sep, NULL, 0))) - { - /* Prevent running ACL for an empty item */ - if (!item || !*item) continue; + /* Finish off the body hashes, calculate sigs and do compares */ - /* Only run ACL once for each domain or identity, - no matter how often it appears in the expanded list. */ - if (seen_items) - { - uschar * seen_item; - const uschar * seen_items_list = string_from_gstring(seen_items); - int seen_sep = ':'; - BOOL seen_this_item = FALSE; - - while ((seen_item = string_nextinlist(&seen_items_list, &seen_sep, - NULL, 0))) - if (Ustrcmp(seen_item,item) == 0) - { - seen_this_item = TRUE; - break; - } - - if (seen_this_item) - { - DEBUG(D_receive) - debug_printf("acl_smtp_dkim: skipping signer %s, " - "already seen\n", item); - continue; - } + (((vfin_fn_t *) mi->functions)[DKIM_VERIFY_FINISH]) (); - seen_items = string_catn(seen_items, US":", 1); - } - seen_items = string_cat(seen_items, item); + /* Check if we must run the DKIM ACL */ + + GET_OPTION("acl_smtp_dkim"); + if (acl_smtp_dkim) + { + rc = (((vacl_fn_t *) mi->functions)[DKIM_ACL_ENTRY]) + (&user_msg, &log_msg); + add_acl_headers(ACL_WHERE_DKIM, US"DKIM"); - rc = dkim_exim_acl_run(item, &results, &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(TRUE, US"dkim acl not ok"); - break; + + if (rc != DISCARD) + { + Uunlink(spool_name); + if (smtp_handle_acl_fail(ACL_WHERE_DKIM, rc, user_msg, log_msg) != 0) + smtp_yield = FALSE; /* No more msgs after dropped conn */ + smtp_reply = US""; /* Indicate reply already sent */ + goto NOT_ACCEPTED; /* Skip to end of function */ + } + recipients_count = 0; + blackholed_by = US"DKIM ACL"; + if (log_msg) + blackhole_log_msg = string_sprintf(": %s", log_msg); } } - dkim_verify_status = string_from_gstring(results); - store_pool = old_pool; - add_acl_headers(ACL_WHERE_DKIM, US"DKIM"); - if (rc == DISCARD) - { - recipients_count = 0; - blackholed_by = US"DKIM ACL"; - if (log_msg) - 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 messages after dropped connection */ - smtp_reply = US""; /* Indicate reply already sent */ - goto NOT_ACCEPTED; /* Skip to end of function */ - } - } - else - dkim_exim_verify_log_all(); + else /* No ACL; just log */ + (((vlog_fn_t *) mi->functions)[DKIM_VERIFY_LOG_ALL]) (); + } } #endif /* DISABLE_DKIM */ #ifdef WITH_CONTENT_SCAN - if ( recipients_count > 0 - && acl_smtp_mime - && !run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by) - ) - goto TIDYUP; + if (recipients_count > 0) + { + GET_OPTION("acl_smtp_mime"); + if (acl_smtp_mime + && !run_mime_acl(acl_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by) + ) + goto TIDYUP; + } #endif /* WITH_CONTENT_SCAN */ #ifdef SUPPORT_DMARC - dmarc_store_data(from_header); + { + misc_module_info * mi = misc_mod_findonly(US"dmarc"); + if (mi) + { + typedef int (*fn_t)(header_line *); + (((fn_t *) mi->functions)[DMARC_STORE_DATA]) (dmarc_from_header); + } + } #endif #ifndef DISABLE_PRDR - if (prdr_requested && recipients_count > 1 && acl_smtp_data_prdr) + if (prdr_requested && recipients_count > 1) { - int all_pass = OK; - int all_fail = FAIL; + GET_OPTION("acl_smtp_data_prdr"); + if (acl_smtp_data_prdr) + { + int all_pass = OK; + int all_fail = FAIL; - smtp_printf("353 PRDR content analysis beginning\r\n", SP_MORE); - /* Loop through recipients, responses must be in same order received */ - for (unsigned int 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 + smtp_printf("353 PRDR content analysis beginning\r\n", SP_MORE); + /* Loop through recipients, responses must be in same order received */ + for (unsigned int c = 0; recipients_count > c; c++) { + const 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: - 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); + { + 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) - goto NOT_ACCEPTED; + 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) + goto NOT_ACCEPTED; + } + else + prdr_requested = FALSE; } else prdr_requested = FALSE; @@ -3660,6 +3652,7 @@ else /* Check the recipients count again, as the MIME ACL might have changed them. */ + GET_OPTION("acl_smtp_data"); if (acl_smtp_data && recipients_count > 0) { rc = acl_check(ACL_WHERE_DATA, NULL, acl_smtp_data, &user_msg, &log_msg); @@ -3697,6 +3690,7 @@ else { #ifdef WITH_CONTENT_SCAN + GET_OPTION("acl_not_smtp_mime"); if ( acl_not_smtp_mime && !run_mime_acl(acl_not_smtp_mime, &smtp_yield, &smtp_reply, &blackholed_by) @@ -3704,9 +3698,10 @@ else goto TIDYUP; #endif /* WITH_CONTENT_SCAN */ + GET_OPTION("acl_not_smtp"); if (acl_not_smtp) { - uschar *user_msg, *log_msg; + uschar * user_msg, * log_msg; f.authentication_local = TRUE; rc = acl_check(ACL_WHERE_NOTSMTP, NULL, acl_not_smtp, &user_msg, &log_msg); if (rc == DISCARD) @@ -4029,7 +4024,15 @@ else receive_messagecount++; -if (fflush(spool_data_file)) +if ( fflush(spool_data_file) +#if _POSIX_C_SOURCE >= 199309L || _XOPEN_SOURCE >= 500 +# ifdef ENABLE_DISABLE_FSYNC + || !disable_fsync && fdatasync(data_fd) +# else + || fdatasync(data_fd) +# endif +#endif + ) { errmsg = string_sprintf("Spool write error: %s", strerror(errno)); log_write(0, LOG_MAIN, "%s\n", errmsg); @@ -4126,12 +4129,22 @@ if (LOGGING(8bitmime)) g = string_fmt_append(g, " M8S=%d", body_8bitmime); #ifndef DISABLE_DKIM -if (LOGGING(dkim) && dkim_verify_overall) - g = string_append(g, 2, US" DKIM=", dkim_verify_overall); +if (LOGGING(dkim)) + { + misc_module_info * mi = misc_mod_findonly(US"dkim"); + typedef gstring * (*fn_t)(gstring *); + if (mi) + g = (((fn_t *) mi->functions)[DKIM_VDOM_FIRSTPASS]) (g); + # ifdef EXPERIMENTAL_ARC -if (LOGGING(dkim) && arc_state && Ustrcmp(arc_state, "pass") == 0) - g = string_catn(g, US" ARC", 4); + { + mi = misc_mod_findonly(US"arc"); + typedef BOOL (*fn_t)(void); + if (mi && (((fn_t *) mi->functions)[ARC_STATE_IS_PASS]) ()) + g = string_catn(g, US" ARC", 4); + } # endif + } #endif if (LOGGING(receive_time)) @@ -4391,7 +4404,7 @@ data file will be flushed(!) out thereby. Nevertheless, it is theoretically possible for fclose() to fail - and this has been seen on obscure filesystems (probably one that delayed the actual media write as long as possible) but what to do? What has happened to the lock if this happens? -It's a mes because we already logged the acceptance. +It's a mess because we already logged the acceptance. We can at least log the issue, try to remove spoolfiles and respond with a temp-reject. We do not want to close before logging acceptance because we want to hold the lock until we know that logging worked. @@ -4425,8 +4438,8 @@ if (spool_data_file && cutthrough_done == NOT_TRIED) Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H")); Uunlink(spool_fname(US"msglog", message_subdir, message_id, US"")); - /* Claim a data ACL temp-reject, just to get reject logging and resposponse */ - smtp_handle_acl_fail(ACL_WHERE_DATA, rc, NULL, log_msg); + /* Claim a data ACL temp-reject, just to get reject logging and response */ + if (smtp_input) smtp_handle_acl_fail(ACL_WHERE_DATA, rc, NULL, log_msg); smtp_reply = US""; /* Indicate reply already sent */ message_id[0] = 0; /* no message accepted */ @@ -4573,3 +4586,5 @@ return yield; /* TRUE if more messages (SMTP only) */ } /* End of receive.c */ +/* vi: se aw ai sw=2 +*/