/* 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;
}
int rc = FAIL;
int msg_size = 0;
int process_info_len = Ustrlen(process_info);
-int error_rc = (error_handling == ERRORS_SENDER)?
- errors_sender_rc : EXIT_FAILURE;
+int error_rc = error_handling == ERRORS_SENDER
+ ? errors_sender_rc : EXIT_FAILURE;
int header_size = 256;
int start, end, domain;
int id_resolution;
int had_zero = 0;
int prevlines_length = 0;
-register int ptr = 0;
+int ptr = 0;
BOOL contains_resent_headers = FALSE;
BOOL extracted_ignored = FALSE;
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);
/* Check on maximum */
if (recipients_max > 0 && ++rcount > recipients_max)
- {
give_local_error(ERRMESS_TOOMANYRECIP, US"too many recipients",
US"message rejected: ", error_rc, stdin, NULL);
/* Does not return */
- }
/* Make a copy of the address, and remove any internal newlines. These
may be present as a result of continuations of the header line. The
{
uschar *s = next->text;
int len = next->slen;
- len = fwrite(s, 1, len, data_file); len = len; /* compiler quietening */
- body_linecount++; /* Assumes only 1 line */
+ if (fwrite(s, 1, len, data_file) == len) /* "if" for compiler quietening */
+ body_linecount++; /* Assumes only 1 line */
}
/* Note that we might already be at end of file, or the logical end of file
: read_message_bdat_smtp(data_file);
receive_linecount++; /* The terminating "." line */
}
- else message_ended = read_message_data(data_file);
+ else
+ message_ended = read_message_data(data_file);
receive_linecount += body_linecount; /* For BSMTP errors mainly */
message_linecount += body_linecount;
else
{
if (!bad_addresses)
- {
if (extracted_ignored)
fprintf(stderr, "exim: all -t recipients overridden by command line\n");
else
fprintf(stderr, "exim: no recipients in message\n");
- }
else
{
fprintf(stderr, "exim: invalid address%s",
- (bad_addresses->next == NULL)? ":" : "es:\n");
- while (bad_addresses != NULL)
- {
+ bad_addresses->next ? "es:\n" : ":");
+ for ( ; bad_addresses; bad_addresses = bad_addresses->next)
fprintf(stderr, " %s: %s\n", bad_addresses->text1,
bad_addresses->text2);
- bad_addresses = bad_addresses->next;
- }
}
}
goto TIDYUP;
#endif /* WITH_CONTENT_SCAN */
- if (acl_not_smtp != NULL)
+ if (acl_not_smtp)
{
uschar *user_msg, *log_msg;
+ authentication_local = TRUE;
rc = acl_check(ACL_WHERE_NOTSMTP, NULL, acl_not_smtp, &user_msg, &log_msg);
if (rc == DISCARD)
{
recipients_count = 0;
blackholed_by = US"non-SMTP ACL";
- if (log_msg != NULL)
+ if (log_msg)
blackhole_log_msg = string_sprintf(": %s", log_msg);
}
else if (rc != OK)
/* The ACL can specify where rejections are to be logged, possibly
nowhere. The default is main and reject logs. */
- if (log_reject_target != 0)
+ if (log_reject_target)
log_write(0, log_reject_target, "F=<%s> rejected by non-SMTP ACL: %s",
sender_address, log_msg);
- if (user_msg == NULL) user_msg = US"local configuration problem";
+ if (!user_msg) user_msg = US"local configuration problem";
if (smtp_batched_input)
- {
moan_smtp_batch(NULL, "%d %s", 550, user_msg);
/* Does not return */
- }
else
{
fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
#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);
+ receive_bomb_out(US"local-scan-error", US"local verification problem");
+ /* Does not return */
+ }
+ if (had_local_scan_timeout)
+ {
+ log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() function timed out - "
+ "message temporarily rejected (size %d)", message_size);
+ receive_bomb_out(US"local-scan-timeout", US"local verification problem");
+ /* Does not return */
+ }
+ }
/* 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 = ' ';
goto TIDYUP; /* Skip to end of function */
}
else
- {
moan_smtp_batch(NULL, "%s %s", smtp_code, errmsg);
/* Does not return */
- }
}
else
{
signal(SIGTERM, SIG_IGN);
signal(SIGINT, SIG_IGN);
+#endif /* HAVE_LOCAL_SCAN */
/* Ensure the first time flag is set in the newly-received message. */
don't write the header file, and we unlink the data file. If writing the header
file fails, we have failed to accept this message. */
-if (host_checking || blackholed_by != NULL)
+if (host_checking || blackholed_by)
{
header_line *h;
Uunlink(spool_name);
msg_size = 0; /* Compute size for log line */
- for (h = header_list; h != NULL; h = h->next)
+ for (h = header_list; h; h = h->next)
if (h->type != '*') msg_size += h->slen;
}
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. */
-fflush(data_file);
+if (fflush(data_file))
+ {
+ errmsg = string_sprintf("Spool write error: %s", strerror(errno));
+ log_write(0, LOG_MAIN, "%s\n", errmsg);
+ Uunlink(spool_name); /* Lose the data file */
+
+ if (smtp_input)
+ {
+ smtp_reply = US"451 Error in writing spool file";
+ message_id[0] = 0; /* Indicate no message accepted */
+ goto TIDYUP;
+ }
+ else
+ {
+ fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+ give_local_error(ERRMESS_IOERR, errmsg, US"", error_rc, data_file,
+ header_list);
+ /* Does not return */
+ }
+ }
fstat(data_fd, &statbuf);
msg_size += statbuf.st_size - SPOOL_DATA_START_OFFSET + 1;
/* Generate a "message received" log entry. We do this by building up a dynamic
-string as required. Since we commonly want to add two items at a time, use a
-macro to simplify the coding. We log the arrival of a new message while the
+string as required. We log the arrival of a new message while the
file is still locked, just in case the machine is *really* fast, and delivers
it first! Include any message id that is in the message - since the syntax of a
message id is actually an addr-spec, we can use the parse routine to canonicalize
#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 (message_logs && !blackholed_by)
{
int fd;
-
- spool_name = spool_fname(US"msglog", message_subdir, message_id, US"");
+ uschar * m_name = spool_fname(US"msglog", message_subdir, message_id, US"");
- if ( (fd = Uopen(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE)) < 0
+ if ( (fd = Uopen(m_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE)) < 0
&& errno == ENOENT
)
{
(void)directory_make(spool_directory,
spool_sname(US"msglog", message_subdir),
MSGLOG_DIRECTORY_MODE, TRUE);
- fd = Uopen(spool_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE);
+ fd = Uopen(m_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE);
}
if (fd < 0)
log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't open message log %s: %s",
- spool_name, strerror(errno));
+ m_name, strerror(errno));
else
{
FILE *message_log = fdopen(fd, "a");
- if (message_log == NULL)
+ if (!message_log)
{
log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't fdopen message log %s: %s",
- spool_name, strerror(errno));
+ m_name, strerror(errno));
(void)close(fd);
}
else
connection will vanish between the time of this test and the sending of the
response, but the chance of this happening should be small. */
-if (smtp_input && sender_host_address != NULL && !sender_host_notsocket &&
+if (smtp_input && sender_host_address && !sender_host_notsocket &&
!receive_smtp_buffered())
{
struct timeval tv;
/* Delete the files for this aborted message. */
- Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D"));
+ Uunlink(spool_name);
Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
case '4': /* Temp-reject. Keep spoolfiles and accept, unless defer-pass mode.
... for which, pass back the exact error */
if (cutthrough.defer_pass) smtp_reply = string_copy_malloc(msg);
- /*FALLTRHOUGH*/
+ cutthrough_done = TMP_REJ; /* Avoid the usual immediate delivery attempt */
+ break; /* message_id needed for SMTP accept below */
default: /* Unknown response, or error. Treat as temp-reject. */
+ if (cutthrough.defer_pass) smtp_reply = US"450 Onward transmission not accepted";
cutthrough_done = TMP_REJ; /* Avoid the usual immediate delivery attempt */
break; /* message_id needed for SMTP accept below */
#endif
{
log_write(0, LOG_MAIN |
- (LOGGING(received_recipients)? LOG_RECIPIENTS : 0) |
- (LOGGING(received_sender)? LOG_SENDER : 0),
+ (LOGGING(received_recipients) ? LOG_RECIPIENTS : 0) |
+ (LOGGING(received_sender) ? LOG_SENDER : 0),
"%s", g->s);
/* Log any control actions taken by an ACL or local_scan(). */
/* If the message is frozen, and freeze_tell is set, do the telling. */
-if (deliver_freeze && freeze_tell != NULL && freeze_tell[0] != 0)
- {
+if (deliver_freeze && freeze_tell && freeze_tell[0])
moan_tell_someone(freeze_tell, NULL, US"Message frozen on arrival",
"Message %s was frozen on arrival by %s.\nThe sender is <%s>.\n",
message_id, frozen_by, sender_address);
- }
/* Either a message has been successfully received and written to the two spool
files, or an error in writing the spool has occurred for an SMTP message, or
-an SMTP message has been rejected for policy reasons. (For a non-SMTP message
-we will have already given up because there's no point in carrying on!) In
-either event, we must now close (and thereby unlock) the data file. In the
-successful case, this leaves the message on the spool, ready for delivery. In
-the error case, the spool file will be deleted. Then tidy up store, interact
-with an SMTP call if necessary, and return.
+an SMTP message has been rejected for policy reasons, or a message was passed on
+by cutthrough delivery. (For a non-SMTP message we will have already given up
+because there's no point in carrying on!) For non-cutthrough we must now close
+(and thereby unlock) the data file. In the successful case, this leaves the
+message on the spool, ready for delivery. In the error case, the spool file will
+be deleted. Then tidy up store, interact with an SMTP call if necessary, and
+return.
+
+For cutthrough we hold the data file locked until we have deleted it, otherwise
+a queue-runner could grab it in the window.
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? */
+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. */
TIDYUP:
-process_info[process_info_len] = 0; /* Remove message id */
-if (data_file != NULL) (void)fclose(data_file); /* Frees the lock */
+process_info[process_info_len] = 0; /* Remove message id */
+if (data_file && cutthrough_done == NOT_TRIED)
+ if (fclose(data_file)) /* Frees the lock */
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "spoolfile error on close: %s", strerror(errno));
/* Now reset signal handlers to their defaults */
/* smtp_reply is set non-empty */
else if (smtp_reply[0] != 0)
- if (fake_response != OK && (smtp_reply[0] == '2'))
- smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE,
+ if (fake_response != OK && smtp_reply[0] == '2')
+ smtp_respond(fake_response == DEFER ? US"450" : US"550", 3, TRUE,
fake_response_text);
else
smtp_printf("%.1024s\r\n", FALSE, smtp_reply);
log_write(0, LOG_MAIN, "Completed");/* Delivery was done */
case PERM_REJ:
/* Delete spool files */
- Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D"));
+ Uunlink(spool_name);
Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
break;
case TMP_REJ:
if (cutthrough.defer_pass)
{
- Uunlink(spool_fname(US"input", message_subdir, message_id, US"-D"));
+ Uunlink(spool_name);
Uunlink(spool_fname(US"input", message_subdir, message_id, US"-H"));
Uunlink(spool_fname(US"msglog", message_subdir, message_id, US""));
}
}
if (cutthrough_done != NOT_TRIED)
{
+ if (data_file)
+ (void) fclose(data_file); /* Frees the lock; do not care if error */
message_id[0] = 0; /* Prevent a delivery from starting */
cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
cutthrough.defer_pass = FALSE;
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;