* Exim - an Internet mail transport agent *
*************************************************/
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
/* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 */
/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
/* The main code for delivering a message. */
static BOOL remove_journal;
static int parcount = 0;
static pardata *parlist = NULL;
+static struct pollfd *parpoll;
static int return_count;
static uschar *frozen_info = US"";
static uschar *used_return_path = NULL;
address_item *
deliver_make_addr(uschar *address, BOOL copy)
{
-address_item *addr = store_get(sizeof(address_item), FALSE);
+address_item * addr = store_get(sizeof(address_item), GET_UNTAINTED);
*addr = address_defaults;
if (copy) address = string_copy(address);
addr->address = address;
open_msglog_file(uschar *filename, int mode, uschar **error)
{
if (Ustrstr(filename, US"/../"))
- log_write(0, LOG_MAIN|LOG_PANIC,
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE,
"Attempt to open msglog file path with upward-traversal: '%s'\n", filename);
for (int i = 2; i > 0; i--)
{
int fd = Uopen(filename,
-#ifdef O_CLOEXEC
- O_CLOEXEC |
-#endif
-#ifdef O_NOFOLLOW
- O_NOFOLLOW |
-#endif
- O_WRONLY|O_APPEND|O_CREAT, mode);
+ EXIM_CLOEXEC | EXIM_NOFOLLOW | O_WRONLY|O_APPEND|O_CREAT, mode);
if (fd >= 0)
{
/* Set the close-on-exec flag and change the owner to the exim uid/gid (this
#ifndef DISABLE_EVENT
+/* Distribute a named event to any listeners.
+
+Args: action config option specifying listener
+ event name of the event
+ ev_data associated data for the event
+ errnop pointer to errno for modification, or null
+
+Return: string expansion from listener, or NULL
+*/
+
uschar *
-event_raise(uschar * action, const uschar * event, uschar * ev_data)
+event_raise(uschar * action, const uschar * event, uschar * ev_data, int * errnop)
{
uschar * s;
if (action)
{
DEBUG(D_deliver)
debug_printf("Event(%s): event_action returned \"%s\"\n", event, s);
- errno = ERRNO_EVENT;
+ if (errnop)
+ *errnop = ERRNO_EVENT;
return s;
}
}
a filter was used which triggered a fail command (in such a case
a transport isn't needed). Convert it to an internal fail event. */
- (void) event_raise(event_action, US"msg:fail:internal", addr->message);
+ (void) event_raise(event_action, US"msg:fail:internal", addr->message, NULL);
}
}
else
|| Ustrcmp(addr->transport->driver_name, "smtp") == 0
|| Ustrcmp(addr->transport->driver_name, "lmtp") == 0
|| Ustrcmp(addr->transport->driver_name, "autoreply") == 0
- ? addr->message : NULL);
+ ? addr->message : NULL,
+ NULL);
}
deliver_host_port = save_port;
/*************************************************
-* Generate local prt for logging *
+* Generate local part for logging *
*************************************************/
+static uschar *
+string_get_lpart_sub(const address_item * addr, uschar * s)
+{
+#ifdef SUPPORT_I18N
+if (testflag(addr, af_utf8_downcvt))
+ {
+ uschar * t = string_localpart_utf8_to_alabel(s, NULL);
+ return t ? t : s; /* t is NULL on a failed conversion */
+ }
+#endif
+return s;
+}
+
/* This function is a subroutine for use in string_log_address() below.
Arguments:
{
uschar * s;
-s = addr->prefix;
-if (testflag(addr, af_include_affixes) && s)
- {
-#ifdef SUPPORT_I18N
- if (testflag(addr, af_utf8_downcvt))
- s = string_localpart_utf8_to_alabel(s, NULL);
-#endif
- yield = string_cat(yield, s);
- }
+if (testflag(addr, af_include_affixes) && (s = addr->prefix))
+ yield = string_cat(yield, string_get_lpart_sub(addr, s));
-s = addr->local_part;
-#ifdef SUPPORT_I18N
-if (testflag(addr, af_utf8_downcvt))
- s = string_localpart_utf8_to_alabel(s, NULL);
-#endif
-yield = string_cat(yield, s);
+yield = string_cat(yield, string_get_lpart_sub(addr, addr->local_part));
-s = addr->suffix;
-if (testflag(addr, af_include_affixes) && s)
- {
-#ifdef SUPPORT_I18N
- if (testflag(addr, af_utf8_downcvt))
- s = string_localpart_utf8_to_alabel(s, NULL);
-#endif
- yield = string_cat(yield, s);
- }
+if (testflag(addr, af_include_affixes) && (s = addr->suffix))
+ yield = string_cat(yield, string_get_lpart_sub(addr, s));
return yield;
}
else
{
uschar * cmp;
- int off = g->ptr; /* start of the "full address" */
+ int off = gstring_length(g); /* start of the "full address" */
if (addr->local_part)
{
#endif
reset_point = store_mark();
-g = string_get_tainted(256, TRUE); /* addrs will be tainted, so avoid copy */
+g = string_get_tainted(256, GET_TAINTED); /* addrs will be tainted, so avoid copy */
if (msg)
g = string_append(g, 2, host_and_ident(TRUE), US" ");
if (*queue_name)
g = string_append(g, 2, US" Q=", queue_name);
-#ifdef EXPERIMENTAL_SRS_ALT
-if(addr->prop.srs_sender)
- g = string_append(g, 3, US" SRS=<", addr->prop.srs_sender, US">");
-#endif
-
/* You might think that the return path must always be set for a successful
delivery; indeed, I did for some time, until this statement crashed. The case
when it is not set is for a delivery to /dev/null which is optimised by not
if (addr->message)
g = string_append(g, 2, US": ", addr->message);
-(void) string_from_gstring(g);
+ {
+ const uschar * s = string_from_gstring(g);
-/* Log the deferment in the message log, but don't clutter it
-up with retry-time defers after the first delivery attempt. */
+ /* Log the deferment in the message log, but don't clutter it
+ up with retry-time defers after the first delivery attempt. */
-if (f.deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
- deliver_msglog("%s %s\n", now, g->s);
+ if (f.deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
+ deliver_msglog("%s %s\n", now, s);
-/* Write the main log and reset the store.
-For errors of the type "retry time not reached" (also remotes skipped
-on queue run), logging is controlled by L_retry_defer. Note that this kind
-of error number is negative, and all the retry ones are less than any
-others. */
+ /* Write the main log and reset the store.
+ For errors of the type "retry time not reached" (also remotes skipped
+ on queue run), logging is controlled by L_retry_defer. Note that this kind
+ of error number is negative, and all the retry ones are less than any
+ others. */
-log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags,
- "== %s", g->s);
+ log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags,
+ "== %s", s);
+ }
store_reset(reset_point);
return;
if (LOGGING(deliver_time))
g = string_append(g, 2, US" DT=", string_timediff(&addr->delivery_time));
-(void) string_from_gstring(g);
-
/* Do the logging. For the message log, "routing failed" for those cases,
just to make it clearer. */
-if (driver_kind)
- deliver_msglog("%s %s failed for %s\n", now, driver_kind, g->s);
-else
- deliver_msglog("%s %s\n", now, g->s);
+ {
+ const uschar * s = string_from_gstring(g);
-log_write(0, LOG_MAIN, "** %s", g->s);
+ if (driver_kind)
+ deliver_msglog("%s %s failed for %s\n", now, driver_kind, s);
+ else
+ deliver_msglog("%s %s\n", now, s);
+
+ log_write(0, LOG_MAIN, "** %s", s);
+ }
store_reset(reset_point);
return;
/* Each local delivery is performed in a separate process which sets its
uid and gid as specified. This is a safer way than simply changing and
-restoring using seteuid(); there is a body of opinion that seteuid() cannot be
-used safely. From release 4, Exim no longer makes any use of it. Besides, not
-all systems have seteuid().
+restoring using seteuid(); there is a body of opinion that seteuid()
+cannot be used safely. From release 4, Exim no longer makes any use of
+it for delivery. Besides, not all systems have seteuid().
If the uid/gid are specified in the transport_instance, they are used; the
transport initialization must ensure that either both or neither are set.
if(addr->prop.errors_address)
return_path = addr->prop.errors_address;
-#ifdef EXPERIMENTAL_SRS_ALT
-else if (addr->prop.srs_sender)
- return_path = addr->prop.srs_sender;
-#endif
else
return_path = sender_address;
{
BOOL ok = TRUE;
set_process_info("delivering %s to %s using %s", message_id,
- addr->local_part, addr->transport->name);
+ addr->local_part, tp->name);
- /* Setting this global in the subprocess means we need never clear it */
+ /* Setting these globals in the subprocess means we need never clear them */
transport_name = addr->transport->name;
+ driver_srcfile = tp->srcfile;
+ driver_srcline = tp->srcline;
/* If a transport filter has been specified, set up its argument list.
Any errors will get put into the address, and FALSE yielded. */
- if (addr->transport->filter_command)
+ if (tp->filter_command)
{
ok = transport_set_up_command(&transport_filter_argv,
- addr->transport->filter_command,
- TRUE, PANIC, addr, US"transport filter", NULL);
- transport_filter_timeout = addr->transport->filter_timeout;
+ tp->filter_command,
+ TRUE, PANIC, addr, FALSE, US"transport filter", NULL);
+ transport_filter_timeout = tp->filter_timeout;
}
else transport_filter_argv = NULL;
if (ok)
{
- debug_print_string(addr->transport->debug_string);
- replicate = !(addr->transport->info->code)(addr->transport, addr);
+ debug_print_string(tp->debug_string);
+ replicate = !(tp->info->code)(addr->transport, addr);
}
}
uschar *s;
int ret;
- if( (ret = write(pfd[pipe_write], &addr2->transport_return, sizeof(int))) != sizeof(int)
+ if( (i = addr2->transport_return, (ret = write(pfd[pipe_write], &i, sizeof(int))) != sizeof(int))
|| (ret = write(pfd[pipe_write], &transport_count, sizeof(transport_count))) != sizeof(transport_count)
|| (ret = write(pfd[pipe_write], &addr2->flags, sizeof(addr2->flags))) != sizeof(addr2->flags)
|| (ret = write(pfd[pipe_write], &addr2->basic_errno, sizeof(int))) != sizeof(int)
|| (ret = write(pfd[pipe_write], &addr2->more_errno, sizeof(int))) != sizeof(int)
|| (ret = write(pfd[pipe_write], &addr2->delivery_time, sizeof(struct timeval))) != sizeof(struct timeval)
- || (ret = write(pfd[pipe_write], &addr2->special_action, sizeof(int))) != sizeof(int)
+ || (i = addr2->special_action, (ret = write(pfd[pipe_write], &i, sizeof(int))) != sizeof(int))
|| (ret = write(pfd[pipe_write], &addr2->transport,
sizeof(transport_instance *))) != sizeof(transport_instance *)
len = read(pfd[pipe_read], &addr2->basic_errno, sizeof(int));
len = read(pfd[pipe_read], &addr2->more_errno, sizeof(int));
len = read(pfd[pipe_read], &addr2->delivery_time, sizeof(struct timeval));
- len = read(pfd[pipe_read], &addr2->special_action, sizeof(int));
+ len = read(pfd[pipe_read], &i, sizeof(int)); addr2->special_action = i;
len = read(pfd[pipe_read], &addr2->transport,
sizeof(transport_instance *));
else for (addr2 = addr; addr2; addr2 = addr2->next)
if (addr2->transport_return == OK)
{
- addr3 = store_get(sizeof(address_item), FALSE);
+ addr3 = store_get(sizeof(address_item), GET_UNTAINTED);
*addr3 = *addr2;
addr3->next = NULL;
addr3->shadow_message = US &addr2->shadow_message;
/* Loop through all items, reading from the pipe when necessary. The pipe
used to be non-blocking. But I do not see a reason for using non-blocking I/O
-here, as the preceding select() tells us, if data is available for reading.
+here, as the preceding poll() tells us, if data is available for reading.
A read() on a "selected" handle should never block, but(!) it may return
less data then we expected. (The buffer size we pass to read() shouldn't be
if (!r || !(*ptr & rf_delete))
{
- r = store_get(sizeof(retry_item), FALSE);
+ r = store_get(sizeof(retry_item), GET_UNTAINTED);
r->next = addr->retries;
addr->retries = r;
r->flags = *ptr++;
if (*ptr)
{
- h = store_get(sizeof(host_item), FALSE);
+ h = store_get(sizeof(host_item), GET_UNTAINTED);
h->name = string_copy(ptr);
while (*ptr++);
h->address = string_copy(ptr);
par_wait(void)
{
int poffset, status;
-address_item *addr, *addrlist;
+address_item * addr, * addrlist;
pid_t pid;
set_process_info("delivering %s: waiting for a remote delivery subprocess "
existence - in which case give an error return. We cannot proceed just by
waiting for a completion, because a subprocess may have filled up its pipe, and
be waiting for it to be emptied. Therefore, if no processes have finished, we
-wait for one of the pipes to acquire some data by calling select(), with a
+wait for one of the pipes to acquire some data by calling poll(), with a
timeout just in case.
The simple approach is just to iterate after reading data from a ready pipe.
This leads to non-ideal behaviour when the subprocess has written its final Z
item, closed the pipe, and is in the process of exiting (the common case). A
-call to waitpid() yields nothing completed, but select() shows the pipe ready -
+call to waitpid() yields nothing completed, but poll() shows the pipe ready -
reading it yields EOF, so you end up with busy-waiting until the subprocess has
actually finished.
To avoid this, if all the data that is needed has been read from a subprocess
-after select(), an explicit wait() for it is done. We know that all it is doing
+after poll(), an explicit wait() for it is done. We know that all it is doing
is writing to the pipe and then exiting, so the wait should not be long.
The non-blocking waitpid() is to some extent just insurance; if we could
{
while ((pid = waitpid(-1, &status, WNOHANG)) <= 0)
{
- struct timeval tv;
- fd_set select_pipes;
- int maxpipe, readycount;
+ int readycount;
/* A return value of -1 can mean several things. If errno != ECHILD, it
either means invalid options (which we discount), or that this process was
subprocesses are still in existence. If kill() gives an OK return, we know
it must be for one of our processes - it can't be for a re-use of the pid,
because if our process had finished, waitpid() would have found it. If any
- of our subprocesses are in existence, we proceed to use select() as if
+ of our subprocesses are in existence, we proceed to use poll() as if
waitpid() had returned zero. I think this is safe. */
if (pid < 0)
if (poffset >= remote_max_parallel)
{
DEBUG(D_deliver) debug_printf("*** no delivery children found\n");
- return NULL; /* This is the error return */
+ return NULL; /* This is the error return */
}
}
subprocess, but there are no completed subprocesses. See if any pipes are
ready with any data for reading. */
- DEBUG(D_deliver) debug_printf("selecting on subprocess pipes\n");
+ DEBUG(D_deliver) debug_printf("polling subprocess pipes\n");
- maxpipe = 0;
- FD_ZERO(&select_pipes);
for (poffset = 0; poffset < remote_max_parallel; poffset++)
if (parlist[poffset].pid != 0)
- {
- int fd = parlist[poffset].fd;
- FD_SET(fd, &select_pipes);
- if (fd > maxpipe) maxpipe = fd;
- }
+ {
+ parpoll[poffset].fd = parlist[poffset].fd;
+ parpoll[poffset].events = POLLIN;
+ }
+ else
+ parpoll[poffset].fd = -1;
/* Stick in a 60-second timeout, just in case. */
- tv.tv_sec = 60;
- tv.tv_usec = 0;
-
- readycount = select(maxpipe + 1, (SELECT_ARG2_TYPE *)&select_pipes,
- NULL, NULL, &tv);
+ readycount = poll(parpoll, remote_max_parallel, 60 * 1000);
/* Scan through the pipes and read any that are ready; use the count
- returned by select() to stop when there are no more. Select() can return
+ returned by poll() to stop when there are no more. Select() can return
with no processes (e.g. if interrupted). This shouldn't matter.
If par_read_pipe() returns TRUE, it means that either the terminating Z was
poffset++)
{
if ( (pid = parlist[poffset].pid) != 0
- && FD_ISSET(parlist[poffset].fd, &select_pipes)
+ && parpoll[poffset].revents
)
{
readycount--;
"transport process list", pid);
} /* End of the "for" loop */
-/* Come here when all the data was completely read after a select(), and
+/* Come here when all the data was completely read after a poll(), and
the process in pid has been wait()ed for. */
PROCESS_DONE:
"%s %d",
addrlist->transport->driver_name,
status,
- (msb == 0)? "terminated by signal" : "exit code",
+ msb == 0 ? "terminated by signal" : "exit code",
code);
if (msb != 0 || (code != SIGTERM && code != SIGKILL && code != SIGQUIT))
/* Else complete reading the pipe to get the result of the delivery, if all
the data has not yet been obtained. */
-else if (!parlist[poffset].done) (void)par_read_pipe(poffset, TRUE);
+else if (!parlist[poffset].done)
+ (void) par_read_pipe(poffset, TRUE);
/* Put the data count and return path into globals, mark the data slot unused,
decrement the count of subprocesses, and return the address chain. */
if (!parlist)
{
- parlist = store_get(remote_max_parallel * sizeof(pardata), FALSE);
+ parlist = store_get(remote_max_parallel * sizeof(pardata), GET_UNTAINTED);
for (poffset = 0; poffset < remote_max_parallel; poffset++)
parlist[poffset].pid = 0;
+ parpoll = store_get(remote_max_parallel * sizeof(struct pollfd), GET_UNTAINTED);
}
/* Now loop for each remote delivery */
}
/* Get the maximum it can handle in one envelope, with zero meaning
- unlimited, which is forced for the MUA wrapper case. */
+ unlimited, which is forced for the MUA wrapper case and if the
+ value could vary depending on the messages.
+ For those, we only split (below) by (tpt,dest,erraddr,hdrs) and rely on the
+ transport splitting further by max_rcp. So we potentially lose some
+ parallellism. */
- address_count_max = tp->max_addresses;
- if (address_count_max == 0 || mua_wrapper) address_count_max = 999999;
+ address_count_max = mua_wrapper || Ustrchr(tp->max_addresses, '$')
+ ? UNLIMITED_ADDRS : expand_max_rcpt(tp->max_addresses);
/************************************************************************/
if(addr->prop.errors_address)
return_path = addr->prop.errors_address;
-#ifdef EXPERIMENTAL_SRS_ALT
- else if(addr->prop.srs_sender)
- return_path = addr->prop.srs_sender;
-#endif
else
return_path = sender_address;
that it can use either of them, though it prefers O_NONBLOCK, which
distinguishes between EOF and no-more-data. */
-/* The data appears in a timely manner and we already did a select on
+/* The data appears in a timely manner and we already did a poll on
all pipes, so I do not see a reason to use non-blocking IO here
#ifdef O_NONBLOCK
int fd = pfd[pipe_write];
host_item *h;
- /* Setting this global in the subprocess means we need never clear it */
- transport_name = tp->name;
+ /* Setting these globals in the subprocess means we need never clear them */
+ transport_name = addr->transport->name;
+ driver_srcfile = tp->srcfile;
+ driver_srcline = tp->srcline;
/* There are weird circumstances in which logging is disabled */
f.disable_logging = tp->disable_logging;
{
uschar * fname = spool_fname(US"input", message_subdir, message_id, US"-D");
- if ((deliver_datafile = Uopen(fname,
-#ifdef O_CLOEXEC
- O_CLOEXEC |
-#endif
- O_RDWR | O_APPEND, 0)) < 0)
+ if ( (deliver_datafile = Uopen(fname, EXIM_CLOEXEC | O_RDWR | O_APPEND, 0))
+ < 0)
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed to reopen %s for remote "
"parallel delivery: %s", fname, strerror(errno));
}
- /* Set the close-on-exec flag */
-#ifndef O_CLOEXEC
+#ifndef O_CLOEXEC /* Set the close-on-exec flag */
(void)fcntl(deliver_datafile, F_SETFD, fcntl(deliver_datafile, F_GETFD) |
FD_CLOEXEC);
#endif
this, Jan 1999.] We know the syntax is valid, so this can be done by simply
removing quoting backslashes and any unquoted doublequotes. */
-t = addr->cc_local_part = store_get(len+1, is_tainted(address));
+t = addr->cc_local_part = store_get(len+1, address);
while(len-- > 0)
{
int c = *address++;
if (new_address)
{
- address_item *new_parent = store_get(sizeof(address_item), FALSE);
+ address_item * new_parent = store_get(sizeof(address_item), GET_UNTAINTED);
*new_parent = *addr;
addr->parent = new_parent;
new_parent->child_count = 1;
*/
static void
-print_address_error(address_item *addr, FILE *f, uschar *t)
+print_address_error(address_item * addr, FILE * f, const uschar * t)
{
int count = Ustrlen(t);
-uschar *s = testflag(addr, af_pass_message) ? addr->message : NULL;
+uschar * s = testflag(addr, af_pass_message) ? addr->message : NULL;
if (!s && !(s = addr->user_message))
return;
uschar * fname = spool_fname(US"input", message_subdir, id, US"-J");
FILE * jread;
- if ( (journal_fd = Uopen(fname, O_RDWR|O_APPEND
-#ifdef O_CLOEXEC
- | O_CLOEXEC
-#endif
-#ifdef O_NOFOLLOW
- | O_NOFOLLOW
-#endif
- , SPOOL_MODE)) >= 0
+ if ( (journal_fd = Uopen(fname,
+ O_RDWR|O_APPEND | EXIM_CLOEXEC | EXIM_NOFOLLOW, SPOOL_MODE)) >= 0
&& lseek(journal_fd, 0, SEEK_SET) == 0
&& (jread = fdopen(journal_fd, "rb"))
)
return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */
}
- /* Make a C stream out of it. */
+ /* Make a stdio stream out of it. */
if (!(message_log = fdopen(fd, "a")))
{
string_copyn(addr+start, dom ? (dom-1) - start : end - start);
deliver_domain = dom ? CUS string_copyn(addr+dom, end - dom) : CUS"";
- event_raise(event_action, US"msg:fail:internal", new->message);
+ (void) event_raise(event_action, US"msg:fail:internal", new->message, NULL);
deliver_localpart = save_local;
deliver_domain = save_domain;
while (addr_new)
{
int rc;
- uschar *p;
- tree_node *tnode;
- dbdata_retry *domain_retry_record;
- dbdata_retry *address_retry_record;
+ tree_node * tnode;
+ dbdata_retry * domain_retry_record, * address_retry_record;
addr = addr_new;
addr_new = addr->next;
/* Treat /dev/null as a special case and abandon the delivery. This
avoids having to specify a uid on the transport just for this case.
- Arrange for the transport name to be logged as "**bypassed**". */
+ Arrange for the transport name to be logged as "**bypassed**".
+ Copy the transport for this fairly unusual case rather than having
+ to make all transports mutable. */
if (Ustrcmp(addr->address, "/dev/null") == 0)
{
- uschar *save = addr->transport->name;
- addr->transport->name = US"**bypassed**";
+ transport_instance * save_t = addr->transport;
+ transport_instance * t = store_get(sizeof(*t), save_t);
+ *t = *save_t;
+ t->name = US"**bypassed**";
+ addr->transport = t;
(void)post_process_one(addr, OK, LOG_MAIN, EXIM_DTYPE_TRANSPORT, '=');
- addr->transport->name = save;
+ addr->transport= save_t;
continue; /* with the next new address */
}
/* Ensure that the domain in the unique field is lower cased, because
domains are always handled caselessly. */
- p = Ustrrchr(addr->unique, '@');
- while (*p != 0) { *p = tolower(*p); p++; }
+ for (uschar * p = Ustrrchr(addr->unique, '@'); *p; p++) *p = tolower(*p);
DEBUG(D_deliver|D_route) debug_printf("unique = %s\n", addr->unique);
uschar * fname = spool_fname(US"input", message_subdir, id, US"-J");
if ((journal_fd = Uopen(fname,
-#ifdef O_CLOEXEC
- O_CLOEXEC |
-#endif
- O_WRONLY|O_APPEND|O_CREAT|O_EXCL, SPOOL_MODE)) < 0)
+ EXIM_CLOEXEC | O_WRONLY|O_APPEND|O_CREAT|O_EXCL, SPOOL_MODE)) < 0)
{
log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't open journal file %s: %s",
fname, strerror(errno));
if (!regex_IGNOREQUOTA)
regex_IGNOREQUOTA =
- regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE);
+ regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", MCS_NOFLAGS, TRUE);
/* Handle local deliveries */
{
/* copy and relink address_item and send report with all of them at once later */
address_item * addr_next = addr_senddsn;
- addr_senddsn = store_get(sizeof(address_item), FALSE);
+ addr_senddsn = store_get(sizeof(address_item), GET_UNTAINTED);
*addr_senddsn = *a;
addr_senddsn->next = addr_next;
}
f.deliver_freeze = FALSE;
#ifndef DISABLE_EVENT
- (void) event_raise(event_action, US"msg:complete", NULL);
+ (void) event_raise(event_action, US"msg:complete", NULL, NULL);
#endif
}
if (pid == 0) /* child: will fork again to totally disconnect */
{
smtp_proxy_tls(cutthrough.cctx.tls_ctx, big_buffer, big_buffer_size,
- pfd, 5*60);
+ pfd, 5*60, cutthrough.host.name);
/* does not return */
}