X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/4596719398f6f2365bed563aafd757a6433ce7b4..f2738aab2d72569b6d47b788099f6ebab701b2b2:/src/src/receive.c diff --git a/src/src/receive.c b/src/src/receive.c index e8bd80596..a56ff473e 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -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 " @@ -829,14 +828,20 @@ 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) { enum { s_linestart, s_normal, s_had_cr, s_had_nl_dot, s_had_dot_cr } ch_state = s_linestart; @@ -863,14 +868,17 @@ while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF) ch_state = s_had_cr; continue; /* Don't write the CR */ } - if (ch == '\n') /* Bare NL ends line */ - { - ch_state = s_linestart; - body_linecount++; - if (linelength > max_received_linelength) - max_received_linelength = linelength; - linelength = -1; - } + 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 */ @@ -893,8 +901,11 @@ while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF) case s_had_nl_dot: /* After [CR] LF . */ if (ch == '\n') /* [CR] LF . LF */ - return END_DOT; - if (ch == '\r') /* [CR] LF . CR */ + 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 */ @@ -902,7 +913,7 @@ while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF) /* 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. */ - if (ch == '.') + else if (ch == '.') { uschar c = ch; cutthrough_data_puts(&c, 1); @@ -1140,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); } @@ -1192,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; @@ -1560,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; @@ -2187,8 +2201,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); @@ -2610,12 +2625,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; + uschar * s = Ustrchr(h->text, ':') + 1; while (isspace(*s)) 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; @@ -2623,7 +2638,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 */ @@ -2807,6 +2822,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); @@ -2828,6 +2844,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); @@ -3057,7 +3074,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 */ @@ -3084,7 +3101,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))) @@ -3123,9 +3140,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 @@ -3241,7 +3260,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); @@ -3501,6 +3520,7 @@ else dkim_exim_verify_finish(); /* Check if we must run the DKIM ACL */ + GET_OPTION("acl_smtp_dkim"); if (acl_smtp_dkim && dkim_verify_signers && *dkim_verify_signers) { uschar * dkim_verify_signers_expanded = @@ -3590,11 +3610,14 @@ else #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 @@ -3602,67 +3625,73 @@ else #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++) - { - 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: 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; @@ -3671,6 +3700,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); @@ -3708,6 +3738,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) @@ -3715,9 +3746,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) @@ -4040,7 +4072,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);