* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* Copyright (c) The Exim Maintainers 2020 - 2023 */
/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* SPDX-License-Identifier: GPL-2.0-or-later */
#endif
#ifdef SUPPORT_DMARC
-# include "dmarc.h"
+# include "miscmods/dmarc.h"
#endif
/*************************************************
*/
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, '@')
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 "
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 "
*/
void
-receive_add_recipient(uschar * recipient, int pno)
+receive_add_recipient(const uschar * recipient, int pno)
{
if (recipients_count >= recipients_list_max)
{
*/
BOOL
-receive_remove_recipient(uschar *recipient)
+receive_remove_recipient(const uschar * recipient)
{
DEBUG(D_receive) debug_printf("receive_remove_recipient(\"%s\") called\n",
recipient);
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
{
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);
}
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;
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)
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;
/* Working header pointers */
rmark reset_point;
-header_line *next;
+header_line * next;
/* Flags for noting the existence of certain headers (only one left) */
/* 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 */
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
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;
}
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')
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;
}
{
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);
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;
DEBUG(D_receive)
{
debug_printf(">>Headers received:\n");
+ acl_level++;
for (header_line * h = header_list->next; h; h = h->next)
- debug_printf("%s", h->text);
+ debug_printf_indent("%s", h->text);
debug_printf("\n");
+ acl_level--;
}
/* End of file on any SMTP connection is an error. If an incoming SMTP call
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;
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");
}
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;
/* 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 */
/* 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 */
/* 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);
/* 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);
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 */
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)))
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
/* 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");
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);
{
Uunlink(spool_name); /* Lose data file when closed */
cancel_cutthrough_connection(TRUE, US"sender closed connection");
- message_id[0] = 0; /* Indicate no message_accepted */
smtp_reply = handle_lost_connection(US"");
smtp_yield = FALSE;
- goto TIDYUP; /* Skip to end of function */
+ goto NOT_ACCEPTED; /* Skip to end of function */
}
break;
}
}
- 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);
#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;
/* Check the recipients count again, as the MIME ACL might have changed
them. */
- if (acl_smtp_data != NULL && recipients_count > 0)
+ 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);
add_acl_headers(ACL_WHERE_DATA, US"DATA");
{
#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)
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)
receive_messagecount++;
-/* Add data size to written header size. We do not count the initial file name
-that is in the file, but we do add one extra for the notional blank line that
-precedes the data. This total differs from message_size in that it include the
-added Received: header and any other headers that got created locally. */
-
-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);
/* Does not return */
}
}
-fstat(data_fd, &statbuf);
+/* Add data size to written header size. We do not count the initial file name
+that is in the file, but we do add one extra for the notional blank line that
+precedes the data. This total differs from message_size in that it include the
+added Received: header and any other headers that got created locally. */
+
+fstat(data_fd, &statbuf);
msg_size += statbuf.st_size - spool_data_start_offset(message_id) + 1;
/* Generate a "message received" log entry. We do this by building up a dynamic
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))
#endif
{
log_write(0, LOG_MAIN |
- (LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
- (LOGGING(received_sender) ? LOG_SENDER : 0),
- "%Y", g);
+ (LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
+ (LOGGING(received_sender) ? LOG_SENDER : 0),
+ "%Y", g);
/* Log any control actions taken by an ACL or local_scan(). */
A fflush() was done earlier in the expectation that any write errors on the
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? We can at least log it; if it is observed on some platform
-then we can think about properly declaring the message not-received. */
+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 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.
+Could we make this less likely by doing an fdatasync() just after the fflush()?
+That seems like a good thing on data-security grounds, but how much will it hit
+performance? */
goto TIDYUP;
if (spool_data_file && cutthrough_done == NOT_TRIED)
{
if (fclose(spool_data_file)) /* Frees the lock */
- log_write(0, LOG_MAIN|LOG_PANIC,
- "spoolfile error on close: %s", strerror(errno));
+ {
+ log_msg = string_sprintf("spoolfile error on close: %s", strerror(errno));
+ log_write(0, LOG_MAIN|LOG_PANIC |
+ (LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
+ (LOGGING(received_sender) ? LOG_SENDER : 0),
+ "%s", log_msg);
+ log_write(0, LOG_MAIN |
+ (LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
+ (LOGGING(received_sender) ? LOG_SENDER : 0),
+ "rescind the above message-accept");
+
+ Uunlink(spool_name);
+ 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 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 */
+ }
spool_data_file = NULL;
}
}
/* End of receive.c */
+/* vi: se aw ai sw=2
+*/