/* Code for receiving a message and setting up spool files. */
#include "exim.h"
+#include <setjmp.h>
#ifdef EXPERIMENTAL_DCC
extern int dcc_ok;
enum CH_STATE {LF_SEEN, MID_LINE, CR_SEEN};
+#ifdef HAVE_LOCAL_SCAN
+jmp_buf local_scan_env; /* error-handling context for local_scan */
+unsigned had_local_scan_crash;
+unsigned had_local_scan_timeout;
+#endif
+
/*************************************************
* Non-SMTP character reading functions *
int
stdin_getc(unsigned lim)
{
-return getc(stdin);
+int c = getc(stdin);
+
+if (had_data_timeout)
+ {
+ fprintf(stderr, "exim: timed out while reading - message abandoned\n");
+ log_write(L_lost_incoming_connection,
+ LOG_MAIN, "timed out while reading local message");
+ receive_bomb_out(US"data-timeout", NULL); /* Does not return */
+ }
+if (had_data_sigint)
+ {
+ if (filter_test == FTEST_NONE)
+ {
+ fprintf(stderr, "\nexim: %s received - message abandoned\n",
+ had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT");
+ log_write(0, LOG_MAIN, "%s received while reading local message",
+ had_data_sigint == SIGTERM ? "SIGTERM" : "SIGINT");
+ }
+ receive_bomb_out(US"signal-exit", NULL); /* Does not return */
+ }
+return c;
}
int
/* Now close the file if it is open, either as a fd or a stream. */
-if (data_file != NULL)
+if (data_file)
{
(void)fclose(data_file);
data_file = NULL;
-} else if (data_fd >= 0) {
+ }
+else if (data_fd >= 0)
+ {
(void)close(data_fd);
data_fd = -1;
}
static void
data_timeout_handler(int sig)
{
-uschar *msg = NULL;
-
-sig = sig; /* Keep picky compilers happy */
-
-if (smtp_input)
- {
- msg = US"SMTP incoming data timeout";
- log_write(L_lost_incoming_connection,
- LOG_MAIN, "SMTP data timeout (message abandoned) on connection "
- "from %s F=<%s>",
- (sender_fullhost != NULL)? sender_fullhost : US"local process",
- sender_address);
- }
-else
- {
- fprintf(stderr, "exim: timed out while reading - message abandoned\n");
- log_write(L_lost_incoming_connection,
- LOG_MAIN, "timed out while reading local message");
- }
-
-receive_bomb_out(US"data-timeout", msg); /* Does not return */
+had_data_timeout = sig;
}
+#ifdef HAVE_LOCAL_SCAN
/*************************************************
* local_scan() timeout *
*************************************************/
/* Handler function for timeouts that occur while running a local_scan()
-function.
+function. Posix recommends against calling longjmp() from a signal-handler,
+but the GCC manual says you can so we will, and trust that it's better than
+calling probably non-signal-safe funxtions during logging from within the
+handler, even with other compilers.
+
+See also https://cwe.mitre.org/data/definitions/745.html which also lists
+it as unsafe.
+
+This is all because we have no control over what might be written for a
+local-scan function, so cannot sprinkle had-signal checks after each
+call-site. At least with the default "do-nothing" function we won't
+ever get here.
Argument: the signal number
Returns: nothing
static void
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);
-/* Does not return */
-receive_bomb_out(US"local-scan-timeout", US"local verification problem");
+had_local_scan_timeout = sig;
+siglongjmp(local_scan_env, 1);
}
static void
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);
-/* Does not return */
-receive_bomb_out(US"local-scan-error", US"local verification problem");
+had_local_scan_crash = sig;
+siglongjmp(local_scan_env, 1);
}
+#endif /*HAVE_LOCAL_SCAN*/
+
/*************************************************
* SIGTERM or SIGINT received *
static void
data_sigterm_sigint_handler(int sig)
{
-uschar *msg = NULL;
-
-if (smtp_input)
- {
- msg = US"Service not available - SIGTERM or SIGINT received";
- log_write(0, LOG_MAIN, "%s closed after %s", smtp_get_connection_info(),
- (sig == SIGTERM)? "SIGTERM" : "SIGINT");
- }
-else
- {
- if (filter_test == FTEST_NONE)
- {
- fprintf(stderr, "\nexim: %s received - message abandoned\n",
- (sig == SIGTERM)? "SIGTERM" : "SIGINT");
- log_write(0, LOG_MAIN, "%s received while reading local message",
- (sig == SIGTERM)? "SIGTERM" : "SIGINT");
- }
- }
-
-receive_bomb_out(US"signal-exit", msg); /* Does not return */
+had_data_sigint = sig;
}
unsigned len = MAX(chunking_data_left, thismessage_size_limit - message_size + 1);
uschar * buf = bdat_getbuf(&len);
+ if (!buf) return END_EOF;
message_size += len;
if (fout && fwrite(buf, len, 1, fout) != 1) return END_WERROR;
}
switch(h->type)
{
case htype_add_top:
- h->next = header_list;
- header_list = h;
- DEBUG(D_receive|D_acl) debug_printf_indent(" (at top)");
- break;
+ h->next = header_list;
+ header_list = h;
+ DEBUG(D_receive|D_acl) debug_printf_indent(" (at top)");
+ break;
case htype_add_rec:
- if (last_received == NULL)
- {
- last_received = header_list;
- while (!header_testname(last_received, US"Received", 8, FALSE))
- last_received = last_received->next;
- while (last_received->next != NULL &&
- header_testname(last_received->next, US"Received", 8, FALSE))
- last_received = last_received->next;
- }
- h->next = last_received->next;
- last_received->next = h;
- DEBUG(D_receive|D_acl) debug_printf_indent(" (after Received:)");
- break;
+ if (!last_received)
+ {
+ last_received = header_list;
+ while (!header_testname(last_received, US"Received", 8, FALSE))
+ last_received = last_received->next;
+ while (last_received->next &&
+ header_testname(last_received->next, US"Received", 8, FALSE))
+ last_received = last_received->next;
+ }
+ h->next = last_received->next;
+ last_received->next = h;
+ DEBUG(D_receive|D_acl) debug_printf_indent(" (after Received:)");
+ break;
case htype_add_rfc:
- /* add header before any header which is NOT Received: or Resent- */
- last_received = header_list;
- while ( (last_received->next != NULL) &&
- ( (header_testname(last_received->next, US"Received", 8, FALSE)) ||
- (header_testname_incomplete(last_received->next, US"Resent-", 7, FALSE)) ) )
- last_received = last_received->next;
- /* last_received now points to the last Received: or Resent-* header
- in an uninterrupted chain of those header types (seen from the beginning
- of all headers. Our current header must follow it. */
- h->next = last_received->next;
- last_received->next = h;
- DEBUG(D_receive|D_acl) debug_printf_indent(" (before any non-Received: or Resent-*: header)");
- break;
+ /* add header before any header which is NOT Received: or Resent- */
+ last_received = header_list;
+ while ( last_received->next &&
+ ( (header_testname(last_received->next, US"Received", 8, FALSE)) ||
+ (header_testname_incomplete(last_received->next, US"Resent-", 7, FALSE)) ) )
+ last_received = last_received->next;
+ /* last_received now points to the last Received: or Resent-* header
+ in an uninterrupted chain of those header types (seen from the beginning
+ of all headers. Our current header must follow it. */
+ h->next = last_received->next;
+ last_received->next = h;
+ DEBUG(D_receive|D_acl) debug_printf_indent(" (before any non-Received: or Resent-*: header)");
+ break;
default:
- h->next = NULL;
- header_last->next = h;
- break;
+ h->next = NULL;
+ header_last->next = h;
+ DEBUG(D_receive|D_acl) debug_printf_indent(" ");
+ break;
}
- if (h->next == NULL) header_last = h;
+ if (!h->next) header_last = h;
/* Check for one of the known header types (From:, To:, etc.) though in
practice most added headers are going to be "other". Lower case
h->type = header_checkname(h, FALSE);
if (h->type >= 'a') h->type = htype_other;
- DEBUG(D_receive|D_acl) debug_printf_indent(" %s", header_last->text);
+ DEBUG(D_receive|D_acl) debug_printf("%s", h->text);
}
acl_added_headers = NULL;
uschar *timestamp;
int tslen;
+
/* Release any open files that might have been cached while preparing to
accept the message - e.g. by verifying addresses - because reading a message
might take a fair bit of real time. */
/* If SMTP input, set the special handler for timeouts. The alarm() calls
happen in the smtp_getc() function when it refills its buffer. */
-if (smtp_input) os_non_restarting_signal(SIGALRM, data_timeout_handler);
+had_data_timeout = 0;
+if (smtp_input)
+ os_non_restarting_signal(SIGALRM, data_timeout_handler);
/* If not SMTP input, timeout happens only if configured, and we just set a
single timeout for the whole message. */
/* SIGTERM and SIGINT are caught always. */
+had_data_sigint = 0;
signal(SIGTERM, data_sigterm_sigint_handler);
signal(SIGINT, data_sigterm_sigint_handler);
switch (header_checkname(h, is_resent))
{
case htype_bcc:
- h->type = htype_bcc; /* Both Bcc: and Resent-Bcc: */
- break;
+ h->type = htype_bcc; /* Both Bcc: and Resent-Bcc: */
+ break;
case htype_cc:
- h->type = htype_cc; /* Both Cc: and Resent-Cc: */
- break;
+ h->type = htype_cc; /* Both Cc: and Resent-Cc: */
+ break;
- /* Record whether a Date: or Resent-Date: header exists, as appropriate. */
+ /* Record whether a Date: or Resent-Date: header exists, as appropriate. */
case htype_date:
- if (!resents_exist || is_resent) date_header_exists = TRUE;
- break;
+ if (!resents_exist || is_resent) date_header_exists = TRUE;
+ break;
- /* Same comments as about Return-Path: below. */
+ /* Same comments as about Return-Path: below. */
case htype_delivery_date:
- if (delivery_date_remove) h->type = htype_old;
- break;
+ if (delivery_date_remove) h->type = htype_old;
+ break;
- /* Same comments as about Return-Path: below. */
+ /* Same comments as about Return-Path: below. */
case htype_envelope_to:
- if (envelope_to_remove) h->type = htype_old;
- break;
+ if (envelope_to_remove) h->type = htype_old;
+ break;
- /* Mark all "From:" headers so they get rewritten. Save the one that is to
- be used for Sender: checking. For Sendmail compatibility, if the "From:"
- header consists of just the login id of the user who called Exim, rewrite
- it with the gecos field first. Apply this rule to Resent-From: if there
- are resent- fields. */
+ /* Mark all "From:" headers so they get rewritten. Save the one that is to
+ be used for Sender: checking. For Sendmail compatibility, if the "From:"
+ header consists of just the login id of the user who called Exim, rewrite
+ it with the gecos field first. Apply this rule to Resent-From: if there
+ are resent- fields. */
case htype_from:
- h->type = htype_from;
- if (!resents_exist || is_resent)
- {
- from_header = h;
- if (!smtp_input)
- {
- int len;
- uschar *s = Ustrchr(h->text, ':') + 1;
- while (isspace(*s)) 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";
- header_add(htype_from, "%s: %s <%s@%s>\n", name, originator_name,
- originator_login, qualify_domain_sender);
- from_header = header_last;
- h->type = htype_old;
- DEBUG(D_receive|D_rewrite)
- debug_printf("rewrote \"%s:\" header using gecos\n", name);
- }
- }
- }
- break;
+ h->type = htype_from;
+ if (!resents_exist || is_resent)
+ {
+ from_header = h;
+ if (!smtp_input)
+ {
+ int len;
+ uschar *s = Ustrchr(h->text, ':') + 1;
+ while (isspace(*s)) 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";
+ header_add(htype_from, "%s: %s <%s@%s>\n", name, originator_name,
+ originator_login, qualify_domain_sender);
+ from_header = header_last;
+ h->type = htype_old;
+ DEBUG(D_receive|D_rewrite)
+ debug_printf("rewrote \"%s:\" header using gecos\n", name);
+ }
+ }
+ }
+ break;
- /* Identify the Message-id: header for generating "in-reply-to" in the
- autoreply transport. For incoming logging, save any resent- value. In both
- cases, take just the first of any multiples. */
+ /* Identify the Message-id: header for generating "in-reply-to" in the
+ autoreply transport. For incoming logging, save any resent- value. In both
+ cases, take just the first of any multiples. */
case htype_id:
- if (msgid_header == NULL && (!resents_exist || is_resent))
- {
- msgid_header = h;
- h->type = htype_id;
- }
- break;
+ if (!msgid_header && (!resents_exist || is_resent))
+ {
+ msgid_header = h;
+ h->type = htype_id;
+ }
+ break;
- /* Flag all Received: headers */
+ /* Flag all Received: headers */
case htype_received:
- h->type = htype_received;
- received_count++;
- break;
+ h->type = htype_received;
+ received_count++;
+ break;
- /* "Reply-to:" is just noted (there is no resent-reply-to field) */
+ /* "Reply-to:" is just noted (there is no resent-reply-to field) */
case htype_reply_to:
- h->type = htype_reply_to;
- break;
+ h->type = htype_reply_to;
+ break;
- /* The Return-path: header is supposed to be added to messages when
- they leave the SMTP system. We shouldn't receive messages that already
- contain Return-path. However, since Exim generates Return-path: on
- local delivery, resent messages may well contain it. We therefore
- provide an option (which defaults on) to remove any Return-path: headers
- on input. Removal actually means flagging as "old", which prevents the
- header being transmitted with the message. */
+ /* The Return-path: header is supposed to be added to messages when
+ they leave the SMTP system. We shouldn't receive messages that already
+ contain Return-path. However, since Exim generates Return-path: on
+ local delivery, resent messages may well contain it. We therefore
+ provide an option (which defaults on) to remove any Return-path: headers
+ on input. Removal actually means flagging as "old", which prevents the
+ header being transmitted with the message. */
case htype_return_path:
- if (return_path_remove) h->type = htype_old;
+ if (return_path_remove) h->type = htype_old;
- /* If we are testing a mail filter file, use the value of the
- Return-Path: header to set up the return_path variable, which is not
- otherwise set. However, remove any <> that surround the address
- because the variable doesn't have these. */
+ /* If we are testing a mail filter file, use the value of the
+ Return-Path: header to set up the return_path variable, which is not
+ otherwise set. However, remove any <> that surround the address
+ because the variable doesn't have these. */
- if (filter_test != FTEST_NONE)
- {
- uschar *start = h->text + 12;
- uschar *end = start + Ustrlen(start);
- while (isspace(*start)) start++;
- while (end > start && isspace(end[-1])) end--;
- if (*start == '<' && end[-1] == '>')
- {
- start++;
- end--;
- }
- return_path = string_copyn(start, end - start);
- printf("Return-path taken from \"Return-path:\" header line\n");
- }
- break;
+ if (filter_test != FTEST_NONE)
+ {
+ uschar *start = h->text + 12;
+ uschar *end = start + Ustrlen(start);
+ while (isspace(*start)) start++;
+ while (end > start && isspace(end[-1])) end--;
+ if (*start == '<' && end[-1] == '>')
+ {
+ start++;
+ end--;
+ }
+ return_path = string_copyn(start, end - start);
+ printf("Return-path taken from \"Return-path:\" header line\n");
+ }
+ break;
/* If there is a "Sender:" header and the message is locally originated,
and from an untrusted caller and suppress_local_fixups is not set, or if we
set.) */
case htype_sender:
- h->type = ((!active_local_sender_retain &&
- (
- (sender_local && !trusted_caller && !suppress_local_fixups)
- || submission_mode
- )
- ) &&
- (!resents_exist||is_resent))?
- htype_old : htype_sender;
- break;
+ h->type = !active_local_sender_retain
+ && ( sender_local && !trusted_caller && !suppress_local_fixups
+ || submission_mode
+ )
+ && (!resents_exist || is_resent)
+ ? htype_old : htype_sender;
+ break;
- /* Remember the Subject: header for logging. There is no Resent-Subject */
+ /* Remember the Subject: header for logging. There is no Resent-Subject */
case htype_subject:
- subject_header = h;
- break;
+ subject_header = h;
+ break;
- /* "To:" gets flagged, and the existence of a recipient header is noted,
- whether it's resent- or not. */
+ /* "To:" gets flagged, and the existence of a recipient header is noted,
+ whether it's resent- or not. */
case htype_to:
- h->type = htype_to;
- /****
- to_or_cc_header_exists = TRUE;
- ****/
- break;
+ h->type = htype_to;
+ /****
+ to_or_cc_header_exists = TRUE;
+ ****/
+ break;
}
}
#endif
+#ifdef HAVE_LOCAL_SCAN
/* 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
the recipients have been discarded. */
-/*XXS could we avoid this for the standard case, given that few people will use it? */
lseek(data_fd, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
/* Arrange to catch crashes in local_scan(), so that the -D file gets
deleted, and the incident gets logged. */
-os_non_restarting_signal(SIGSEGV, local_scan_crash_handler);
-os_non_restarting_signal(SIGFPE, local_scan_crash_handler);
-os_non_restarting_signal(SIGILL, local_scan_crash_handler);
-os_non_restarting_signal(SIGBUS, local_scan_crash_handler);
-
-DEBUG(D_receive) debug_printf("calling local_scan(); timeout=%d\n",
- local_scan_timeout);
-local_scan_data = NULL;
-
-os_non_restarting_signal(SIGALRM, local_scan_timeout_handler);
-if (local_scan_timeout > 0) alarm(local_scan_timeout);
-rc = local_scan(data_fd, &local_scan_data);
-alarm(0);
-os_non_restarting_signal(SIGALRM, sigalrm_handler);
-
-enable_dollar_recipients = FALSE;
-
-store_pool = POOL_MAIN; /* In case changed */
-DEBUG(D_receive) debug_printf("local_scan() returned %d %s\n", rc,
- local_scan_data);
-
-os_non_restarting_signal(SIGSEGV, SIG_DFL);
-os_non_restarting_signal(SIGFPE, SIG_DFL);
-os_non_restarting_signal(SIGILL, SIG_DFL);
-os_non_restarting_signal(SIGBUS, SIG_DFL);
+if (sigsetjmp(local_scan_env, 1) == 0)
+ {
+ had_local_scan_crash = 0;
+ os_non_restarting_signal(SIGSEGV, local_scan_crash_handler);
+ os_non_restarting_signal(SIGFPE, local_scan_crash_handler);
+ os_non_restarting_signal(SIGILL, local_scan_crash_handler);
+ os_non_restarting_signal(SIGBUS, local_scan_crash_handler);
+
+ DEBUG(D_receive) debug_printf("calling local_scan(); timeout=%d\n",
+ local_scan_timeout);
+ local_scan_data = NULL;
+
+ had_local_scan_timeout = 0;
+ os_non_restarting_signal(SIGALRM, local_scan_timeout_handler);
+ if (local_scan_timeout > 0) alarm(local_scan_timeout);
+ rc = local_scan(data_fd, &local_scan_data);
+ alarm(0);
+ os_non_restarting_signal(SIGALRM, sigalrm_handler);
+
+ enable_dollar_recipients = FALSE;
+
+ store_pool = POOL_MAIN; /* In case changed */
+ DEBUG(D_receive) debug_printf("local_scan() returned %d %s\n", rc,
+ local_scan_data);
+
+ os_non_restarting_signal(SIGSEGV, SIG_DFL);
+ os_non_restarting_signal(SIGFPE, SIG_DFL);
+ os_non_restarting_signal(SIGILL, SIG_DFL);
+ os_non_restarting_signal(SIGBUS, SIG_DFL);
+ }
+else
+ {
+ if (had_local_scan_crash)
+ {
+ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function crashed with "
+ "signal %d - message temporarily rejected (size %d)",
+ had_local_scan_crash, message_size);
+ /* Does not return */
+ receive_bomb_out(US"local-scan-error", US"local verification problem");
+ }
+ if (had_local_scan_timeout)
+ {
+ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function timed out - "
+ "message temporarily rejected (size %d)", message_size);
+ /* Does not return */
+ receive_bomb_out(US"local-scan-timeout", US"local verification problem");
+ }
+ }
/* The length check is paranoia against some runaway code, and also because
(for a success return) lines in the spool file are read into big_buffer. */
-if (local_scan_data != NULL)
+if (local_scan_data)
{
int len = Ustrlen(local_scan_data);
if (len > LOCAL_SCAN_MAX_RETURN) len = LOCAL_SCAN_MAX_RETURN;
if (rc == LOCAL_SCAN_ACCEPT)
{
- if (local_scan_data != NULL)
+ if (local_scan_data)
{
uschar *s;
for (s = local_scan_data; *s != 0; s++) if (*s == '\n') *s = ' ';
signal(SIGTERM, SIG_IGN);
signal(SIGINT, SIG_IGN);
+#endif /* HAVE_LOCAL_SCAN */
/* Ensure the first time flag is set in the newly-received message. */
#ifndef DISABLE_DKIM
if (LOGGING(dkim) && dkim_verify_overall)
g = string_append(g, 2, US" DKIM=", dkim_verify_overall);
+# ifdef EXPERIMENTAL_ARC
+if (LOGGING(dkim) && arc_state && Ustrcmp(arc_state, "pass") == 0)
+ g = string_catn(g, US" ARC", 4);
+# endif
#endif
if (LOGGING(receive_time))
if (blackholed_by)
{
- const uschar *detail = local_scan_data
- ? string_printing(local_scan_data)
- : string_sprintf("(%s discarded recipients)", blackholed_by);
+ const uschar *detail =
+#ifdef HAVE_LOCAL_SCAN
+ local_scan_data ? string_printing(local_scan_data) :
+#endif
+ string_sprintf("(%s discarded recipients)", blackholed_by);
log_write(0, LOG_MAIN, "=> blackhole %s%s", detail, blackhole_log_msg);
log_write(0, LOG_MAIN, "Completed");
message_id[0] = 0;