*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 - 2021 */
/* See the file NOTICE for conditions of use and distribution. */
/* Code for receiving a message and setting up spool files. */
extern int dcc_ok;
#endif
-#ifdef EXPERIMENTAL_DMARC
+#ifdef SUPPORT_DMARC
# include "dmarc.h"
-#endif /* EXPERIMENTAL_DMARC */
+#endif
/*************************************************
* Local static variables *
*************************************************/
-static FILE *data_file = NULL;
static int data_fd = -1;
static uschar *spool_name = US"";
the file. (When SMTP input is occurring, different functions are used by
changing the pointer variables.) */
-int
-stdin_getc(unsigned lim)
-{
-int c = getc(stdin);
+uschar stdin_buf[4096];
+uschar * stdin_inptr = stdin_buf;
+uschar * stdin_inend = stdin_buf;
-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)
+static BOOL
+stdin_refill(void)
+{
+size_t rc = fread(stdin_buf, 1, sizeof(stdin_buf), stdin);
+if (rc <= 0)
{
- if (filter_test == FTEST_NONE)
+ 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)
{
- 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");
+ 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 */
}
- receive_bomb_out(US"signal-exit", NULL); /* Does not return */
+ return FALSE;
}
-return c;
+stdin_inend = stdin_buf + rc;
+stdin_inptr = stdin_buf;
+return TRUE;
+}
+
+int
+stdin_getc(unsigned lim)
+{
+if (stdin_inptr >= stdin_inend)
+ if (!stdin_refill())
+ return EOF;
+return *stdin_inptr++;
+}
+
+
+BOOL
+stdin_hasc(void)
+{
+return stdin_inptr < stdin_inend;
}
int
stdin_ungetc(int c)
{
-return ungetc(c, stdin);
+if (stdin_inptr <= stdin_buf)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in stdin_ungetc");
+
+*--stdin_inptr = c;
+return c;
}
int
stdin_feof(void)
{
-return feof(stdin);
+return stdin_hasc() ? FALSE : feof(stdin);
}
int
receive_check_set_sender(uschar *newsender)
{
uschar *qnewsender;
-if (trusted_caller) return TRUE;
+if (f.trusted_caller) return TRUE;
if (!newsender || !untrusted_set_sender) return FALSE;
qnewsender = Ustrchr(newsender, '@')
? newsender : string_sprintf("%s@%s", newsender, qualify_domain_sender);
All values are -1 if the STATFS functions are not available.
*/
-int
+int_eximarith_t
receive_statvfs(BOOL isspool, int *inodeptr)
{
#ifdef HAVE_STATFS
empty item in a list. */
if (*p == 0) p = US":";
+ /* should never be a tainted list */
while ((path = string_nextinlist(&p, &sep, buffer, sizeof(buffer))))
if (Ustrcmp(path, "syslog") != 0)
break;
log_write(0, LOG_MAIN|LOG_PANIC, "cannot accept message: failed to stat "
"%s directory %s: %s", name, path, strerror(errno));
smtp_closedown(US"spool or log directory problem");
- exim_exit(EXIT_FAILURE, NULL);
+ exim_exit(EXIT_FAILURE);
}
*inodeptr = (statbuf.F_FILES > 0)? statbuf.F_FAVAIL : -1;
/* Disks are getting huge. Take care with computing the size in kilobytes. */
-return (int)(((double)statbuf.F_BAVAIL * (double)statbuf.F_FRSIZE)/1024.0);
+return (int_eximarith_t)(((double)statbuf.F_BAVAIL * (double)statbuf.F_FRSIZE)/1024.0);
#else
/* Unable to find partition sizes in this environment. */
BOOL
receive_check_fs(int msg_size)
{
-int space, inodes;
+int_eximarith_t space;
+int inodes;
if (check_spool_space > 0 || msg_size > 0 || check_spool_inodes > 0)
{
space = receive_statvfs(TRUE, &inodes);
DEBUG(D_receive)
- debug_printf("spool directory space = %dK inodes = %d "
- "check_space = %dK inodes = %d msg_size = %d\n",
+ debug_printf("spool directory space = " PR_EXIM_ARITH "K inodes = %d "
+ "check_space = " PR_EXIM_ARITH "K inodes = %d msg_size = %d\n",
space, inodes, check_spool_space, check_spool_inodes, msg_size);
- if ((space >= 0 && space < check_spool_space) ||
- (inodes >= 0 && inodes < check_spool_inodes))
+ if ( space >= 0 && space + msg_size / 1024 < check_spool_space
+ || inodes >= 0 && inodes < check_spool_inodes)
{
- log_write(0, LOG_MAIN, "spool directory space check failed: space=%d "
- "inodes=%d", space, inodes);
+ log_write(0, LOG_MAIN, "spool directory space check failed: space="
+ PR_EXIM_ARITH " inodes=%d", space, inodes);
return FALSE;
}
}
space = receive_statvfs(FALSE, &inodes);
DEBUG(D_receive)
- debug_printf("log directory space = %dK inodes = %d "
- "check_space = %dK inodes = %d\n",
+ debug_printf("log directory space = " PR_EXIM_ARITH "K inodes = %d "
+ "check_space = " PR_EXIM_ARITH "K inodes = %d\n",
space, inodes, check_log_space, check_log_inodes);
- if ((space >= 0 && space < check_log_space) ||
- (inodes >= 0 && inodes < check_log_inodes))
+ if ( space >= 0 && space < check_log_space
+ || inodes >= 0 && inodes < check_log_inodes)
{
- log_write(0, LOG_MAIN, "log directory space check failed: space=%d "
- "inodes=%d", space, inodes);
+ log_write(0, LOG_MAIN, "log directory space check failed: space=" PR_EXIM_ARITH
+ " inodes=%d", space, inodes);
return FALSE;
}
}
/* Now close the file if it is open, either as a fd or a stream. */
-if (data_file)
+if (spool_data_file)
{
- (void)fclose(data_file);
- data_file = NULL;
+ (void)fclose(spool_data_file);
+ spool_data_file = NULL;
}
else if (data_fd >= 0)
{
/* Exit from the program (non-BSMTP cases) */
-exim_exit(EXIT_FAILURE, NULL);
+exim_exit(EXIT_FAILURE);
}
{
recipient_item *oldlist = recipients_list;
int oldmax = recipients_list_max;
- recipients_list_max = recipients_list_max? 2*recipients_list_max : 50;
- recipients_list = store_get(recipients_list_max * sizeof(recipient_item));
- if (oldlist != NULL)
+
+ const int safe_recipients_limit = INT_MAX / 2 / sizeof(recipient_item);
+ if (recipients_list_max < 0 || recipients_list_max >= safe_recipients_limit)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Too many recipients: %d", recipients_list_max);
+ }
+
+ recipients_list_max = recipients_list_max ? 2*recipients_list_max : 50;
+ recipients_list = store_get(recipients_list_max * sizeof(recipient_item), GET_UNTAINTED);
+ if (oldlist)
memcpy(recipients_list, oldlist, oldmax * sizeof(recipient_item));
}
BOOL
receive_remove_recipient(uschar *recipient)
{
-int count;
DEBUG(D_receive) debug_printf("receive_remove_recipient(\"%s\") called\n",
recipient);
-for (count = 0; count < recipients_count; count++)
- {
+for (int count = 0; count < recipients_count; count++)
if (Ustrcmp(recipients_list[count].address, recipient) == 0)
{
if ((--recipients_count - count) > 0)
(recipients_count - count)*sizeof(recipient_item));
return TRUE;
}
- }
return FALSE;
}
+/* Pause for a while waiting for input. If none received in that time,
+close the logfile, if we had one open; then if we wait for a long-running
+datasource (months, in one use-case) log rotation will not leave us holding
+the file copy. */
+
+static void
+log_close_chk(void)
+{
+if (!receive_timeout && !receive_hasc())
+ {
+ struct timeval t;
+ timesince(&t, &received_time);
+ if (t.tv_sec > 30*60)
+ mainlog_close();
+ else
+ if (poll_one_fd(0, POLLIN, (30*60 - t.tv_sec) * 1000) == 0)
+ mainlog_close();
+ }
+}
+
/*************************************************
* Read data portion of a non-SMTP message *
*************************************************/
/* Handle the case when only EOF terminates the message */
-if (!dot_ends)
+if (!f.dot_ends)
{
- register int last_ch = '\n';
+ int last_ch = '\n';
- for (; (ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF; last_ch = ch)
+ for ( ;
+ log_close_chk(), (ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF;
+ last_ch = ch)
{
if (ch == 0) body_zerocount++;
if (last_ch == '\r' && ch != '\n')
ch_state = 1;
-while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
+while (log_close_chk(), (ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
{
if (ch == 0) body_zerocount++;
switch (ch_state)
DEBUG(D_receive) debug_printf("CHUNKING: %s\n",
fout ? "writing spoolfile in wire format" : "flushing input");
-spool_file_wireformat = TRUE;
+f.spool_file_wireformat = TRUE;
for (;;)
{
else
fprintf(stderr, "exim: %s%s\n", text2, text1); /* Sic */
(void)fclose(f);
-exim_exit(error_rc, US"");
+exim_exit(error_rc);
}
static void
add_acl_headers(int where, uschar *acl_name)
{
-header_line *h, *next;
header_line *last_received = NULL;
switch(where)
case ACL_WHERE_DKIM:
case ACL_WHERE_MIME:
case ACL_WHERE_DATA:
- if ( cutthrough.fd >= 0 && cutthrough.delivery
+ if ( cutthrough.cctx.sock >= 0 && cutthrough.delivery
&& (acl_removed_headers || acl_added_headers))
{
log_write(0, LOG_MAIN|LOG_PANIC, "Header modification in data ACLs"
{
DEBUG(D_receive|D_acl) debug_printf_indent(">>Headers removed by %s ACL:\n", acl_name);
- for (h = header_list; h; h = h->next) if (h->type != htype_old)
+ for (header_line * h = header_list; h; h = h->next) if (h->type != htype_old)
{
const uschar * list = acl_removed_headers;
int sep = ':'; /* This is specified as a colon-separated list */
uschar *s;
- uschar buffer[128];
- while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
+ while ((s = string_nextinlist(&list, &sep, NULL, 0)))
if (header_testname(h, s, Ustrlen(s), FALSE))
{
h->type = htype_old;
if (!acl_added_headers) return;
DEBUG(D_receive|D_acl) debug_printf_indent(">>Headers added by %s ACL:\n", acl_name);
-for (h = acl_added_headers; h; h = next)
+for (header_line * h = acl_added_headers, * next; h; h = next)
{
next = h->next;
if (LOGGING(dnssec) && sender_host_dnssec) /*XXX sender_helo_dnssec? */
g = string_catn(g, US" DS", 3);
g = string_append(g, 2, US" H=", sender_fullhost);
- if (LOGGING(incoming_interface) && interface_address != NULL)
- {
- g = string_cat(g,
- string_sprintf(" I=[%s]:%d", interface_address, interface_port));
- }
+ if (LOGGING(incoming_interface) && interface_address)
+ g = string_fmt_append(g, " I=[%s]:%d", interface_address, interface_port);
}
-if (tcp_in_fastopen && !tcp_in_fastopen_logged)
+if (f.tcp_in_fastopen && !f.tcp_in_fastopen_logged)
{
- g = string_catn(g, US" TFO", 4);
- tcp_in_fastopen_logged = TRUE;
+ g = string_catn(g, US" TFO*", f.tcp_in_fastopen_data ? 5 : 4);
+ f.tcp_in_fastopen_logged = TRUE;
}
if (sender_ident)
g = string_append(g, 2, US" U=", sender_ident);
if (received_protocol)
g = string_append(g, 2, US" P=", received_protocol);
+if (LOGGING(pipelining) && f.smtp_in_pipelining_advertised)
+ {
+ g = string_catn(g, US" L", 2);
+#ifndef DISABLE_PIPE_CONNECT
+ if (f.smtp_in_early_pipe_used)
+ g = string_catn(g, US"*", 1);
+ else if (f.smtp_in_early_pipe_advertised)
+ g = string_catn(g, US".", 1);
+#endif
+ if (!f.smtp_in_pipelining_used)
+ g = string_catn(g, US"-", 1);
+ }
return g;
}
FILE *mbox_file;
uschar * rfc822_file_path = NULL;
unsigned long mbox_size;
-header_line *my_headerlist;
uschar *user_msg, *log_msg;
int mime_part_count_buffer = -1;
uschar * mbox_filename;
/* check if it is a MIME message */
-for (my_headerlist = header_list; my_headerlist; my_headerlist = my_headerlist->next)
+for (header_line * my_headerlist = header_list; my_headerlist;
+ my_headerlist = my_headerlist->next)
if ( my_headerlist->type != '*' /* skip deleted headers */
&& strncmpic(my_headerlist->text, US"Content-Type:", 13) == 0
)
struct dirent * entry;
DIR * tempdir;
- for (tempdir = opendir(CS scandir); entry = readdir(tempdir); )
+ for (tempdir = exim_opendir(scandir); entry = readdir(tempdir); )
if (strncmpic(US entry->d_name, US"__rfc822_", 9) == 0)
{
rfc822_file_path = string_sprintf("%s/%s", scandir, entry->d_name);
{
recipients_count = 0;
*blackholed_by_ptr = US"MIME ACL";
+ cancel_cutthrough_connection(TRUE, US"mime acl discard");
}
else if (rc != OK)
{
Uunlink(spool_name);
+ cancel_cutthrough_connection(TRUE, US"mime acl not ok");
unspool_mbox();
#ifdef EXPERIMENTAL_DCC
dcc_ok = 0;
void
received_header_gen(void)
{
-uschar *received;
-uschar *timestamp;
-header_line *received_header= header_list;
+uschar * received;
+uschar * timestamp = expand_string(US"${tod_full}");
+header_line * received_header= header_list;
-timestamp = expand_string(US"${tod_full}");
if (recipients_count == 1) received_for = recipients_list[0].address;
received = expand_string(received_header_text);
received_for = NULL;
the result of the expansion is an empty string, we leave the header marked as
"old" so as to refrain from adding a Received header. */
-if (received[0] == 0)
+if (!received[0])
{
received_header->text = string_sprintf("Received: ; %s\n", timestamp);
received_header->type = htype_old;
}
else
{
- received_header->text = string_sprintf("%s; %s\n", received, timestamp);
+ received_header->text = string_sprintf("%s;\n\t%s\n", received, timestamp);
received_header->type = htype_received;
}
BOOL
receive_msg(BOOL extract_recip)
{
-int i;
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 header_size = 256;
-int start, end, domain;
-int id_resolution;
int had_zero = 0;
int prevlines_length = 0;
+const int id_resolution = BASE_62 == 62 ? 5000 : 10000;
int ptr = 0;
uschar *queued_by = NULL;
uschar *errmsg;
+rmark rcvd_log_reset_point;
gstring * g;
struct stat statbuf;
/* Working header pointers */
-header_line *h, *next;
+rmark reset_point;
+header_line *next;
/* Flags for noting the existence of certain headers (only one left) */
header_line *subject_header = NULL;
header_line *msgid_header = NULL;
header_line *received_header;
-
-#ifdef EXPERIMENTAL_DMARC
-int dmarc_up = 0;
-#endif /* EXPERIMENTAL_DMARC */
+BOOL msgid_header_newly_created = FALSE;
/* Variables for use when building the Received: header. */
uschar *timestamp;
int tslen;
+/* Time of creation of message_id */
+
+static struct timeval message_id_tv = { 0, 0 };
+
/* Release any open files that might have been cached while preparing to
accept the message - e.g. by verifying addresses - because reading a message
header. Temporarily mark it as "old", i.e. not to be used. We keep header_last
pointing to the end of the chain to make adding headers simple. */
-received_header = header_list = header_last = store_get(sizeof(header_line));
+received_header = header_list = header_last = store_get(sizeof(header_line), GET_UNTAINTED);
header_list->next = NULL;
header_list->type = htype_old;
header_list->text = NULL;
header_list->slen = 0;
-/* Control block for the next header to be read. */
+/* Control block for the next header to be read.
+The data comes from the message, so is tainted. */
-next = store_get(sizeof(header_line));
-next->text = store_get(header_size);
+reset_point = store_mark();
+next = store_get(sizeof(header_line), GET_UNTAINTED);
+next->text = store_get(header_size, GET_TAINTED);
/* Initialize message id to be null (indicating no message read), and the
header names list to be the normal list. Indicate there is no data file open
yet, initialize the size and warning count, and deal with no size limit. */
message_id[0] = 0;
-data_file = NULL;
+spool_data_file = NULL;
data_fd = -1;
spool_name = US"";
message_size = 0;
message_linecount = body_linecount = body_zerocount =
max_received_linelength = 0;
+#ifdef WITH_CONTENT_SCAN
+/* reset non-per-part mime variables */
+mime_is_coverletter = 0;
+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 && !dkim_disable_verify)
+if (smtp_input && !smtp_batched_input && !f.dkim_disable_verify)
dkim_exim_verify_init(chunking_state <= CHUNKING_OFFERED);
#endif
-#ifdef EXPERIMENTAL_DMARC
-/* initialize libopendmarc */
-dmarc_up = dmarc_init();
+#ifdef SUPPORT_DMARC
+if (sender_host_address) dmarc_init(); /* initialize libopendmarc */
#endif
+/* 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
+granularity.
+This is so that the combination of time+pid is unique, even on systems where the
+pid can be re-used within our time interval. We can't shorten the interval
+without re-designing the message-id. See comments above where the message id is
+created. This is Something For The Future.
+Do this wait any time we have previously created a message-id, even if we
+rejected the message. This gives unique IDs for logging done by ACLs.
+The initial timestamp must have been obtained via exim_gettime() to avoid
+issues on Linux with suspend/resume. */
+
+if (message_id_tv.tv_sec)
+ {
+ message_id_tv.tv_usec = (message_id_tv.tv_usec/id_resolution) * id_resolution;
+ exim_wait_tick(&message_id_tv, id_resolution);
+ }
+
/* Remember the time of reception. Exim uses time+pid for uniqueness of message
ids, and fractions of a second are required. See the comments that precede the
-message id creation below. */
+message id creation below.
+We use a routine that if possible uses a monotonic clock, and can be used again
+after reception for the tick-wait even under the Linux non-Posix behaviour. */
-(void)gettimeofday(&message_id_tv, NULL);
+else
+ exim_gettime(&message_id_tv);
/* For other uses of the received time we can operate with granularity of one
second, and for that we use the global variable received_time. This is for
-things like ultimate message timeouts. */
+things like ultimate message timeouts.
+For this we do not care about the Linux suspend/resume problem, so rather than
+use exim_gettime() everywhere we use a plain gettimeofday() here. */
-received_time = message_id_tv;
+gettimeofday(&received_time, NULL);
/* If SMTP input, set the special handler for timeouts. The alarm() calls
happen in the smtp_getc() function when it refills its buffer. */
else if (receive_timeout > 0)
{
os_non_restarting_signal(SIGALRM, data_timeout_handler);
- alarm(receive_timeout);
+ ALARM(receive_timeout);
}
/* SIGTERM and SIGINT are caught always. */
if (ptr >= header_size - 4)
{
int oldsize = header_size;
- /* header_size += 256; */
+
+ if (header_size >= INT_MAX/2)
+ goto OVERSIZE;
header_size *= 2;
+
if (!store_extend(next->text, oldsize, header_size))
next->text = store_newblock(next->text, header_size, ptr);
}
prevent further reading), and break out of the loop, having freed the
empty header, and set next = NULL to indicate no data line. */
- if (ptr == 0 && ch == '.' && dot_ends)
+ if (ptr == 0 && ch == '.' && f.dot_ends)
{
ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
if (ch == '\r')
if (ch == '\n')
{
message_ended = END_DOT;
- store_reset(next);
+ reset_point = store_reset(reset_point);
next = NULL;
break; /* End character-reading loop */
}
if (message_size >= header_maxsize)
{
+OVERSIZE:
next->text[ptr] = 0;
next->slen = ptr;
next->type = htype_other;
log_write(0, LOG_MAIN, "ridiculously long message header received from "
"%s (more than %d characters): message abandoned",
- sender_host_unknown? sender_ident : sender_fullhost, header_maxsize);
+ f.sender_host_unknown ? sender_ident : sender_fullhost, header_maxsize);
if (smtp_input)
{
if (ptr == 1)
{
- store_reset(next);
+ reset_point = store_reset(reset_point);
next = NULL;
break;
}
if (nextch == ' ' || nextch == '\t')
{
next->text[ptr++] = nextch;
- message_size++;
+ if (++message_size >= header_maxsize)
+ goto OVERSIZE;
continue; /* Iterate the loop */
}
else if (nextch != EOF) (receive_ungetc)(nextch); /* For next time */
next->text[ptr] = 0;
next->slen = ptr;
- store_reset(next->text + ptr + 1);
+ store_release_above(next->text + ptr + 1);
/* Check the running total size against the overall message size limit. We
don't expect to fail here, but if the overall limit is set less than MESSAGE_
&& regex_match_and_setup(regex_From, next->text, 0, -1)
)
{
- if (!sender_address_forced)
+ if (!f.sender_address_forced)
{
uschar *uucp_sender = expand_string(uucp_from_sender);
if (!uucp_sender)
if (newsender)
{
if (domain == 0 && newsender[0] != 0)
- newsender = rewrite_address_qualify(newsender, FALSE);
+ /* deconst ok as newsender was not const */
+ newsender = US rewrite_address_qualify(newsender, FALSE);
if (filter_test != FTEST_NONE || receive_check_set_sender(newsender))
{
sender_address = newsender;
- if (trusted_caller || filter_test != FTEST_NONE)
+ if (f.trusted_caller || filter_test != FTEST_NONE)
{
authenticated_sender = NULL;
originator_name = US"";
- sender_local = FALSE;
+ f.sender_local = FALSE;
}
if (filter_test != FTEST_NONE)
the line, stomp on them here. */
if (had_zero > 0)
- for (p = next->text; p < next->text + ptr; p++) if (*p == 0) *p = '?';
+ for (uschar * p = next->text; p < next->text + ptr; p++) if (*p == 0)
+ *p = '?';
/* It is perfectly legal to have an empty continuation line
at the end of a header, but it is confusing to humans
{
log_write(0, LOG_MAIN, "overlong message header line received from "
"%s (more than %d characters): message abandoned",
- sender_host_unknown? sender_ident : sender_fullhost,
+ f.sender_host_unknown ? sender_ident : sender_fullhost,
header_line_maxsize);
if (smtp_input)
/* Set up for the next header */
+ reset_point = store_mark();
header_size = 256;
- next = store_get(sizeof(header_line));
- next->text = store_get(header_size);
+ next = store_get(sizeof(header_line), GET_UNTAINTED);
+ next->text = store_get(header_size, GET_TAINTED);
ptr = 0;
had_zero = 0;
prevlines_length = 0;
DEBUG(D_receive)
{
debug_printf(">>Headers received:\n");
- for (h = header_list->next; h; h = h->next)
+ for (header_line * h = header_list->next; h; h = h->next)
debug_printf("%s", h->text);
debug_printf("\n");
}
/* Scan the headers to identify them. Some are merely marked for later
processing; some are dealt with here. */
-for (h = header_list->next; h; h = h->next)
+for (header_line * h = header_list->next; h; h = h->next)
{
BOOL is_resent = strncmpic(h->text, US"resent-", 7) == 0;
if (is_resent) contains_resent_headers = TRUE;
set.) */
case htype_sender:
- h->type = !active_local_sender_retain
- && ( sender_local && !trusted_caller && !suppress_local_fixups
- || submission_mode
+ h->type = !f.active_local_sender_retain
+ && ( f.sender_local && !f.trusted_caller && !f.suppress_local_fixups
+ || f.submission_mode
)
&& (!resents_exist || is_resent)
? htype_old : htype_sender;
{
while (recipients_count-- > 0)
{
- uschar *s = rewrite_address(recipients_list[recipients_count].address,
+ const uschar * s = rewrite_address(recipients_list[recipients_count].address,
TRUE, TRUE, global_rewrite_rules, rewrite_existflags);
tree_add_nonrecipient(s);
}
/* Now scan the headers */
- for (h = header_list->next; h; h = h->next)
+ for (header_line * h = header_list->next; h; h = h->next)
{
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++;
- parse_allow_group = TRUE; /* Allow address group syntax */
+ f.parse_allow_group = TRUE; /* Allow address group syntax */
while (*s != 0)
{
uschar *ss = parse_find_address_end(s, FALSE);
- uschar *recipient, *errmess, *p, *pp;
+ uschar *recipient, *errmess, *pp;
int start, end, domain;
/* Check on maximum */
white space that follows the newline must not be removed - it is part
of the header. */
- pp = recipient = store_get(ss - s + 1);
- for (p = s; p < ss; p++) if (*p != '\n') *pp++ = *p;
+ pp = recipient = store_get(ss - s + 1, s);
+ for (uschar * p = s; p < ss; p++) if (*p != '\n') *pp++ = *p;
*pp = 0;
#ifdef SUPPORT_I18N
&domain, FALSE);
#ifdef SUPPORT_I18N
- if (string_is_utf8(recipient))
- message_smtputf8 = TRUE;
- else
- allow_utf8_domains = b;
+ if (recipient)
+ if (string_is_utf8(recipient)) message_smtputf8 = TRUE;
+ else allow_utf8_domains = b;
}
+#else
+ ;
#endif
/* Keep a list of all the bad addresses so we can send a single
If there are no recipients at all, an error will occur later. */
- if (recipient == NULL && Ustrcmp(errmess, "empty address") != 0)
+ if (!recipient && Ustrcmp(errmess, "empty address") != 0)
{
int len = Ustrlen(s);
- error_block *b = store_get(sizeof(error_block));
+ error_block * b = store_get(sizeof(error_block), GET_UNTAINTED);
while (len > 0 && isspace(s[len-1])) len--;
b->next = NULL;
b->text1 = string_printing(string_copyn(s, len));
while (isspace(*s)) s++;
} /* Next address */
- parse_allow_group = FALSE; /* Reset group syntax flags */
- parse_found_group = FALSE;
+ f.parse_allow_group = FALSE; /* Reset group syntax flags */
+ f.parse_found_group = FALSE;
/* If this was the bcc: header, mark it "old", which means it
will be kept on the spool, but not transmitted as part of the
Ustrncpy(message_id + 7, string_base62((long int)getpid()), 6);
/* Deal with the case where the host number is set. The value of the number was
-checked when it was read, to ensure it isn't too big. The timing granularity is
-left in id_resolution so that an appropriate wait can be done after receiving
-the message, if necessary (we hope it won't be). */
+checked when it was read, to ensure it isn't too big. */
if (host_number_string)
- {
- id_resolution = (BASE_62 == 62)? 5000 : 10000;
sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s",
string_base62((long int)(
host_number * (1000000/id_resolution) +
message_id_tv.tv_usec/id_resolution)) + 4);
- }
/* Host number not set: final field is just the fractional time at an
appropriate resolution. */
else
- {
- id_resolution = (BASE_62 == 62)? 500 : 1000;
sprintf(CS(message_id + MESSAGE_ID_LENGTH - 3), "-%2s",
string_base62((long int)(message_id_tv.tv_usec/id_resolution)) + 4);
- }
/* Add the current message id onto the current process info string if
it will fit. */
to be the least significant base-62 digit of the time of arrival. Otherwise
ensure that it is an empty string. */
-message_subdir[0] = split_spool_directory ? message_id[5] : 0;
+set_subdir_str(message_subdir, message_id, 0);
/* Now that we have the message-id, if there is no message-id: header, generate
one, but only for local (without suppress_local_fixups) or submission mode
any illegal characters therein. */
if ( !msgid_header
- && ((!sender_host_address && !suppress_local_fixups) || submission_mode))
+ && ((!sender_host_address && !f.suppress_local_fixups) || f.submission_mode))
{
- uschar *p;
uschar *id_text = US"";
uschar *id_domain = primary_hostname;
+ header_line * h;
/* Permit only letters, digits, dots, and hyphens in the domain */
uschar *new_id_domain = expand_string(message_id_domain);
if (!new_id_domain)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
log_write(0, LOG_MAIN|LOG_PANIC,
"expansion of \"%s\" (message_id_header_domain) "
"failed: %s", message_id_domain, expand_string_message);
else if (*new_id_domain)
{
id_domain = new_id_domain;
- for (p = id_domain; *p; p++)
+ for (uschar * p = id_domain; *p; p++)
if (!isalnum(*p) && *p != '.') *p = '-'; /* No need to test '-' ! */
}
}
uschar *new_id_text = expand_string(message_id_text);
if (!new_id_text)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
log_write(0, LOG_MAIN|LOG_PANIC,
"expansion of \"%s\" (message_id_header_text) "
"failed: %s", message_id_text, expand_string_message);
else if (*new_id_text)
{
id_text = new_id_text;
- for (p = id_text; *p; p++) if (mac_iscntrl_or_special(*p)) *p = '-';
+ for (uschar * p = id_text; *p; p++) if (mac_iscntrl_or_special(*p)) *p = '-';
}
}
- /* Add the header line
- * Resent-* headers are prepended, per RFC 5322 3.6.6. Non-Resent-* are
- * appended, to preserve classical expectations of header ordering. */
+ /* Add the header line.
+ Resent-* headers are prepended, per RFC 5322 3.6.6. Non-Resent-* are
+ appended, to preserve classical expectations of header ordering. */
- header_add_at_position(!resents_exist, NULL, FALSE, htype_id,
+ h = header_add_at_position_internal(!resents_exist, NULL, FALSE, htype_id,
"%sMessage-Id: <%s%s%s@%s>\n", resent_prefix, message_id_external,
- (*id_text == 0)? "" : ".", id_text, id_domain);
+ *id_text == 0 ? "" : ".", id_text, id_domain);
+
+ /* Arrange for newly-created Message-Id to be logged */
+
+ if (!resents_exist)
+ {
+ msgid_header_newly_created = TRUE;
+ msgid_header = h;
+ }
}
/* If we are to log recipients, keep a copy of the raw ones before any possible
if (LOGGING(received_recipients))
{
- raw_recipients = store_get(recipients_count * sizeof(uschar *));
- for (i = 0; i < recipients_count; i++)
+ raw_recipients = store_get(recipients_count * sizeof(uschar *), GET_UNTAINTED);
+ for (int i = 0; i < recipients_count; i++)
raw_recipients[i] = string_copy(recipients_list[i].address);
raw_recipients_count = recipients_count;
}
recipients will get here only if the conditions were right (allow_unqualified_
recipient is TRUE). */
-for (i = 0; i < recipients_count; i++)
- recipients_list[i].address =
- rewrite_address(recipients_list[i].address, TRUE, TRUE,
+DEBUG(D_rewrite)
+ { debug_printf_indent("qualify & rewrite recipients list\n"); acl_level++; }
+for (int i = 0; i < recipients_count; i++)
+ recipients_list[i].address = /* deconst ok as src was not cont */
+ US rewrite_address(recipients_list[i].address, TRUE, TRUE,
global_rewrite_rules, rewrite_existflags);
+DEBUG(D_rewrite) acl_level--;
/* If there is no From: header, generate one for local (without
suppress_local_fixups) or submission_mode messages. If there is no sender
From:) but we still want to ensure a valid Sender: if it is required. */
if ( !from_header
- && ((!sender_host_address && !suppress_local_fixups) || submission_mode))
+ && ((!sender_host_address && !f.suppress_local_fixups) || f.submission_mode))
{
- uschar *oname = US"";
+ const uschar * oname = US"";
/* Use the originator_name if this is a locally submitted message and the
caller is not trusted. For trusted callers, use it only if -F was used to
if (!sender_host_address)
{
- if (!trusted_caller || sender_name_forced ||
- (!smtp_input && !sender_address_forced))
+ if (!f.trusted_caller || f.sender_name_forced ||
+ (!smtp_input && !f.sender_address_forced))
oname = originator_name;
}
resent_prefix, oname, *oname ? " <" : "");
fromend = *oname ? US">" : US"";
- if (sender_local || local_error_message)
+ if (f.sender_local || f.local_error_message)
header_add(htype_from, "%s%s@%s%s\n", fromstart,
local_part_quote(originator_login), qualify_domain_sender,
fromend);
- else if (submission_mode && authenticated_id)
+ else if (f.submission_mode && authenticated_id)
{
if (!submission_domain)
header_add(htype_from, "%s%s@%s%s\n", fromstart,
parse_extract_address fails, and a Sender: header is inserted, as required. */
if ( from_header
- && ( active_local_from_check
- && ( sender_local && !trusted_caller && !suppress_local_fixups
- || submission_mode && authenticated_id
+ && ( f.active_local_from_check
+ && ( f.sender_local && !f.trusted_caller && !f.suppress_local_fixups
+ || f.submission_mode && authenticated_id
) ) )
{
BOOL make_sender = TRUE;
&start, &end, &domain, FALSE);
uschar *generated_sender_address;
- generated_sender_address = submission_mode
+ generated_sender_address = f.submission_mode
? !submission_domain
? string_sprintf("%s@%s",
local_part_quote(authenticated_id), qualify_domain_sender)
uschar *at = domain ? from_address + domain - 1 : NULL;
if (at) *at = 0;
- from_address += route_check_prefix(from_address, local_from_prefix);
- slen = route_check_suffix(from_address, local_from_suffix);
- if (slen > 0)
+ from_address += route_check_prefix(from_address, local_from_prefix, NULL);
+ if ((slen = route_check_suffix(from_address, local_from_suffix, NULL)) > 0)
{
memmove(from_address+slen, from_address, Ustrlen(from_address)-slen);
from_address += slen;
appropriate rewriting rules. */
if (make_sender)
- if (submission_mode && !submission_name)
+ if (f.submission_mode && !submission_name)
header_add(htype_sender, "%sSender: %s\n", resent_prefix,
generated_sender_address);
else
header_add(htype_sender, "%sSender: %s <%s>\n",
resent_prefix,
- submission_mode? submission_name : originator_name,
+ f.submission_mode ? submission_name : originator_name,
generated_sender_address);
/* Ensure that a non-null envelope sender address corresponds to the
submission mode sender address. */
- if (submission_mode && *sender_address)
+ if (f.submission_mode && *sender_address)
{
if (!sender_address_unrewritten)
sender_address_unrewritten = sender_address;
/* If there are any rewriting rules, apply them to the sender address, unless
it has already been rewritten as part of verification for SMTP input. */
+DEBUG(D_rewrite)
+ { debug_printf("global rewrite rules\n"); acl_level++; }
if (global_rewrite_rules && !sender_address_unrewritten && *sender_address)
{
- sender_address = rewrite_address(sender_address, FALSE, TRUE,
+ /* deconst ok as src was not const */
+ sender_address = US rewrite_address(sender_address, FALSE, TRUE,
global_rewrite_rules, rewrite_existflags);
DEBUG(D_receive|D_rewrite)
debug_printf("rewritten sender = %s\n", sender_address);
}
+DEBUG(D_rewrite) acl_level--;
/* The headers must be run through rewrite_header(), because it ensures that
documented as happening *after* recipient addresses are taken from the headers
by the -t command line option. An added Sender: gets rewritten here. */
-for (h = header_list->next; h; h = h->next)
- {
- header_line *newh = rewrite_header(h, NULL, NULL, global_rewrite_rules,
- rewrite_existflags, TRUE);
- if (newh) h = newh;
- }
+DEBUG(D_rewrite)
+ { debug_printf("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)))
+ h = newh;
+DEBUG(D_rewrite) acl_level--;
/* An RFC 822 (sic) message is not legal unless it has at least one of "to",
*/
if ( !date_header_exists
- && ((!sender_host_address && !suppress_local_fixups) || submission_mode))
+ && ((!sender_host_address && !f.suppress_local_fixups) || f.submission_mode))
header_add_at_position(!resents_exist, NULL, FALSE, htype_other,
"%sDate: %s\n", resent_prefix, tod_stamp(tod_full));
DEBUG(D_receive)
{
debug_printf(">>Headers after rewriting and local additions:\n");
- for (h = header_list->next; h; h = h->next)
+ for (header_line * h = header_list->next; h; h = h->next)
debug_printf("%c %s", h->type, h->text);
debug_printf("\n");
}
so the timestamp behaviour is a change to the normal case.
Having created it, send the headers to the destination. */
-if (cutthrough.fd >= 0 && cutthrough.delivery)
+if (cutthrough.cctx.sock >= 0 && cutthrough.delivery)
{
if (received_count > received_headers_max)
{
/* Make sure the file's group is the Exim gid, and double-check the mode
because the group setting doesn't always get set automatically. */
-if (fchown(data_fd, exim_uid, exim_gid))
+if (0 != exim_fchown(data_fd, exim_uid, exim_gid, spool_name))
log_write(0, LOG_MAIN|LOG_PANIC_DIE,
"Failed setting ownership on spool file %s: %s",
spool_name, strerror(errno));
are problems when Exim is run under Cygwin (I'm told). See comments in
spool_in.c, where the same locking is done. */
-data_file = fdopen(data_fd, "w+");
+spool_data_file = fdopen(data_fd, "w+");
lock_data.l_type = F_WRLCK;
lock_data.l_whence = SEEK_SET;
lock_data.l_start = 0;
format); write it (remembering that it might contain binary zeros). The result
of fwrite() isn't inspected; instead we call ferror() below. */
-fprintf(data_file, "%s-D\n", message_id);
+fprintf(spool_data_file, "%s-D\n", message_id);
if (next)
{
uschar *s = next->text;
int len = next->slen;
- if (fwrite(s, 1, len, data_file) == len) /* "if" for compiler quietening */
+ if (fwrite(s, 1, len, spool_data_file) == len) /* "if" for compiler quietening */
body_linecount++; /* Assumes only 1 line */
}
(indicated by '.'), or might have encountered an error while writing the
message id or "next" line. */
-if (!ferror(data_file) && !(receive_feof)() && message_ended != END_DOT)
+if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT)
{
if (smtp_input)
{
message_ended = chunking_state <= CHUNKING_OFFERED
- ? read_message_data_smtp(data_file)
+ ? read_message_data_smtp(spool_data_file)
: spool_wireformat
- ? read_message_bdat_smtp_wire(data_file)
- : read_message_bdat_smtp(data_file);
+ ? read_message_bdat_smtp_wire(spool_data_file)
+ : read_message_bdat_smtp(spool_data_file);
receive_linecount++; /* The terminating "." line */
}
else
- message_ended = read_message_data(data_file);
+ message_ended = read_message_data(spool_data_file);
receive_linecount += body_linecount; /* For BSMTP errors mainly */
message_linecount += body_linecount;
}
else
{
- fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+ fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
give_local_error(ERRMESS_TOOBIG,
string_sprintf("message too big (max=%d)", thismessage_size_limit),
- US"message rejected: ", error_rc, data_file, header_list);
+ US"message rejected: ", error_rc, spool_data_file, header_list);
/* Does not return */
}
break;
the input in cases of output errors, since the far end doesn't expect to see
anything until the terminating dot line is sent. */
-if (fflush(data_file) == EOF || ferror(data_file) ||
- EXIMfsync(fileno(data_file)) < 0 || (receive_ferror)())
+if (fflush(spool_data_file) == EOF || ferror(spool_data_file) ||
+ EXIMfsync(fileno(spool_data_file)) < 0 || (receive_ferror)())
{
uschar *msg_errno = US strerror(errno);
BOOL input_error = (receive_ferror)() != 0;
else
{
- fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
- give_local_error(ERRMESS_IOERR, msg, US"", error_rc, data_file,
+ fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+ give_local_error(ERRMESS_IOERR, msg, US"", error_rc, spool_data_file,
header_list);
/* Does not return */
}
/* No I/O errors were encountered while writing the data file. */
DEBUG(D_receive) debug_printf("Data file written for message %s\n", message_id);
-if (LOGGING(receive_time)) timesince(&received_time_taken, &received_time);
+gettimeofday(&received_time_complete, NULL);
/* If there were any bad addresses extracted by -t, or there were no recipients
if (recipients_count == 0) debug_printf("*** No recipients\n");
if (bad_addresses)
{
- error_block *eblock = bad_addresses;
debug_printf("*** Bad address(es)\n");
- while (eblock != NULL)
- {
+ for (error_block * eblock = bad_addresses; eblock; eblock = eblock->next)
debug_printf(" %s: %s\n", eblock->text1, eblock->text2);
- eblock = eblock->next;
- }
}
}
- fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+ log_write(0, LOG_MAIN|LOG_PANIC, "%s %s found in headers",
+ message_id, bad_addresses ? "bad addresses" : "no recipients");
+
+ fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
/* If configured to send errors to the sender, but this fails, force
a failure error code. We use a special one for no recipients so that it
if (error_handling == ERRORS_SENDER)
{
if (!moan_to_sender(
- (bad_addresses == NULL)?
- (extracted_ignored? ERRMESS_IGADDRESS : ERRMESS_NOADDRESS) :
- (recipients_list == NULL)? ERRMESS_BADNOADDRESS : ERRMESS_BADADDRESS,
- bad_addresses, header_list, data_file, FALSE))
- error_rc = (bad_addresses == NULL)? EXIT_NORECIPIENTS : EXIT_FAILURE;
+ bad_addresses
+ ? recipients_list ? ERRMESS_BADADDRESS : ERRMESS_BADNOADDRESS
+ : extracted_ignored ? ERRMESS_IGADDRESS : ERRMESS_NOADDRESS,
+ bad_addresses, header_list, spool_data_file, FALSE
+ ) )
+ error_rc = bad_addresses ? EXIT_FAILURE : EXIT_NORECIPIENTS;
}
else
{
if (recipients_count == 0 || error_handling == ERRORS_STDERR)
{
Uunlink(spool_name);
- (void)fclose(data_file);
- exim_exit(error_rc, US"receiving");
+ (void)fclose(spool_data_file);
+ exim_exit(error_rc);
}
}
deliver_datafile = data_fd;
user_msg = NULL;
-enable_dollar_recipients = TRUE;
+f.enable_dollar_recipients = TRUE;
if (recipients_count == 0)
- blackholed_by = recipients_discarded ? US"MAIL ACL" : US"RCPT ACL";
+ blackholed_by = f.recipients_discarded ? US"MAIL ACL" : US"RCPT ACL";
else
{
{
#ifndef DISABLE_DKIM
- if (!dkim_disable_verify)
+ if (!f.dkim_disable_verify)
{
/* Finish verification */
dkim_exim_verify_finish();
goto TIDYUP;
#endif /* WITH_CONTENT_SCAN */
-#ifdef EXPERIMENTAL_DMARC
- dmarc_up = dmarc_store_data(from_header);
-#endif /* EXPERIMENTAL_DMARC */
+#ifdef SUPPORT_DMARC
+ dmarc_store_data(from_header);
+#endif
#ifndef DISABLE_PRDR
if (prdr_requested && recipients_count > 1 && acl_smtp_data_prdr)
{
- unsigned int c;
int all_pass = OK;
int all_fail = FAIL;
smtp_printf("353 PRDR content analysis beginning\r\n", TRUE);
/* Loop through recipients, responses must be in same order received */
- for (c = 0; recipients_count > c; c++)
+ for (unsigned int c = 0; recipients_count > c; c++)
{
uschar * addr= recipients_list[c].address;
uschar * msg= US"PRDR R=<%s> %s";
if (acl_not_smtp)
{
uschar *user_msg, *log_msg;
- authentication_local = TRUE;
+ f.authentication_local = TRUE;
rc = acl_check(ACL_WHERE_NOTSMTP, NULL, acl_not_smtp, &user_msg, &log_msg);
if (rc == DISCARD)
{
/* Does not return */
else
{
- fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+ fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
give_local_error(ERRMESS_LOCAL_ACL, user_msg,
- US"message rejected by non-SMTP ACL: ", error_rc, data_file,
+ US"message rejected by non-SMTP ACL: ", error_rc, spool_data_file,
header_list);
/* Does not return */
}
/* The applicable ACLs have been run */
- if (deliver_freeze) frozen_by = US"ACL"; /* for later logging */
- if (queue_only_policy) queued_by = US"ACL";
+ if (f.deliver_freeze) frozen_by = US"ACL"; /* for later logging */
+ if (f.queue_only_policy) queued_by = US"ACL";
}
#ifdef WITH_CONTENT_SCAN
had_local_scan_timeout = 0;
os_non_restarting_signal(SIGALRM, local_scan_timeout_handler);
- if (local_scan_timeout > 0) alarm(local_scan_timeout);
+ if (local_scan_timeout > 0) ALARM(local_scan_timeout);
rc = local_scan(data_fd, &local_scan_data);
- alarm(0);
+ ALARM_CLR(0);
os_non_restarting_signal(SIGALRM, sigalrm_handler);
- enable_dollar_recipients = FALSE;
+ f.enable_dollar_recipients = FALSE;
store_pool = POOL_MAIN; /* In case changed */
DEBUG(D_receive) debug_printf("local_scan() returned %d %s\n", rc,
if (rc == LOCAL_SCAN_ACCEPT_FREEZE)
{
- if (!deliver_freeze) /* ACL might have already frozen */
+ if (!f.deliver_freeze) /* ACL might have already frozen */
{
- deliver_freeze = TRUE;
+ f.deliver_freeze = TRUE;
deliver_frozen_at = time(NULL);
frozen_by = US"local_scan()";
}
}
else if (rc == LOCAL_SCAN_ACCEPT_QUEUE)
{
- if (!queue_only_policy) /* ACL might have already queued */
+ if (!f.queue_only_policy) /* ACL might have already queued */
{
- queue_only_policy = TRUE;
+ f.queue_only_policy = TRUE;
queued_by = US"local_scan()";
}
rc = LOCAL_SCAN_ACCEPT;
if (rc == LOCAL_SCAN_ACCEPT)
{
if (local_scan_data)
- {
- uschar *s;
- for (s = local_scan_data; *s != 0; s++) if (*s == '\n') *s = ' ';
- }
- for (i = 0; i < recipients_count; i++)
+ for (uschar * s = local_scan_data; *s != 0; s++) if (*s == '\n') *s = ' ';
+ for (int i = 0; i < recipients_count; i++)
{
recipient_item *r = recipients_list + i;
r->address = rewrite_address_qualify(r->address, TRUE);
- if (r->errors_to != NULL)
+ if (r->errors_to)
r->errors_to = rewrite_address_qualify(r->errors_to, TRUE);
}
- if (recipients_count == 0 && blackholed_by == NULL)
+ if (recipients_count == 0 && !blackholed_by)
blackholed_by = US"local_scan";
}
string_from_gstring(g), istemp, string_printing(errmsg));
if (smtp_input)
- {
if (!smtp_batched_input)
{
smtp_respond(smtp_code, 3, TRUE, errmsg);
else
moan_smtp_batch(NULL, "%s %s", smtp_code, errmsg);
/* Does not return */
- }
else
{
- fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+ fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
give_local_error(ERRMESS_LOCAL_SCAN, errmsg,
- US"message rejected by local scan code: ", error_rc, data_file,
+ US"message rejected by local scan code: ", error_rc, spool_data_file,
header_list);
/* Does not return */
}
/* Ensure the first time flag is set in the newly-received message. */
-deliver_firsttime = TRUE;
+f.deliver_firsttime = TRUE;
#ifdef EXPERIMENTAL_BRIGHTMAIL
if (bmi_run == 1)
if (mua_wrapper)
{
- deliver_freeze = FALSE;
- queue_only_policy = FALSE;
+ f.deliver_freeze = FALSE;
+ f.queue_only_policy = FALSE;
}
/* Keep the data file open until we have written the header file, in order to
if (host_checking || blackholed_by)
{
- header_line *h;
Uunlink(spool_name);
msg_size = 0; /* Compute size for log line */
- for (h = header_list; h; h = h->next)
+ for (header_line * h = header_list; h; h = h->next)
if (h->type != '*') msg_size += h->slen;
}
}
else
{
- fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
- give_local_error(ERRMESS_IOERR, errmsg, US"", error_rc, data_file,
+ fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+ give_local_error(ERRMESS_IOERR, errmsg, US"", error_rc, spool_data_file,
header_list);
/* Does not return */
}
receive_messagecount++;
-/* In SMTP sessions we may receive several in one connection. After each one,
-we wait for the clock to tick at the level of message-id granularity. This is
-so that the combination of time+pid is unique, even on systems where the pid
-can be re-used within our time interval. We can't shorten the interval without
-re-designing the message-id. See comments above where the message id is
-created. This is Something For The Future. */
-
-message_id_tv.tv_usec = (message_id_tv.tv_usec/id_resolution) * id_resolution;
-exim_wait_tick(&message_id_tv, id_resolution);
-
/* 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(data_file))
+if (fflush(spool_data_file))
{
errmsg = string_sprintf("Spool write error: %s", strerror(errno));
log_write(0, LOG_MAIN, "%s\n", errmsg);
}
else
{
- fseek(data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
- give_local_error(ERRMESS_IOERR, errmsg, US"", error_rc, data_file,
+ fseek(spool_data_file, (long int)SPOOL_DATA_START_OFFSET, SEEK_SET);
+ give_local_error(ERRMESS_IOERR, errmsg, US"", error_rc, spool_data_file,
header_list);
/* Does not return */
}
message id is actually an addr-spec, we can use the parse routine to canonicalize
it. */
+rcvd_log_reset_point = store_mark();
g = string_get(256);
g = string_append(g, 2,
g = add_host_info_for_log(g);
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
if (LOGGING(tls_cipher) && tls_in.cipher)
+ {
g = string_append(g, 2, US" X=", tls_in.cipher);
+# ifndef DISABLE_TLS_RESUME
+ if (LOGGING(tls_resumption) && tls_in.resumption & RESUME_USED)
+ g = string_catn(g, US"*", 1);
+# endif
+ }
if (LOGGING(tls_certificate_verified) && tls_in.cipher)
g = string_append(g, 2, US" CV=", tls_in.certificate_verified ? "yes":"no");
if (LOGGING(tls_peerdn) && tls_in.peerdn)
g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\"");
if (LOGGING(tls_sni) && tls_in.sni)
- g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\"");
+ g = string_append(g, 2, US" SNI=", string_printing2(tls_in.sni, SP_TAB|SP_SPACE));
#endif
if (sender_host_authenticated)
if (chunking_state > CHUNKING_OFFERED)
g = string_catn(g, US" K", 2);
-sprintf(CS big_buffer, "%d", msg_size);
-g = string_append(g, 2, US" S=", big_buffer);
+g = string_fmt_append(g, " S=%d", msg_size);
/* log 8BITMIME mode announced in MAIL_FROM
0 ... no BODY= used
7 ... 7BIT
8 ... 8BITMIME */
if (LOGGING(8bitmime))
- {
- sprintf(CS big_buffer, "%d", body_8bitmime);
- g = string_append(g, 2, US" M8S=", big_buffer);
- }
+ g = string_fmt_append(g, " M8S=%d", body_8bitmime);
#ifndef DISABLE_DKIM
if (LOGGING(dkim) && dkim_verify_overall)
#endif
if (LOGGING(receive_time))
- g = string_append(g, 2, US" RT=", string_timediff(&received_time_taken));
+ {
+ struct timeval diff = received_time_complete;
+ timediff(&diff, &received_time);
+ g = string_append(g, 2, US" RT=", string_timediff(&diff));
+ }
if (*queue_name)
g = string_append(g, 2, US" Q=", queue_name);
Therefore, make sure we use a printing-characters only version for the log.
Also, allow for domain literals in the message id. */
-if (msgid_header)
+if ( LOGGING(msg_id) && msgid_header
+ && (LOGGING(msg_id_created) || !msgid_header_newly_created)
+ )
{
- uschar *old_id;
+ uschar * old_id;
BOOL save_allow_domain_literals = allow_domain_literals;
allow_domain_literals = TRUE;
+ int start, end, domain;
+
old_id = parse_extract_address(Ustrchr(msgid_header->text, ':') + 1,
&errmsg, &start, &end, &domain, FALSE);
allow_domain_literals = save_allow_domain_literals;
- if (old_id != NULL)
- g = string_append(g, 2, US" id=", string_printing(old_id));
+ if (old_id)
+ g = string_append(g, 2,
+ msgid_header_newly_created ? US" id*=" : US" id=",
+ string_printing(old_id));
}
/* If subject logging is turned on, create suitable printing-character
if (LOGGING(subject) && subject_header)
{
- int i;
uschar *p = big_buffer;
uschar *ss = expand_string(US"$h_subject:");
a C-like string, and turn any non-printers into escape sequences. */
*p++ = '\"';
- if (*ss != 0) for (i = 0; i < 100 && ss[i] != 0; i++)
+ if (*ss != 0) for (int i = 0; i < 100 && ss[i] != 0; i++)
{
if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\';
*p++ = ss[i];
{
int fd;
uschar * m_name = spool_fname(US"msglog", message_subdir, message_id, US"");
-
+
if ( (fd = Uopen(m_name, O_WRONLY|O_APPEND|O_CREAT, SPOOL_MODE)) < 0
&& errno == ENOENT
)
{
uschar *now = tod_stamp(tod_log);
fprintf(message_log, "%s Received from %s\n", now, g->s+3);
- if (deliver_freeze) fprintf(message_log, "%s frozen by %s\n", now,
+ if (f.deliver_freeze) fprintf(message_log, "%s frozen by %s\n", now,
frozen_by);
- if (queue_only_policy) fprintf(message_log,
+ if (f.queue_only_policy) fprintf(message_log,
"%s no immediate delivery: queued%s%s by %s\n", now,
*queue_name ? " in " : "", *queue_name ? CS queue_name : "",
queued_by);
arrival, and outputting an SMTP response. While writing to the log, set a flag
to cause a call to receive_bomb_out() if the log cannot be opened. */
-receive_call_bombout = TRUE;
+f.receive_call_bombout = TRUE;
/* Before sending an SMTP response in a TCP/IP session, we check to see if the
connection has gone away. This can only be done if there is no unconsumed input
waiting in the local input buffer. We can test for this by calling
-receive_smtp_buffered(). RFC 2920 (pipelining) explicitly allows for additional
+receive_hasc(). RFC 2920 (pipelining) explicitly allows for additional
input to be sent following the final dot, so the presence of following input is
not an error.
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 && !sender_host_notsocket &&
- !receive_smtp_buffered())
+if ( smtp_input && sender_host_address && !f.sender_host_notsocket
+ && !receive_hasc())
{
- struct timeval tv;
- fd_set select_check;
- FD_ZERO(&select_check);
- FD_SET(fileno(smtp_in), &select_check);
- tv.tv_sec = 0;
- tv.tv_usec = 0;
-
- if (select(fileno(smtp_in) + 1, &select_check, NULL, NULL, &tv) != 0)
+ if (poll_one_fd(fileno(smtp_in), POLLIN, 0) != 0)
{
int c = (receive_getc)(GETC_BUFFER_UNLIMITED);
if (c != EOF) (receive_ungetc)(c); else
XXX We do not handle queue-only, freezing, or blackholes.
*/
-if(cutthrough.fd >= 0 && cutthrough.delivery)
+if(cutthrough.cctx.sock >= 0 && cutthrough.delivery)
{
uschar * msg = cutthrough_finaldot(); /* Ask the target system to accept the message */
/* Logging was done in finaldot() */
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);
+ if (cutthrough.defer_pass) smtp_reply = string_copy_perm(msg, TRUE);
cutthrough_done = TMP_REJ; /* Avoid the usual immediate delivery attempt */
break; /* message_id needed for SMTP accept below */
break; /* message_id needed for SMTP accept below */
case '5': /* Perm-reject. Do the same to the source. Dump any spoolfiles */
- smtp_reply = string_copy_malloc(msg); /* Pass on the exact error */
+ smtp_reply = string_copy_perm(msg, TRUE); /* Pass on the exact error */
cutthrough_done = PERM_REJ;
break;
}
/* Log any control actions taken by an ACL or local_scan(). */
- if (deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by);
- if (queue_only_policy) log_write(L_delay_delivery, LOG_MAIN,
+ if (f.deliver_freeze) log_write(0, LOG_MAIN, "frozen by %s", frozen_by);
+ if (f.queue_only_policy) log_write(L_delay_delivery, LOG_MAIN,
"no immediate delivery: queued%s%s by %s",
- *queue_name ? " in " : "", *queue_name ? CS queue_name : "",
+ *queue_name ? " in " : "", *queue_name ? CS queue_name : "",
queued_by);
}
-receive_call_bombout = FALSE;
+f.receive_call_bombout = FALSE;
-store_reset(g); /* The store for the main log message can be reused */
+/* The store for the main log message can be reused */
+rcvd_log_reset_point = store_reset(rcvd_log_reset_point);
/* If the message is frozen, and freeze_tell is set, do the telling. */
-if (deliver_freeze && freeze_tell && freeze_tell[0])
+if (f.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);
TIDYUP:
process_info[process_info_len] = 0; /* Remove message id */
-if (data_file && cutthrough_done == NOT_TRIED)
- if (fclose(data_file)) /* Frees the lock */
+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));
+ spool_data_file = NULL;
+ }
/* Now reset signal handlers to their defaults */
else if (chunking_state > CHUNKING_OFFERED)
{
- smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n", FALSE,
+ /* If there is more input waiting, no need to flush (probably the client
+ pipelined QUIT after data). We check only the in-process buffer, not
+ the socket. */
+
+ smtp_printf("250- %u byte chunk, total %d\r\n250 OK id=%s\r\n",
+ receive_hasc(),
chunking_datasize, message_size+message_linecount, message_id);
chunking_state = CHUNKING_OFFERED;
}
else
- smtp_printf("250 OK id=%s\r\n", FALSE, message_id);
+ smtp_printf("250 OK id=%s\r\n", receive_hasc(), message_id);
if (host_checking)
fprintf(stdout,
}
if (cutthrough_done != NOT_TRIED)
{
- if (data_file)
- (void) fclose(data_file); /* Frees the lock; do not care if error */
+ if (spool_data_file)
+ {
+ (void) fclose(spool_data_file); /* Frees the lock; do not care if error */
+ spool_data_file = NULL;
+ }
message_id[0] = 0; /* Prevent a delivery from starting */
cutthrough.delivery = cutthrough.callout_hold_only = FALSE;
cutthrough.defer_pass = FALSE;