* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
/* See the file NOTICE for conditions of use and distribution. */
/* The main code for delivering a message. */
static address_item *addr_remote = NULL;
static address_item *addr_route = NULL;
static address_item *addr_succeed = NULL;
-static address_item *addr_dsntmp = NULL;
static address_item *addr_senddsn = NULL;
static FILE *message_log = NULL;
static ssize_t
readn(int fd, void * buffer, size_t len)
{
- void * next = buffer;
- void * end = buffer + len;
+uschar * next = buffer;
+uschar * end = next + len;
- while (next < end)
- {
- ssize_t got = read(fd, next, end - next);
+while (next < end)
+ {
+ ssize_t got = read(fd, next, end - next);
- /* I'm not sure if there are signals that can interrupt us,
- for now I assume the worst */
- if (got == -1 && errno == EINTR) continue;
- if (got <= 0) return next - buffer;
- next += got;
- }
+ /* I'm not sure if there are signals that can interrupt us,
+ for now I assume the worst */
+ if (got == -1 && errno == EINTR) continue;
+ if (got <= 0) return next - US buffer;
+ next += got;
+ }
- return len;
+return len;
}
address_item *
deliver_make_addr(uschar *address, BOOL copy)
{
-address_item *addr = store_get(sizeof(address_item));
+address_item *addr = store_get(sizeof(address_item), FALSE);
*addr = address_defaults;
if (copy) address = string_copy(address);
addr->address = address;
deliver_address_data = addr->prop.address_data;
deliver_domain_data = addr->prop.domain_data;
deliver_localpart_data = addr->prop.localpart_data;
+router_var = addr->prop.variables;
/* These may be unset for multiple addresses */
deliver_localpart = addr->local_part;
deliver_localpart_prefix = addr->prefix;
+ deliver_localpart_prefix_v = addr->prefix_v;
deliver_localpart_suffix = addr->suffix;
+ deliver_localpart_suffix_v = addr->suffix_v;
for (addr_orig = addr; addr_orig->parent; addr_orig = addr_orig->parent) ;
deliver_domain_orig = addr_orig->domain;
else if (deliver_localpart[0] == '|') address_pipe = addr->local_part;
deliver_localpart = addr->parent->local_part;
deliver_localpart_prefix = addr->parent->prefix;
+ deliver_localpart_prefix_v = addr->parent->prefix_v;
deliver_localpart_suffix = addr->parent->suffix;
+ deliver_localpart_suffix_v = addr->parent->suffix_v;
}
}
else
{
- address_item *addr2;
if (testflag(addr, af_pfr))
{
if (testflag(addr, af_file)) address_file = addr->local_part;
else if (addr->local_part[0] == '|') address_pipe = addr->local_part;
}
- for (addr2 = addr->next; addr2; addr2 = addr2->next)
+ for (address_item * addr2 = addr->next; addr2; addr2 = addr2->next)
{
if (deliver_domain && Ustrcmp(deliver_domain, addr2->domain) != 0)
deliver_domain = NULL;
static int
open_msglog_file(uschar *filename, int mode, uschar **error)
{
-int fd, i;
+if (Ustrstr(filename, US"/../"))
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "Attempt to open msglog file path with upward-traversal: '%s'\n", filename);
-for (i = 2; i > 0; i--)
+for (int i = 2; i > 0; i--)
{
- fd = Uopen(filename,
+ int fd = Uopen(filename,
#ifdef O_CLOEXEC
O_CLOEXEC |
#endif
#ifndef O_CLOEXEC
(void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
#endif
- if (fchown(fd, exim_uid, exim_gid) < 0)
+ if (exim_fchown(fd, exim_uid, exim_gid, filename) < 0)
{
*error = US"chown";
return -1;
MSGLOG_DIRECTORY_MODE, TRUE);
}
-*error = US"create";
+*error = US"create or open";
return -1;
}
static void
replicate_status(address_item *addr)
{
-address_item *addr2;
-for (addr2 = addr->next; addr2; addr2 = addr2->next)
+for (address_item * addr2 = addr->next; addr2; addr2 = addr2->next)
{
addr2->transport = addr->transport;
addr2->transport_return = addr->transport_return;
addr2->basic_errno = addr->basic_errno;
addr2->more_errno = addr->more_errno;
- addr2->delivery_usec = addr->delivery_usec;
+ addr2->delivery_time = addr->delivery_time;
addr2->special_action = addr->special_action;
addr2->message = addr->message;
addr2->user_message = addr->user_message;
This enables Exim to use a single SMTP transaction for sending to two entirely
different domains that happen to end up pointing at the same hosts.
+We do not try to batch up different A-record host names that refer to the
+same IP.
+
Arguments:
one points to the first host list
two points to the second host list
static void
address_done(address_item *addr, uschar *now)
{
-address_item *dup;
-
update_spool = TRUE; /* Ensure spool gets updated */
/* Top-level address */
/* Check the list of duplicate addresses and ensure they are now marked
done as well. */
-for (dup = addr_duplicate; dup; dup = dup->next)
+for (address_item * dup = addr_duplicate; dup; dup = dup->next)
if (Ustrcmp(addr->unique, dup->unique) == 0)
{
tree_add_nonrecipient(dup->unique);
static void
child_done(address_item *addr, uschar *now)
{
-address_item *aa;
while (addr->parent)
{
+ address_item *aa;
+
addr = addr->parent;
if (--addr->child_count > 0) return; /* Incomplete parent */
address_done(addr, now);
if (LOGGING(incoming_interface) && LOGGING(outgoing_interface)
&& sending_ip_address)
{
- g = string_append(g, 2, US" I=[", sending_ip_address);
- g = LOGGING(outgoing_port)
- ? string_append(g, 2, US"]:", string_sprintf("%d", sending_port))
- : string_catn(g, US"]", 1);
+ g = string_fmt_append(g, " I=[%s]", sending_ip_address);
+ if (LOGGING(outgoing_port))
+ g = string_fmt_append(g, ":%d", sending_port);
}
return g;
}
g = string_append(g, 3, US" [", h->address, US"]");
if (LOGGING(outgoing_port))
- g = string_append(g, 2, US":", string_sprintf("%d", h->port));
+ g = string_fmt_append(g, ":%d", h->port);
#ifdef SUPPORT_SOCKS
if (LOGGING(proxy) && proxy_local_address)
{
g = string_append(g, 3, US" PRX=[", proxy_local_address, US"]");
if (LOGGING(outgoing_port))
- g = string_append(g, 2, US":", string_sprintf("%d", proxy_local_port));
+ g = string_fmt_append(g, ":%d", proxy_local_port);
}
#endif
g = d_log_interface(g);
if (testflag(addr, af_tcp_fastopen))
- g = string_catn(g, US" TFO", 4);
+ g = string_catn(g, US" TFO*", testflag(addr, af_tcp_fastopen_data) ? 5 : 4);
return g;
}
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
static gstring *
-d_tlslog(gstring * s, address_item * addr)
+d_tlslog(gstring * g, address_item * addr)
{
if (LOGGING(tls_cipher) && addr->cipher)
- s = string_append(s, 2, US" X=", addr->cipher);
+ {
+ g = string_append(g, 2, US" X=", addr->cipher);
+#ifndef DISABLE_TLS_RESUME
+ if (LOGGING(tls_resumption) && testflag(addr, af_tls_resume))
+ g = string_catn(g, US"*", 1);
+#endif
+ }
if (LOGGING(tls_certificate_verified) && addr->cipher)
- s = string_append(s, 2, US" CV=",
+ g = string_append(g, 2, US" CV=",
testflag(addr, af_cert_verified)
?
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
testflag(addr, af_dane_verified)
? "dane"
:
"yes"
: "no");
if (LOGGING(tls_peerdn) && addr->peerdn)
- s = string_append(s, 3, US" DN=\"", string_printing(addr->peerdn), US"\"");
-return s;
+ g = string_append(g, 3, US" DN=\"", string_printing(addr->peerdn), US"\"");
+return g;
}
#endif
if (!(s = expand_string(action)) && *expand_string_message)
log_write(0, LOG_MAIN|LOG_PANIC,
"failed to expand event_action %s in %s: %s\n",
- event, transport_name, expand_string_message);
+ event, transport_name ? transport_name : US"main", expand_string_message);
event_name = event_data = NULL;
{
DEBUG(D_deliver)
debug_printf("Event(%s): event_action returned \"%s\"\n", event, s);
+ errno = ERRNO_EVENT;
return s;
}
}
const uschar * save_address = deliver_host_address;
const int save_port = deliver_host_port;
-if (!addr->transport)
- return;
-
router_name = addr->router ? addr->router->name : NULL;
-transport_name = addr->transport->name;
deliver_domain = addr->domain;
deliver_localpart = addr->local_part;
deliver_host = addr->host_used ? addr->host_used->name : NULL;
-(void) event_raise(addr->transport->event_action, event,
- addr->host_used
- || Ustrcmp(addr->transport->driver_name, "smtp") == 0
- || Ustrcmp(addr->transport->driver_name, "lmtp") == 0
- ? addr->message : NULL);
+if (!addr->transport)
+ {
+ if (Ustrcmp(event, "msg:fail:delivery") == 0)
+ {
+ /* An address failed with no transport involved. This happens when
+ 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);
+ }
+ }
+else
+ {
+ transport_name = addr->transport->name;
+
+ (void) event_raise(addr->transport->event_action, event,
+ addr->host_used
+ || Ustrcmp(addr->transport->driver_name, "smtp") == 0
+ || Ustrcmp(addr->transport->driver_name, "lmtp") == 0
+ || Ustrcmp(addr->transport->driver_name, "autoreply") == 0
+ ? addr->message : NULL);
+ }
deliver_host_port = save_port;
deliver_host_address = save_address;
else
{
- uschar * cmp = g->s + g->ptr;
+ uschar * cmp;
+ int off = g->ptr; /* start of the "full address" */
if (addr->local_part)
{
of all, do a caseless comparison; if this succeeds, do a caseful comparison
on the local parts. */
+ cmp = g->s + off; /* only now, as rebuffer likely done */
string_from_gstring(g); /* ensure nul-terminated */
if ( strcmpic(cmp, topaddr->address) == 0
&& Ustrncmp(cmp, topaddr->address, Ustrchr(cmp, '@') - cmp) == 0
&& addr->parent != topaddr)
{
uschar *s = US" (";
- address_item *addr2;
- for (addr2 = addr->parent; addr2 != topaddr; addr2 = addr2->parent)
+ for (address_item * addr2 = addr->parent; addr2 != topaddr; addr2 = addr2->parent)
{
g = string_catn(g, s, 2);
g = string_cat (g, addr2->address);
-void
-timesince(struct timeval * diff, struct timeval * then)
-{
-gettimeofday(diff, NULL);
-diff->tv_sec -= then->tv_sec;
-if ((diff->tv_usec -= then->tv_usec) < 0)
- {
- diff->tv_sec--;
- diff->tv_usec += 1000*1000;
- }
-}
-
-
-
-uschar *
-string_timediff(struct timeval * diff)
-{
-static uschar buf[sizeof("0.000s")];
-
-if (diff->tv_sec >= 5 || !LOGGING(millisec))
- return readconf_printtime((int)diff->tv_sec);
-
-sprintf(CS buf, "%d.%03ds", (int)diff->tv_sec, (int)diff->tv_usec/1000);
-return buf;
-}
-
-
-uschar *
-string_timesince(struct timeval * then)
-{
-struct timeval diff;
-
-timesince(&diff, then);
-return string_timediff(&diff);
-}
-
/******************************************************************************/
delivery_log(int flags, address_item * addr, int logchar, uschar * msg)
{
gstring * g; /* Used for a temporary, expanding buffer, for building log lines */
-void * reset_point; /* released afterwards. */
+rmark reset_point;
/* Log the delivery on the main log. We use an extensible string to build up
the log line, and reset the store afterwards. Remote deliveries should always
lookup_dnssec_authenticated = NULL;
#endif
-g = reset_point = string_get(256);
+reset_point = store_mark();
+g = string_get_tainted(256, TRUE); /* 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
+#ifdef EXPERIMENTAL_SRS_ALT
if(addr->prop.srs_sender)
g = string_append(g, 3, US" SRS=<", addr->prop.srs_sender, US">");
#endif
g = string_append(g, 2, US" T=", addr->transport->name);
if (LOGGING(delivery_size))
- g = string_append(g, 2, US" S=",
- string_sprintf("%d", transport_count));
+ g = string_fmt_append(g, " S=%d", transport_count);
/* Local delivery */
if (addr->host_used)
{
g = d_hostlog(g, addr);
- if (continue_sequence > 1)
+ if (continue_sequence > 1) /*XXX this is wrong for a dropped proxyconn. Would have to pass back from transport */
g = string_catn(g, US"*", 1);
#ifndef DISABLE_EVENT
#endif
}
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
g = d_tlslog(g, addr);
#endif
}
}
+ if (LOGGING(pipelining))
+ {
+ if (testflag(addr, af_pipelining))
+ g = string_catn(g, US" L", 2);
+#ifndef DISABLE_PIPE_CONNECT
+ if (testflag(addr, af_early_pipe))
+ g = string_catn(g, US"*", 1);
+#endif
+ }
+
#ifndef DISABLE_PRDR
if (testflag(addr, af_prdr_used))
g = string_catn(g, US" PRDR", 5);
&& (addr->host_used || Ustrcmp(addr->transport->driver_name, "lmtp") == 0)
)
{
- unsigned i;
unsigned lim = big_buffer_size < 1024 ? big_buffer_size : 1024;
uschar *p = big_buffer;
uschar *ss = addr->message;
*p++ = '\"';
- for (i = 0; i < lim && ss[i] != 0; i++) /* limit logged amount */
+ for (int i = 0; i < lim && ss[i] != 0; i++) /* limit logged amount */
{
if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\'; /* quote \ and " */
*p++ = ss[i];
string_timesince(&received_time));
if (LOGGING(deliver_time))
- {
- struct timeval diff = {.tv_sec = addr->more_errno, .tv_usec = addr->delivery_usec};
- g = string_append(g, 2, US" DT=", string_timediff(&diff));
- }
+ g = string_append(g, 2, US" DT=", string_timediff(&addr->delivery_time));
/* string_cat() always leaves room for the terminator. Release the
store we used to build the line after writing it. */
deferral_log(address_item * addr, uschar * now,
int logflags, uschar * driver_name, uschar * driver_kind)
{
-gstring * g;
-void * reset_point;
+rmark reset_point = store_mark();
+gstring * g = string_get(256);
/* Build up the line that is used for both the message log and the main
log. */
-g = reset_point = string_get(256);
-
/* Create the address string for logging. Must not do this earlier, because
an OK result may be changed to FAIL when a pipe returns text. */
{
if (driver_kind[1] == 't' && addr->router)
g = string_append(g, 2, US" R=", addr->router->name);
- g = string_cat(g, string_sprintf(" %c=%s", toupper(driver_kind[1]), driver_name));
+ g = string_fmt_append(g, " %c=%s", toupper(driver_kind[1]), driver_name);
}
else if (driver_kind)
g = string_append(g, 2, US" ", driver_kind);
-/*XXX need an s+s+p sprintf */
-g = string_cat(g, string_sprintf(" defer (%d)", addr->basic_errno));
+g = string_fmt_append(g, " defer (%d)", addr->basic_errno);
if (addr->basic_errno > 0)
g = string_append(g, 2, US": ",
if (LOGGING(outgoing_port))
{
int port = addr->host_used->port;
- g = string_append(g, 2,
- US":", port == PORT_NONE ? US"25" : string_sprintf("%d", port));
+ g = string_fmt_append(g, ":%d", port == PORT_NONE ? 25 : port);
}
}
+if (LOGGING(deliver_time))
+ g = string_append(g, 2, US" DT=", string_timediff(&addr->delivery_time));
+
if (addr->message)
g = string_append(g, 2, US": ", addr->message);
/* Log the deferment in the message log, but don't clutter it
up with retry-time defers after the first delivery attempt. */
-if (deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
+if (f.deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
deliver_msglog("%s %s\n", now, g->s);
/* Write the main log and reset the store.
static void
failure_log(address_item * addr, uschar * driver_kind, uschar * now)
{
-void * reset_point;
-gstring * g = reset_point = string_get(256);
+rmark reset_point = store_mark();
+gstring * g = string_get(256);
+
+#ifndef DISABLE_EVENT
+/* Message failures for which we will send a DSN get their event raised
+later so avoid doing it here. */
+
+if ( !addr->prop.ignore_error
+ && !(addr->dsn_flags & (rf_dsnflags & ~rf_notify_failure))
+ )
+ msg_event_raise(US"msg:fail:delivery", addr);
+#endif
/* Build up the log line for the message and main logs */
if (addr->host_used)
g = d_hostlog(g, addr);
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
g = d_tlslog(g, addr);
#endif
if (addr->message)
g = string_append(g, 2, US": ", addr->message);
+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,
log_write(0, LOG_MAIN, "** %s", g->s);
-#ifndef DISABLE_EVENT
-msg_event_raise(US"msg:fail:delivery", addr);
-#endif
-
store_reset(reset_point);
return;
}
{
driver_name = addr->transport->name;
driver_kind = US" transport";
- disable_logging = addr->transport->disable_logging;
+ f.disable_logging = addr->transport->disable_logging;
}
else driver_kind = US"transporting";
}
{
driver_name = addr->router->name;
driver_kind = US" router";
- disable_logging = addr->router->disable_logging;
+ f.disable_logging = addr->router->disable_logging;
}
else driver_kind = US"routing";
}
log_write(0, LOG_MAIN, "<%s>: %s transport output: %s",
addr->address, tb->name, sp);
}
- (void)fclose(f);
+ (void)fclose(f);
}
/* Handle returning options, but only if there is an address to return
}
/* Certificates for logging (via events) */
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
tls_out.ourcert = addr->ourcert;
addr->ourcert = NULL;
tls_out.peercert = addr->peercert;
addr->peercert = NULL;
+ tls_out.ver = addr->tlsver;
tls_out.cipher = addr->cipher;
tls_out.peerdn = addr->peerdn;
tls_out.ocsp = addr->ocsp;
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
tls_out.dane_verified = testflag(addr, af_dane_verified);
# endif
#endif
delivery_log(LOG_MAIN, addr, logchar, NULL);
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
tls_free_cert(&tls_out.ourcert);
tls_free_cert(&tls_out.peercert);
+ tls_out.ver = NULL;
tls_out.cipher = NULL;
tls_out.peerdn = NULL;
tls_out.ocsp = OCSP_NOT_REQ;
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
tls_out.dane_verified = FALSE;
# endif
#endif
if (addr->special_action == SPECIAL_FREEZE)
{
- deliver_freeze = TRUE;
+ f.deliver_freeze = TRUE;
deliver_frozen_at = time(NULL);
update_spool = TRUE;
}
/* If doing a 2-stage queue run, we skip writing to either the message
log or the main log for SMTP defers. */
- if (!queue_2stage || addr->basic_errno != 0)
+ if (!f.queue_2stage || addr->basic_errno != 0)
deferral_log(addr, now, logflags, driver_name, driver_kind);
}
{
frozen_info = addr->special_action == SPECIAL_FREEZE
? US""
- : sender_local && !local_error_message
+ : f.sender_local && !f.local_error_message
? US" (message created with -f <>)"
: US" (delivery error message)";
- deliver_freeze = TRUE;
+ f.deliver_freeze = TRUE;
deliver_frozen_at = time(NULL);
update_spool = TRUE;
/* Ensure logging is turned on again in all cases */
-disable_logging = FALSE;
+f.disable_logging = FALSE;
}
static void
common_error(BOOL logit, address_item *addr, int code, uschar *format, ...)
{
-address_item *addr2;
addr->basic_errno = code;
if (format)
{
va_list ap;
- uschar buffer[512];
+ gstring * g;
+
va_start(ap, format);
- if (!string_vformat(buffer, sizeof(buffer), CS format, ap))
- log_write(0, LOG_MAIN|LOG_PANIC_DIE,
- "common_error expansion was longer than " SIZE_T_FMT, sizeof(buffer));
+ g = string_vformat(NULL, SVFMT_EXTEND|SVFMT_REBUFFER, CS format, ap);
va_end(ap);
- addr->message = string_copy(buffer);
+ addr->message = string_from_gstring(g);
}
-for (addr2 = addr->next; addr2; addr2 = addr2->next)
+for (address_item * addr2 = addr->next; addr2; addr2 = addr2->next)
{
addr2->basic_errno = code;
addr2->message = addr->message;
static BOOL
check_never_users(uid_t uid, uid_t *nusers)
{
-int i;
if (!nusers) return FALSE;
-for (i = 1; i <= (int)(nusers[0]); i++) if (nusers[i] == uid) return TRUE;
+for (int i = 1; i <= (int)(nusers[0]); i++) if (nusers[i] == uid) return TRUE;
return FALSE;
}
static BOOL
previously_transported(address_item *addr, BOOL testing)
{
-(void)string_format(big_buffer, big_buffer_size, "%s/%s",
+uschar * s = string_sprintf("%s/%s",
addr->unique + (testflag(addr, af_homonym)? 3:0), addr->transport->name);
-if (tree_search(tree_nonrecipients, big_buffer) != 0)
+if (tree_search(tree_nonrecipients, s) != 0)
{
DEBUG(D_deliver|D_route|D_transport)
debug_printf("%s was previously delivered (%s transport): discarded\n",
Returns: nothing
*/
-static void
+void
deliver_local(address_item *addr, BOOL shadowing)
{
BOOL use_initgroups;
if(addr->prop.errors_address)
return_path = addr->prop.errors_address;
-#ifdef EXPERIMENTAL_SRS
+#ifdef EXPERIMENTAL_SRS_ALT
else if (addr->prop.srs_sender)
return_path = addr->prop.srs_sender;
#endif
if (tp->return_path)
{
- uschar *new_return_path = expand_string(tp->return_path);
- if (!new_return_path)
- {
- if (!expand_string_forcedfail)
- {
- common_error(TRUE, addr, ERRNO_EXPANDFAIL,
- US"Failed to expand return path \"%s\" in %s transport: %s",
- tp->return_path, tp->name, expand_string_message);
- return;
- }
+ uschar * new_return_path = expand_string(tp->return_path);
+ if (new_return_path)
+ return_path = new_return_path;
+ else if (!f.expand_string_forcedfail)
+ {
+ common_error(TRUE, addr, ERRNO_EXPANDFAIL,
+ US"Failed to expand return path \"%s\" in %s transport: %s",
+ tp->return_path, tp->name, expand_string_message);
+ return;
}
- else return_path = new_return_path;
}
/* For local deliveries, one at a time, the value used for logging can just be
search_tidyup();
-if ((pid = fork()) == 0)
+if ((pid = exim_fork(US"delivery-local")) == 0)
{
BOOL replicate = TRUE;
DEBUG(D_deliver)
{
- address_item *batched;
debug_printf(" home=%s current=%s\n", deliver_home, working_directory);
- for (batched = addr->next; batched; batched = batched->next)
+ for (address_item * batched = addr->next; batched; batched = batched->next)
debug_printf("additional batched address: %s\n", batched->address);
}
|| (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_usec, 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)
|| (ret = write(pfd[pipe_write], &addr2->transport,
sizeof(transport_instance *))) != sizeof(transport_instance *)
len = read(pfd[pipe_read], &addr2->flags, sizeof(addr2->flags));
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_usec, 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], &addr2->transport,
sizeof(transport_instance *));
/* In the test harness, wait just a bit to let the subprocess finish off
any debug output etc first. */
- if (running_in_test_harness) millisleep(300);
+ testharness_pause_ms(300);
DEBUG(D_deliver) debug_printf("journalling %s", big_buffer);
len = Ustrlen(big_buffer);
"message for %s transport): %s", addr->transport->warn_message,
addr->transport->name, expand_string_message);
- else if ((pid = child_open_exim(&fd)) > 0)
+ else if ((pid = child_open_exim(&fd, US"tpt-warning-message")) > 0)
{
FILE *f = fdopen(fd, "wb");
if (errors_reply_to && !contains_header(US"Reply-To", warn_message))
struct timeval deliver_time;
address_item *addr2, *addr3, *nextaddr;
int logflags = LOG_MAIN;
- int logchar = dont_deliver? '*' : '=';
+ int logchar = f.dont_deliver? '*' : '=';
transport_instance *tp;
uschar * serialize_key = NULL;
if (!(tp = addr->transport))
{
logflags |= LOG_PANIC;
- disable_logging = FALSE; /* Jic */
+ f.disable_logging = FALSE; /* Jic */
addr->message = addr->router
? string_sprintf("No transport set by %s router", addr->router->name)
- : string_sprintf("No transport set by system filter");
+ : US"No transport set by system filter";
post_process_one(addr, DEFER, logflags, EXIM_DTYPE_TRANSPORT, 0);
continue;
}
/* There are weird cases where logging is disabled */
- disable_logging = tp->disable_logging;
+ f.disable_logging = tp->disable_logging;
/* Check for batched addresses and possible amalgamation. Skip all the work
if either batch_max <= 1 or there aren't any other addresses for local
of these checks, rather than for all local deliveries, because some local
deliveries (e.g. to pipes) can take a substantial time. */
- if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE)))
+ if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE)))
{
DEBUG(D_deliver|D_retry|D_hints_lookup)
debug_printf("no retry data available\n");
retry_record->expired);
}
- if (queue_running && !deliver_force)
+ if (f.queue_running && !f.deliver_force)
{
ok = (now - retry_record->time_stamp > retry_data_expire)
|| (now >= retry_record->next_try)
else for (addr2 = addr; addr2; addr2 = addr2->next)
if (addr2->transport_return == OK)
{
- addr3 = store_get(sizeof(address_item));
+ addr3 = store_get(sizeof(address_item), FALSE);
*addr3 = *addr2;
addr3->next = NULL;
addr3->shadow_message = US &addr2->shadow_message;
DEBUG(D_deliver|D_transport)
debug_printf("%s shadow transport returned %s for %s\n",
- stp->name,
- sresult == OK ? "OK" :
- sresult == DEFER ? "DEFER" :
- sresult == FAIL ? "FAIL" :
- sresult == PANIC ? "PANIC" : "?",
- shadow_addr->address);
+ stp->name, rc_to_string(sresult), shadow_addr->address);
}
DEBUG(D_deliver|D_transport)
DEBUG(D_deliver|D_transport)
debug_printf("%s transport returned %s for %s\n",
- tp->name,
- result == OK ? "OK" :
- result == DEFER ? "DEFER" :
- result == FAIL ? "FAIL" :
- result == PANIC ? "PANIC" : "?",
- addr2->address);
+ tp->name, rc_to_string(result), addr2->address);
/* If there is a retry_record, or if delivery is deferred, build a retry
item for setting a new retry time or deleting the old retry record from
/* Done with this address */
- if (result == OK)
- {
- addr2->more_errno = deliver_time.tv_sec;
- addr2->delivery_usec = deliver_time.tv_usec;
- }
+ addr2->delivery_time = deliver_time;
post_process_one(addr2, result, logflags, EXIM_DTYPE_TRANSPORT, logchar);
/* If a pipe delivery generated text to be sent back, the result may be
DEBUG(D_deliver)
{
- address_item *addr;
debug_printf("remote addresses after sorting:\n");
- for (addr = addr_remote; addr; addr = addr->next)
+ for (address_item * addr = addr_remote; addr; addr = addr->next)
debug_printf(" %s\n", addr->address);
}
}
often bigger) so even if we are reading while the subprocess is still going, we
should never have only a partial item in the buffer.
-hs12: This assumption is not true anymore, since we got quit large items (certificate
-information and such)
+hs12: This assumption is not true anymore, since we get quite large items (certificate
+information and such).
Argument:
poffset the offset of the parlist item
if (!r || !(*ptr & rf_delete))
{
- r = store_get(sizeof(retry_item));
+ r = store_get(sizeof(retry_item), FALSE);
r->next = addr->retries;
addr->retries = r;
r->flags = *ptr++;
it in with the other info, in order to keep each message short enough to
guarantee it won't be split in the pipe. */
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
case 'X':
if (!addr) goto ADDR_MISMATCH; /* Below, in 'A' handler */
switch (*subid)
{
case '1':
- addr->cipher = NULL;
- addr->peerdn = NULL;
+ addr->tlsver = addr->cipher = addr->peerdn = NULL;
if (*ptr)
+ {
addr->cipher = string_copy(ptr);
+ addr->tlsver = string_copyn(ptr, Ustrchr(ptr, ':') - ptr);
+ }
while (*ptr++);
if (*ptr)
addr->peerdn = string_copy(ptr);
}
while (*ptr++);
break;
-#endif /*SUPPORT_TLS*/
+#endif /*DISABLE_TLS*/
case 'C': /* client authenticator information */
switch (*subid)
break;
#endif
+ case 'L':
+ switch (*subid)
+ {
+#ifndef DISABLE_PIPE_CONNECT
+ case 2: setflag(addr, af_early_pipe); /*FALLTHROUGH*/
+#endif
+ case 1: setflag(addr, af_pipelining); break;
+ }
+ break;
+
case 'K':
setflag(addr, af_chunking_used);
break;
case 'T':
setflag(addr, af_tcp_fastopen_conn);
if (*subid > '0') setflag(addr, af_tcp_fastopen);
+ if (*subid > '1') setflag(addr, af_tcp_fastopen_data);
break;
case 'D':
ptr += sizeof(addr->basic_errno);
memcpy(&addr->more_errno, ptr, sizeof(addr->more_errno));
ptr += sizeof(addr->more_errno);
- memcpy(&addr->delivery_usec, ptr, sizeof(addr->delivery_usec));
- ptr += sizeof(addr->delivery_usec);
+ memcpy(&addr->delivery_time, ptr, sizeof(addr->delivery_time));
+ ptr += sizeof(addr->delivery_time);
memcpy(&addr->flags, ptr, sizeof(addr->flags));
ptr += sizeof(addr->flags);
addr->message = *ptr ? string_copy(ptr) : NULL;
if (*ptr)
{
- h = store_get(sizeof(host_item));
+ h = store_get(sizeof(host_item), FALSE);
h->name = string_copy(ptr);
while (*ptr++);
h->address = string_copy(ptr);
remote_post_process(address_item *addr, int logflags, uschar *msg,
BOOL fallback)
{
-host_item *h;
-
/* If any host addresses were found to be unusable, add them to the unusable
tree so that subsequent deliveries don't try them. */
-for (h = addr->host_list; h; h = h->next)
+for (host_item * h = addr->host_list; h; h = h->next)
if (h->address)
if (h->status >= hstatus_unusable) tree_add_unusable(h);
do_remote_deliveries(BOOL fallback)
{
int parmax;
-int delivery_count;
int poffset;
parcount = 0; /* Number of executing subprocesses */
if (!parlist)
{
- parlist = store_get(remote_max_parallel * sizeof(pardata));
+ parlist = store_get(remote_max_parallel * sizeof(pardata), FALSE);
for (poffset = 0; poffset < remote_max_parallel; poffset++)
parlist[poffset].pid = 0;
}
/* Now loop for each remote delivery */
-for (delivery_count = 0; addr_remote; delivery_count++)
+for (int delivery_count = 0; addr_remote; delivery_count++)
{
pid_t pid;
uid_t uid;
if (!(tp = addr->transport))
{
- disable_logging = FALSE; /* Jic */
+ f.disable_logging = FALSE; /* Jic */
panicmsg = US"No transport set by router";
goto panic_continue;
}
}
}
+/*XXX need to defeat this when DANE is used - but we don't know that yet.
+So look out for the place it gets used.
+*/
+
/* Get the flag which specifies whether the transport can handle different
domains that nevertheless resolve to the same set of hosts. If it needs
expanding, get variables set: $address_data, $domain_data, $localpart_data,
/************************************************************************/
+/*XXX don't know yet if DANE will be used. So tpt will have to
+check at the point if gets next addr from list, and skip/defer any
+nonmatch domains
+*/
+
/* Pick off all addresses which have the same transport, errors address,
destination, and extra headers. In some cases they point to the same host
list, but we also need to check for identical host lists generated from
if(addr->prop.errors_address)
return_path = addr->prop.errors_address;
-#ifdef EXPERIMENTAL_SRS
+#ifdef EXPERIMENTAL_SRS_ALT
else if(addr->prop.srs_sender)
return_path = addr->prop.srs_sender;
#endif
uschar *new_return_path = expand_string(tp->return_path);
if (new_return_path)
return_path = new_return_path;
- else if (!expand_string_forcedfail)
+ else if (!f.expand_string_forcedfail)
{
panicmsg = string_sprintf("Failed to expand return path \"%s\": %s",
tp->return_path, expand_string_message);
treat it as if it is a continued connection (apart from the counter used
for the log line mark). */
- if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
+ if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
{
DEBUG(D_deliver)
debug_printf("lazy-callout-close: have conn still open from verification\n");
we must check that the continue host is on the list. Otherwise, the
host is set in the transport. */
- continue_more = FALSE; /* In case got set for the last lot */
+ f.continue_more = FALSE; /* In case got set for the last lot */
if (continue_transport)
{
BOOL ok = Ustrcmp(continue_transport, tp->name) == 0;
+/*XXX do we need to check for a DANEd conn vs. a change of domain? */
/* If the transport is about to override the host list do not check
it here but take the cost of running the transport process to discover
&& addr->host_list
)
{
- host_item * h;
ok = FALSE;
- for (h = addr->host_list; h; h = h->next)
+ for (host_item * h = addr->host_list; h; h = h->next)
if (Ustrcmp(h->name, continue_hostname) == 0)
/*XXX should also check port here */
{ ok = TRUE; break; }
connected to is too hard to manage. Perhaps we need a finer-grain
interface to the transport. */
- for (next = addr_remote; next && !continue_more; next = next->next)
- {
- host_item *h;
- for (h = next->host_list; h; h = h->next)
+ for (next = addr_remote; next && !f.continue_more; next = next->next)
+ for (host_item * h = next->host_list; h; h = h->next)
if (Ustrcmp(h->name, continue_hostname) == 0)
- { continue_more = TRUE; break; }
- }
+ { f.continue_more = TRUE; break; }
}
/* The transports set up the process info themselves as they may connect
search_tidyup();
-
- if ((pid = fork()) == 0)
+ if ((pid = exim_fork(US"transport")) == 0)
{
int fd = pfd[pipe_write];
host_item *h;
- DEBUG(D_deliver) debug_selector |= D_pid; // hs12
/* Setting this global in the subprocess means we need never clear it */
transport_name = tp->name;
/* There are weird circumstances in which logging is disabled */
- disable_logging = tp->disable_logging;
+ f.disable_logging = tp->disable_logging;
/* Show pids on debug output if parallelism possible */
if (parmax > 1 && (parcount > 0 || addr_remote))
- {
DEBUG(D_any|D_v) debug_selector |= D_pid;
- DEBUG(D_deliver) debug_printf("Remote delivery process started\n");
- }
/* Reset the random number generator, so different processes don't all
have the same sequence. In the test harness we want different, but
predictable settings for each delivery process, so do something explicit
here rather they rely on the fixed reset in the random number function. */
- random_seed = running_in_test_harness ? 42 + 2*delivery_count : 0;
+ random_seed = f.running_in_test_harness ? 42 + 2*delivery_count : 0;
/* Set close-on-exec on the pipe so that it doesn't get passed on to
a new process that may be forked to do another delivery down the same
for(; addr; addr = addr->next)
{
uschar *ptr;
- retry_item *r;
/* The certificate verification status goes into the flags */
if (tls_out.certificate_verified) setflag(addr, af_cert_verified);
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
if (tls_out.dane_verified) setflag(addr, af_dane_verified);
#endif
+# ifndef DISABLE_TLS_RESUME
+ if (tls_out.resumption & RESUME_USED) setflag(addr, af_tls_resume);
+# endif
/* Use an X item only if there's something to send */
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
if (addr->cipher)
{
ptr = big_buffer + sprintf(CS big_buffer, "%.128s", addr->cipher) + 1;
if (addr->peercert)
{
ptr = big_buffer;
- if (!tls_export_cert(ptr, big_buffer_size-2, addr->peercert))
+ if (tls_export_cert(ptr, big_buffer_size-2, addr->peercert))
while(*ptr++);
else
*ptr++ = 0;
if (addr->ourcert)
{
ptr = big_buffer;
- if (!tls_export_cert(ptr, big_buffer_size-2, addr->ourcert))
+ if (tls_export_cert(ptr, big_buffer_size-2, addr->ourcert))
while(*ptr++);
else
*ptr++ = 0;
rmt_dlv_checked_write(fd, 'X', '4', big_buffer, ptr - big_buffer);
}
# endif
-#endif /*SUPPORT_TLS*/
+#endif /*DISABLE_TLS*/
if (client_authenticator)
{
rmt_dlv_checked_write(fd, 'P', '0', NULL, 0);
#endif
+ if (testflag(addr, af_pipelining))
+#ifndef DISABLE_PIPE_CONNECT
+ if (testflag(addr, af_early_pipe))
+ rmt_dlv_checked_write(fd, 'L', '2', NULL, 0);
+ else
+#endif
+ rmt_dlv_checked_write(fd, 'L', '1', NULL, 0);
+
if (testflag(addr, af_chunking_used))
rmt_dlv_checked_write(fd, 'K', '0', NULL, 0);
if (testflag(addr, af_tcp_fastopen_conn))
rmt_dlv_checked_write(fd, 'T',
- testflag(addr, af_tcp_fastopen) ? '1' : '0', NULL, 0);
+ testflag(addr, af_tcp_fastopen) ? testflag(addr, af_tcp_fastopen_data)
+ ? '2' : '1' : '0',
+ NULL, 0);
memcpy(big_buffer, &addr->dsn_aware, sizeof(addr->dsn_aware));
rmt_dlv_checked_write(fd, 'D', '0', big_buffer, sizeof(addr->dsn_aware));
/* Retry information: for most success cases this will be null. */
- for (r = addr->retries; r; r = r->next)
+ for (retry_item * r = addr->retries; r; r = r->next)
{
sprintf(CS big_buffer, "%c%.500s", r->flags, r->key);
ptr = big_buffer + Ustrlen(big_buffer+2) + 3;
ptr += sizeof(addr->basic_errno);
memcpy(ptr, &addr->more_errno, sizeof(addr->more_errno));
ptr += sizeof(addr->more_errno);
- memcpy(ptr, &addr->delivery_usec, sizeof(addr->delivery_usec));
- ptr += sizeof(addr->delivery_usec);
+ memcpy(ptr, &addr->delivery_time, sizeof(addr->delivery_time));
+ ptr += sizeof(addr->delivery_time);
memcpy(ptr, &addr->flags, sizeof(addr->flags));
ptr += sizeof(addr->flags);
release its TLS library context (if any) as responsibility was passed to
the delivery child process. */
- if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
+ if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
{
-#ifdef SUPPORT_TLS
- tls_close(FALSE, FALSE);
+#ifndef DISABLE_TLS
+ if (cutthrough.is_tls)
+ tls_close(cutthrough.cctx.tls_ctx, TLS_NO_SHUTDOWN);
#endif
- (void) close(cutthrough.fd);
+ (void) close(cutthrough.cctx.sock);
release_cutthrough_connection(US"passed to transport proc");
}
/* Otherwise, if we are running in the test harness, wait a bit, to let the
newly created process get going before we create another process. This should
- ensure repeatability in the tests. We only need to wait a tad. */
+ ensure repeatability in the tests. Wait long enough for most cases to complete
+ the transport. */
- else if (running_in_test_harness) millisleep(500);
+ else testharness_pause_ms(600);
continue;
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);
+t = addr->cc_local_part = store_get(len+1, is_tainted(address));
while(len-- > 0)
{
int c = *address++;
if (new_address)
{
- address_item *new_parent = store_get(sizeof(address_item));
+ address_item *new_parent = store_get(sizeof(address_item), FALSE);
*new_parent = *addr;
addr->parent = new_parent;
new_parent->child_count = 1;
continue_closedown(void)
{
if (continue_transport)
- {
- transport_instance *t;
- for (t = transports; t; t = t->next)
+ for (transport_instance * t = transports; t; t = t->next)
if (Ustrcmp(t->name, continue_transport) == 0)
{
if (t->info->closedown) (t->info->closedown)(t);
break;
}
- }
return DELIVER_NOT_ATTEMPTED;
}
print_address_error(address_item *addr, FILE *f, 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;
static void
print_dsn_diagnostic_code(const address_item *addr, FILE *f)
{
-uschar *s = testflag(addr, af_pass_message) ? addr->message : NULL;
+uschar * s = testflag(addr, af_pass_message) ? addr->message : NULL;
+unsigned cnt;
/* af_pass_message and addr->message set ? print remote host answer */
if (s)
if (!(s = Ustrstr(addr->message, ": ")))
return; /* not found, bail out */
s += 2; /* skip ": " */
- fprintf(f, "Diagnostic-Code: smtp; ");
+ cnt = fprintf(f, "Diagnostic-Code: smtp; ");
}
/* no message available. do nothing */
else return;
while (*s)
+ {
+ if (cnt > 950) /* RFC line length limit: 998 */
+ {
+ DEBUG(D_deliver) debug_printf("print_dsn_diagnostic_code() truncated line\n");
+ fputs("[truncated]", f);
+ break;
+ }
+
if (*s == '\\' && s[1] == 'n')
{
fputs("\n ", f); /* as defined in RFC 3461 */
s += 2;
+ cnt += 2;
}
else
+ {
fputc(*s++, f);
+ cnt++;
+ }
+ }
fputc('\n', f);
}
+/************************************************/
+
+static void
+print_dsn_addr_action(FILE * f, address_item * addr,
+ uschar * action, uschar * status)
+{
+address_item * pa;
+
+if (addr->dsn_orcpt)
+ fprintf(f,"Original-Recipient: %s\n", addr->dsn_orcpt);
+
+for (pa = addr; pa->parent; ) pa = pa->parent;
+fprintf(f, "Action: %s\n"
+ "Final-Recipient: rfc822;%s\n"
+ "Status: %s\n",
+ action, pa->address, status);
+}
+
+
+
+/* When running in the test harness, there's an option that allows us to
+fudge this time so as to get repeatability of the tests. Take the first
+time off the list. In queue runs, the list pointer gets updated in the
+calling process. */
+
+int
+test_harness_fudged_queue_time(int actual_time)
+{
+int qt;
+if ( f.running_in_test_harness && *fudged_queue_times
+ && (qt = readconf_readtime(fudged_queue_times, '/', FALSE)) >= 0)
+ {
+ DEBUG(D_deliver) debug_printf("fudged queue_times = %s\n",
+ fudged_queue_times);
+ return qt;
+ }
+return actual_time;
+}
+
+/************************************************/
+
+static FILE *
+expand_open(const uschar * filename,
+ const uschar * varname, const uschar * reason)
+{
+const uschar * s = expand_cstring(filename);
+FILE * fp = NULL;
+
+if (!s || !*s)
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "Failed to expand %s: '%s'\n", varname, filename);
+else if (*s != '/' || is_tainted(s))
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "%s is not %s after expansion: '%s'\n",
+ varname, *s == '/' ? "untainted" : "absolute", s);
+else if (!(fp = Ufopen(s, "rb")))
+ log_write(0, LOG_MAIN|LOG_PANIC, "Failed to open %s for %s "
+ "message texts: %s", s, reason, strerror(errno));
+return fp;
+}
+
/*************************************************
* Deliver one message *
*************************************************/
open_db dbblock;
open_db *dbm_file;
extern int acl_where;
+uschar *info;
-uschar *info = queue_run_pid == (pid_t)0
+#ifdef MEASURE_TIMING
+report_time_since(×tamp_startup, US"delivery start"); /* testcase 0022, 2100 */
+#endif
+
+info = queue_run_pid == (pid_t)0
? string_sprintf("delivering %s", id)
: string_sprintf("delivering %s (queue run pid %d)", id, queue_run_pid);
it is obtained from a command line (the -M or -q options), and otherwise it is
known to be a valid message id. */
-Ustrcpy(message_id, id);
-deliver_force = forced;
+if (id != message_id)
+ Ustrcpy(message_id, id);
+f.deliver_force = forced;
return_count = 0;
message_size = 0;
can happen, but in the default situation, unless forced, no delivery is
attempted. */
-if (deliver_freeze)
+if (f.deliver_freeze)
{
#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
/* Moving to another directory removes the message from Exim's view. Other
|| auto_thaw <= 0
|| now <= deliver_frozen_at + auto_thaw
)
- && ( !forced || !deliver_force_thaw
- || !admin_user || continue_hostname
+ && ( !forced || !f.deliver_force_thaw
+ || !f.admin_user || continue_hostname
) )
{
(void)close(deliver_datafile);
if (forced)
{
- deliver_manual_thaw = TRUE;
+ f.deliver_manual_thaw = TRUE;
log_write(0, LOG_MAIN, "Unfrozen by forced delivery");
}
else log_write(0, LOG_MAIN, "Unfrozen by auto-thaw");
/* We get here if any of the rules for unfreezing have triggered. */
- deliver_freeze = FALSE;
+ f.deliver_freeze = FALSE;
update_spool = TRUE;
}
ugid.uid_set = ugid.gid_set = TRUE;
}
else
- {
ugid.uid_set = ugid.gid_set = FALSE;
- }
return_path = sender_address;
- enable_dollar_recipients = TRUE; /* Permit $recipients in system filter */
- system_filtering = TRUE;
+ f.enable_dollar_recipients = TRUE; /* Permit $recipients in system filter */
+ f.system_filtering = TRUE;
/* Any error in the filter file causes a delivery to be abandoned. */
/* Reset things. If the filter message is an empty string, which can happen
for a filter "fail" or "freeze" command with no text, reset it to NULL. */
- system_filtering = FALSE;
- enable_dollar_recipients = FALSE;
+ f.system_filtering = FALSE;
+ f.enable_dollar_recipients = FALSE;
if (filter_message && filter_message[0] == 0) filter_message = NULL;
/* Save the values of the system filter variables so that user filters
unset "delivered", which is forced by the "freeze" command to make -bF
work properly. */
- else if (rc == FF_FREEZE && !deliver_manual_thaw)
+ else if (rc == FF_FREEZE && !f.deliver_manual_thaw)
{
- deliver_freeze = TRUE;
+ f.deliver_freeze = TRUE;
deliver_frozen_at = time(NULL);
process_recipients = RECIP_DEFER;
frozen_info = string_sprintf(" by the system filter%s%s",
if (addr_new)
{
- int uid = (system_filter_uid_set)? system_filter_uid : geteuid();
- int gid = (system_filter_gid_set)? system_filter_gid : getegid();
+ int uid = system_filter_uid_set ? system_filter_uid : geteuid();
+ int gid = system_filter_gid_set ? system_filter_gid : getegid();
/* The text "system-filter" is tested in transport_set_up_command() and in
set_up_shell_command() in the pipe transport, to enable them to permit
if (!tmp)
p->message = string_sprintf("failed to expand \"%s\" as a "
"system filter transport name", tpname);
+ if (is_tainted(tmp))
+ p->message = string_sprintf("attempt to used tainted value '%s' for"
+ "transport '%s' as a system filter", tmp, tpname);
tpname = tmp;
}
else
new->onetime_parent = recipients_list[r->pno].address;
/* If DSN support is enabled, set the dsn flags and the original receipt
- to be passed on to other DSN enabled MTAs */
+ to be passed on to other DSN enabled MTAs */
+
new->dsn_flags = r->dsn_flags & rf_dsnflags;
new->dsn_orcpt = r->orcpt;
- DEBUG(D_deliver) debug_printf("DSN: set orcpt: %s flags: %d\n",
- new->dsn_orcpt, new->dsn_flags);
+ DEBUG(D_deliver) debug_printf("DSN: set orcpt: %s flags: 0x%x\n",
+ new->dsn_orcpt ? new->dsn_orcpt : US"", new->dsn_flags);
switch (process_recipients)
{
/* RECIP_DEFER is set when a system filter freezes a message. */
case RECIP_DEFER:
- new->next = addr_defer;
- addr_defer = new;
- break;
+ new->next = addr_defer;
+ addr_defer = new;
+ break;
/* RECIP_FAIL_FILTER is set when a system filter has obeyed a "fail"
command. */
case RECIP_FAIL_FILTER:
- new->message =
- filter_message ? filter_message : US"delivery cancelled";
- setflag(new, af_pass_message);
- goto RECIP_QUEUE_FAILED; /* below */
+ new->message =
+ filter_message ? filter_message : US"delivery cancelled";
+ setflag(new, af_pass_message);
+ goto RECIP_QUEUE_FAILED; /* below */
/* RECIP_FAIL_TIMEOUT is set when a message is frozen, but is older
been logged. */
case RECIP_FAIL_TIMEOUT:
- new->message = US"delivery cancelled; message timed out";
- goto RECIP_QUEUE_FAILED; /* below */
+ new->message = US"delivery cancelled; message timed out";
+ goto RECIP_QUEUE_FAILED; /* below */
/* RECIP_FAIL is set when -Mg has been used. */
case RECIP_FAIL:
- new->message = US"delivery cancelled by administrator";
- /* Fall through */
+ new->message = US"delivery cancelled by administrator";
+ /* Fall through */
/* Common code for the failure cases above. If this is not a bounce
message, put the address on the failed list so that it is used to
The incident has already been logged. */
RECIP_QUEUE_FAILED:
- if (sender_address[0] != 0)
- {
- new->next = addr_failed;
- addr_failed = new;
- }
+ if (sender_address[0])
+ {
+ new->next = addr_failed;
+ addr_failed = new;
+ }
break;
is a bounce message, it will get frozen. */
case RECIP_FAIL_LOOP:
- new->message = US"Too many \"Received\" headers - suspected mail loop";
- post_process_one(new, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
- break;
+ new->message = US"Too many \"Received\" headers - suspected mail loop";
+ post_process_one(new, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+ break;
/* Value should be RECIP_ACCEPT; take this as the safe default. */
default:
- if (!addr_new) addr_new = new; else addr_last->next = new;
- addr_last = new;
- break;
+ if (!addr_new) addr_new = new; else addr_last->next = new;
+ addr_last = new;
+ break;
}
#ifndef DISABLE_EVENT
- if (process_recipients != RECIP_ACCEPT)
+ if (process_recipients != RECIP_ACCEPT && event_action)
{
uschar * save_local = deliver_localpart;
const uschar * save_domain = deliver_domain;
+ uschar * addr = new->address, * errmsg = NULL;
+ int start, end, dom;
- deliver_localpart = expand_string(
- string_sprintf("${local_part:%s}", new->address));
- deliver_domain = expand_string(
- string_sprintf("${domain:%s}", new->address));
+ if (!parse_extract_address(addr, &errmsg, &start, &end, &dom, TRUE))
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "failed to parse address '%.100s': %s\n", addr, errmsg);
+ else
+ {
+ deliver_localpart =
+ string_copyn(addr+start, dom ? (dom-1) - start : end - start);
+ deliver_domain = dom ? CUS string_copyn(addr+dom, end - dom) : CUS"";
- (void) event_raise(event_action,
- US"msg:fail:internal", new->message);
+ event_raise(event_action, US"msg:fail:internal", new->message);
- deliver_localpart = save_local;
- deliver_domain = save_domain;
+ deliver_localpart = save_local;
+ deliver_domain = save_domain;
+ }
}
#endif
}
DEBUG(D_deliver)
{
- address_item *p;
debug_printf("Delivery address list:\n");
- for (p = addr_new; p; p = p->next)
+ for (address_item * p = addr_new; p; p = p->next)
debug_printf(" %s %s\n", p->address,
p->onetime_parent ? p->onetime_parent : US"");
}
. If new addresses have been generated by the routers, da capo.
*/
-header_rewritten = FALSE; /* No headers rewritten yet */
+f.header_rewritten = FALSE; /* No headers rewritten yet */
while (addr_new) /* Loop until all addresses dealt with */
{
address_item *addr, *parent;
/* Failure to open the retry database is treated the same as if it does
not exist. In both cases, dbm_file is NULL. */
- if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE)))
+ if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE)))
DEBUG(D_deliver|D_retry|D_route|D_hints_lookup)
debug_printf("no retry data available\n");
keep piling '>' characters on the front. */
if (addr->address[0] == '>')
- {
while (tree_search(tree_duplicates, addr->unique))
addr->unique = string_sprintf(">%s", addr->unique);
- }
else if ((tnode = tree_search(tree_duplicates, addr->unique)))
{
if ( domain_retry_record
&& now - domain_retry_record->time_stamp > retry_data_expire
)
+ {
+ DEBUG(D_deliver|D_retry)
+ debug_printf("domain retry record present but expired\n");
domain_retry_record = NULL; /* Ignore if too old */
+ }
address_retry_record = dbfn_read(dbm_file, addr->address_retry_key);
if ( address_retry_record
&& now - address_retry_record->time_stamp > retry_data_expire
)
+ {
+ DEBUG(D_deliver|D_retry)
+ debug_printf("address retry record present but expired\n");
address_retry_record = NULL; /* Ignore if too old */
+ }
if (!address_retry_record)
{
address_retry_record = dbfn_read(dbm_file, altkey);
if ( address_retry_record
&& now - address_retry_record->time_stamp > retry_data_expire)
+ {
+ DEBUG(D_deliver|D_retry)
+ debug_printf("address<sender> retry record present but expired\n");
address_retry_record = NULL; /* Ignore if too old */
+ }
}
}
else
DEBUG(D_deliver|D_retry)
{
if (!domain_retry_record)
- debug_printf("no domain retry record\n");
+ debug_printf("no domain retry record\n");
+ else
+ debug_printf("have domain retry record; next_try = now%+d\n",
+ f.running_in_test_harness ? 0 :
+ (int)(domain_retry_record->next_try - now));
+
if (!address_retry_record)
- debug_printf("no address retry record\n");
+ debug_printf("no address retry record\n");
+ else
+ debug_printf("have address retry record; next_try = now%+d\n",
+ f.running_in_test_harness ? 0 :
+ (int)(address_retry_record->next_try - now));
}
/* If we are sending a message down an existing SMTP connection, we must
addr->message = US"reusing SMTP connection skips previous routing defer";
addr->basic_errno = ERRNO_RRETRY;
(void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+
+ addr->message = domain_retry_record->text;
+ setflag(addr, af_pass_message);
}
/* If we are in a queue run, defer routing unless there is no retry data or
which keep the retry record fresh, which can lead to us perpetually
deferring messages. */
- else if ( ( queue_running && !deliver_force
+ else if ( ( f.queue_running && !f.deliver_force
|| continue_hostname
)
&& ( ( domain_retry_record
addr->message = US"retry time not reached";
addr->basic_errno = ERRNO_RRETRY;
(void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+
+ /* For remote-retry errors (here and just above) that we've not yet
+ hit the retry time, use the error recorded in the retry database
+ as info in the warning message. This lets us send a message even
+ when we're not failing on a fresh attempt. We assume that this
+ info is not sensitive. */
+
+ addr->message = domain_retry_record
+ ? domain_retry_record->text : address_retry_record->text;
+ setflag(addr, af_pass_message);
}
/* The domain is OK for routing. Remember if retry data exists so it
those domains. During queue runs, queue_domains is forced to be unset.
Optimize by skipping this pass through the addresses if nothing is set. */
- if (!deliver_force && queue_domains)
+ if (!f.deliver_force && queue_domains)
{
address_item *okaddr = NULL;
while (addr_route)
&addr_succeed, v_none)) == DEFER)
retry_add_item(addr,
addr->router->retry_use_local_part
- ? string_sprintf("R:%s@%s", addr->local_part, addr->domain)
- : string_sprintf("R:%s", addr->domain),
+ ? string_sprintf("R:%s@%s", addr->local_part, addr->domain)
+ : string_sprintf("R:%s", addr->domain),
0);
/* Otherwise, if there is an existing retry record in the database, add
DEBUG(D_deliver|D_retry|D_route)
{
- address_item *p;
debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
debug_printf("After routing:\n Local deliveries:\n");
- for (p = addr_local; p; p = p->next)
+ for (address_item * p = addr_local; p; p = p->next)
debug_printf(" %s\n", p->address);
debug_printf(" Remote deliveries:\n");
- for (p = addr_remote; p; p = p->next)
+ for (address_item * p = addr_remote; p; p = p->next)
debug_printf(" %s\n", p->address);
debug_printf(" Failed addresses:\n");
- for (p = addr_failed; p; p = p->next)
+ for (address_item * p = addr_failed; p; p = p->next)
debug_printf(" %s\n", p->address);
debug_printf(" Deferred addresses:\n");
- for (p = addr_defer; p; p = p->next)
+ for (address_item * p = addr_defer; p; p = p->next)
debug_printf(" %s\n", p->address);
}
there is only address to be delivered - if it succeeds the spool write need not
happen. */
-if ( header_rewritten
+if ( f.header_rewritten
&& ( addr_local && (addr_local->next || addr_remote)
|| addr_remote && addr_remote->next
) )
{
/* Panic-dies on error */
(void)spool_write_header(message_id, SW_DELIVERING, NULL);
- header_rewritten = FALSE;
+ f.header_rewritten = FALSE;
}
-/* If there are any deliveries to be and we do not already have the journal
+/* If there are any deliveries to do and we do not already have the journal
file, create it. This is used to record successful deliveries as soon as
possible after each delivery is known to be complete. A file opened with
O_APPEND is used so that several processes can run simultaneously.
that the mode is correct - the group setting doesn't always seem to get
set automatically. */
- if( fchown(journal_fd, exim_uid, exim_gid)
+ if( exim_fchown(journal_fd, exim_uid, exim_gid, fname)
|| fchmod(journal_fd, SPOOL_MODE)
#ifndef O_CLOEXEC
|| fcntl(journal_fd, F_SETFD, fcntl(journal_fd, F_GETFD) | FD_CLOEXEC)
DEBUG(D_deliver|D_transport)
debug_printf(">>>>>>>>>>>>>>>> Local deliveries >>>>>>>>>>>>>>>>\n");
do_local_deliveries();
- disable_logging = FALSE;
+ f.disable_logging = FALSE;
}
/* If queue_run_local is set, we do not want to attempt any remote deliveries,
so just queue them all. */
-if (queue_run_local)
+if (f.queue_run_local)
while (addr_remote)
{
address_item *addr = addr_remote;
/* Precompile some regex that are used to recognize parameters in response
to an EHLO command, if they aren't already compiled. */
- deliver_init();
+ smtp_deliver_init();
/* Now sort the addresses if required, and do the deliveries. The yield of
do_remote_deliveries is FALSE when mua_wrapper is set and all addresses
if (remote_sort_domains) sort_remote_deliveries();
do_remote_deliveries(TRUE);
}
- disable_logging = FALSE;
+ f.disable_logging = FALSE;
}
{
if (addr_defer)
{
- address_item *addr, *nextaddr;
- for (addr = addr_defer; addr; addr = nextaddr)
+ address_item * nextaddr;
+ for (address_item * addr = addr_defer; addr; addr = nextaddr)
{
log_write(0, LOG_MAIN, "** %s mua_wrapper forced failure for deferred "
"delivery", addr->address);
updating of the database if the -N flag is set, which is a debugging thing that
prevents actual delivery. */
-else if (!dont_deliver)
+else if (!f.dont_deliver)
retry_update(&addr_defer, &addr_failed, &addr_succeed);
/* Send DSN for successful messages if requested */
addr_senddsn = NULL;
-for (addr_dsntmp = addr_succeed; addr_dsntmp; addr_dsntmp = addr_dsntmp->next)
+for (address_item * a = addr_succeed; a; a = a->next)
{
/* af_ignore_error not honored here. it's not an error */
DEBUG(D_deliver) debug_printf("DSN: processing router : %s\n"
"DSN: processing successful delivery address: %s\n"
"DSN: Sender_address: %s\n"
- "DSN: orcpt: %s flags: %d\n"
+ "DSN: orcpt: %s flags: 0x%x\n"
"DSN: envid: %s ret: %d\n"
"DSN: Final recipient: %s\n"
"DSN: Remote SMTP server supports DSN: %d\n",
- addr_dsntmp->router->name,
- addr_dsntmp->address,
+ a->router ? a->router->name : US"(unknown)",
+ a->address,
sender_address,
- addr_dsntmp->dsn_orcpt, addr_dsntmp->dsn_flags,
- dsn_envid, dsn_ret,
- addr_dsntmp->address,
- addr_dsntmp->dsn_aware
+ a->dsn_orcpt ? a->dsn_orcpt : US"NULL",
+ a->dsn_flags,
+ dsn_envid ? dsn_envid : US"NULL", dsn_ret,
+ a->address,
+ a->dsn_aware
);
/* send report if next hop not DSN aware or a router flagged "last DSN hop"
- and a report was requested */
- if ( ( addr_dsntmp->dsn_aware != dsn_support_yes
- || addr_dsntmp->dsn_flags & rf_dsnlasthop
- )
- && addr_dsntmp->dsn_flags & rf_dsnflags
- && addr_dsntmp->dsn_flags & rf_notify_success
+ and a report was requested */
+
+ if ( (a->dsn_aware != dsn_support_yes || a->dsn_flags & rf_dsnlasthop)
+ && a->dsn_flags & rf_notify_success
)
{
/* 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));
- *addr_senddsn = *addr_dsntmp;
+ addr_senddsn = store_get(sizeof(address_item), FALSE);
+ *addr_senddsn = *a;
addr_senddsn->next = addr_next;
}
else
int fd;
/* create exim process to send message */
- pid = child_open_exim(&fd);
+ pid = child_open_exim(&fd, US"DSN");
DEBUG(D_deliver) debug_printf("DSN: child_open_exim returns: %d\n", pid);
if (pid < 0) /* Creation of child failed */
{
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Process %d (parent %d) failed to "
- "create child process to send failure message: %s", getpid(),
+ "create child process to send success-dsn message: %s", getpid(),
getppid(), strerror(errno));
DEBUG(D_deliver) debug_printf("DSN: child_open_exim failed\n");
}
else /* Creation of child succeeded */
{
- FILE *f = fdopen(fd, "wb");
+ FILE * f = fdopen(fd, "wb");
/* header only as required by RFC. only failure DSN needs to honor RET=FULL */
uschar * bound;
transport_ctx tctx = {{0}};
DEBUG(D_deliver)
- debug_printf("sending error message to: %s\n", sender_address);
+ debug_printf("sending success-dsn to: %s\n", sender_address);
/* build unique id for MIME boundary */
bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
if (errors_reply_to)
fprintf(f, "Reply-To: %s\n", errors_reply_to);
+ moan_write_from(f);
fprintf(f, "Auto-Submitted: auto-generated\n"
- "From: Mail Delivery System <Mailer-Daemon@%s>\n"
"To: %s\n"
- "Subject: Delivery Status Notification\n"
- "Content-Type: multipart/report; report-type=delivery-status; boundary=%s\n"
+ "Subject: Delivery Status Notification\n",
+ sender_address);
+ moan_write_references(f, NULL);
+ fprintf(f, "Content-Type: multipart/report;"
+ " report-type=delivery-status; boundary=%s\n"
"MIME-Version: 1.0\n\n"
"--%s\n"
"This message was created automatically by mail delivery software.\n"
" ----- The following addresses had successful delivery notifications -----\n",
- qualify_domain_sender, sender_address, bound, bound);
+ bound, bound);
- for (addr_dsntmp = addr_senddsn; addr_dsntmp;
- addr_dsntmp = addr_dsntmp->next)
+ for (address_item * a = addr_senddsn; a; a = a->next)
fprintf(f, "<%s> (relayed %s)\n\n",
- addr_dsntmp->address,
- (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1
- ? "via non DSN router"
- : addr_dsntmp->dsn_aware == dsn_support_no
- ? "to non-DSN-aware mailer"
- : "via non \"Remote SMTP\" router"
+ a->address,
+ a->dsn_flags & rf_dsnlasthop ? "via non DSN router"
+ : a->dsn_aware == dsn_support_no ? "to non-DSN-aware mailer"
+ : "via non \"Remote SMTP\" router"
);
fprintf(f, "--%s\n"
}
fputc('\n', f);
- for (addr_dsntmp = addr_senddsn;
- addr_dsntmp;
- addr_dsntmp = addr_dsntmp->next)
+ for (address_item * a = addr_senddsn; a; a = a->next)
{
- if (addr_dsntmp->dsn_orcpt)
- fprintf(f,"Original-Recipient: %s\n", addr_dsntmp->dsn_orcpt);
+ host_item * hu;
- fprintf(f, "Action: delivered\n"
- "Final-Recipient: rfc822;%s\n"
- "Status: 2.0.0\n",
- addr_dsntmp->address);
+ print_dsn_addr_action(f, a, US"delivered", US"2.0.0");
- if (addr_dsntmp->host_used && addr_dsntmp->host_used->name)
+ if ((hu = a->host_used) && hu->name)
fprintf(f, "Remote-MTA: dns; %s\nDiagnostic-Code: smtp; 250 Ok\n\n",
- addr_dsntmp->host_used->name);
+ hu->name);
else
fprintf(f, "Diagnostic-Code: X-Exim; relayed via non %s router\n\n",
- (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1 ? "DSN" : "SMTP");
+ a->dsn_flags & rf_dsnlasthop ? "DSN" : "SMTP");
}
fprintf(f, "--%s\nContent-type: text/rfc822-headers\n\n", bound);
/* Write the original email out */
- tctx.u.fd = fileno(f);
+ tctx.u.fd = fd;
tctx.options = topt_add_return_path | topt_no_body;
+ /*XXX hmm, FALSE(fail) retval ignored.
+ Could error for any number of reasons, and they are not handled. */
transport_write_message(&tctx, 0);
fflush(f);
/* There are weird cases when logging is disabled in the transport. However,
there may not be a transport (address failed by a router). */
- disable_logging = FALSE;
+ f.disable_logging = FALSE;
if (addr_failed->transport)
- disable_logging = addr_failed->transport->disable_logging;
+ f.disable_logging = addr_failed->transport->disable_logging;
DEBUG(D_deliver)
debug_printf("processing failed address %s\n", addr_failed->address);
mark the recipient done. */
if ( addr_failed->prop.ignore_error
- || ( addr_failed->dsn_flags & rf_dsnflags
- && (addr_failed->dsn_flags & rf_notify_failure) != rf_notify_failure
- ) )
+ || addr_failed->dsn_flags & rf_dsnflags
+ && !(addr_failed->dsn_flags & rf_notify_failure)
+ )
{
addr = addr_failed;
addr_failed = addr->next;
if (addr->return_filename) Uunlink(addr->return_filename);
- log_write(0, LOG_MAIN, "%s%s%s%s: error ignored",
+#ifndef DISABLE_EVENT
+ msg_event_raise(US"msg:fail:delivery", addr);
+#endif
+ log_write(0, LOG_MAIN, "%s%s%s%s: error ignored%s",
addr->address,
!addr->parent ? US"" : US" <",
!addr->parent ? US"" : addr->parent->address,
- !addr->parent ? US"" : US">");
+ !addr->parent ? US"" : US">",
+ addr->prop.ignore_error
+ ? US"" : US": RFC 3461 DSN, failure notify not requested");
address_done(addr, logtod);
child_done(addr, logtod);
/* Make a subprocess to send a message */
- if ((pid = child_open_exim(&fd)) < 0)
+ if ((pid = child_open_exim(&fd, US"bounce-message")) < 0)
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Process %d (parent %d) failed to "
"create child process to send failure message: %s", getpid(),
getppid(), strerror(errno));
int filecount = 0;
int rcount = 0;
uschar *bcc, *emf_text;
- FILE *f = fdopen(fd, "wb");
- FILE *emf = NULL;
+ FILE * fp = fdopen(fd, "wb");
+ FILE * emf = NULL;
BOOL to_sender = strcmpic(sender_address, bounce_recipient) == 0;
int max = (bounce_return_size_limit/DELIVER_IN_BUFFER_SIZE + 1) *
DELIVER_IN_BUFFER_SIZE;
if (testflag(addr, af_hide_child)) continue;
if (rcount >= 50)
{
- fprintf(f, "\n");
+ fprintf(fp, "\n");
rcount = 0;
}
- fprintf(f, "%s%s",
+ fprintf(fp, "%s%s",
rcount++ == 0
? "X-Failed-Recipients: "
: ",\n ",
? string_printing(addr->parent->address)
: string_printing(addr->address));
}
- if (rcount > 0) fprintf(f, "\n");
+ if (rcount > 0) fprintf(fp, "\n");
/* Output the standard headers */
if (errors_reply_to)
- fprintf(f, "Reply-To: %s\n", errors_reply_to);
- fprintf(f, "Auto-Submitted: auto-replied\n");
- moan_write_from(f);
- fprintf(f, "To: %s\n", bounce_recipient);
+ fprintf(fp, "Reply-To: %s\n", errors_reply_to);
+ fprintf(fp, "Auto-Submitted: auto-replied\n");
+ moan_write_from(fp);
+ fprintf(fp, "To: %s\n", bounce_recipient);
+ moan_write_references(fp, NULL);
/* generate boundary string and output MIME-Headers */
bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
- fprintf(f, "Content-Type: multipart/report;"
+ fprintf(fp, "Content-Type: multipart/report;"
" report-type=delivery-status; boundary=%s\n"
"MIME-Version: 1.0\n",
bound);
carry on - default texts will be used. */
if (bounce_message_file)
- if (!(emf = Ufopen(bounce_message_file, "rb")))
- log_write(0, LOG_MAIN|LOG_PANIC, "Failed to open %s for error "
- "message texts: %s", bounce_message_file, strerror(errno));
+ emf = expand_open(bounce_message_file,
+ US"bounce_message_file", US"error");
/* Quietly copy to configured additional addresses if required. */
if ((bcc = moan_check_errorcopy(bounce_recipient)))
- fprintf(f, "Bcc: %s\n", bcc);
+ fprintf(fp, "Bcc: %s\n", bcc);
/* The texts for the message can be read from a template file; if there
isn't one, or if it is too short, built-in texts are used. The first
emf text is a Subject: and any other headers. */
if ((emf_text = next_emf(emf, US"header")))
- fprintf(f, "%s\n", emf_text);
+ fprintf(fp, "%s\n", emf_text);
else
- fprintf(f, "Subject: Mail delivery failed%s\n\n",
+ fprintf(fp, "Subject: Mail delivery failed%s\n\n",
to_sender? ": returning message to sender" : "");
/* output human readable part as text/plain section */
- fprintf(f, "--%s\n"
+ fprintf(fp, "--%s\n"
"Content-type: text/plain; charset=us-ascii\n\n",
bound);
if ((emf_text = next_emf(emf, US"intro")))
- fprintf(f, "%s", CS emf_text);
+ fprintf(fp, "%s", CS emf_text);
else
{
- fprintf(f,
+ fprintf(fp,
/* This message has been reworded several times. It seems to be confusing to
somebody, however it is worded. I have retreated to the original, simple
wording. */
"This message was created automatically by mail delivery software.\n");
if (bounce_message_text)
- fprintf(f, "%s", CS bounce_message_text);
+ fprintf(fp, "%s", CS bounce_message_text);
if (to_sender)
- fprintf(f,
+ fprintf(fp,
"\nA message that you sent could not be delivered to one or more of its\n"
"recipients. This is a permanent error. The following address(es) failed:\n");
else
- fprintf(f,
+ fprintf(fp,
"\nA message sent by\n\n <%s>\n\n"
"could not be delivered to one or more of its recipients. The following\n"
"address(es) failed:\n", sender_address);
}
- fputc('\n', f);
+ fputc('\n', fp);
/* Process the addresses, leaving them on the msgchain if they have a
file name for a return message. (There has already been a check in
paddr = &msgchain;
for (addr = msgchain; addr; addr = *paddr)
{
- if (print_address_information(addr, f, US" ", US"\n ", US""))
- print_address_error(addr, f, US"");
+ if (print_address_information(addr, fp, US" ", US"\n ", US""))
+ print_address_error(addr, fp, US"");
/* End the final line for the address */
- fputc('\n', f);
+ fputc('\n', fp);
/* Leave on msgchain if there's a return file. */
}
}
- fputc('\n', f);
+ fputc('\n', fp);
/* Get the next text, whether we need it or not, so as to be
positioned for the one after. */
address_item *nextaddr;
if (emf_text)
- fprintf(f, "%s", CS emf_text);
+ fprintf(fp, "%s", CS emf_text);
else
- fprintf(f,
+ fprintf(fp,
"The following text was generated during the delivery "
"attempt%s:\n", (filecount > 1)? "s" : "");
/* List all the addresses that relate to this file */
- fputc('\n', f);
+ fputc('\n', fp);
while(addr) /* Insurance */
{
- print_address_information(addr, f, US"------ ", US"\n ",
+ print_address_information(addr, fp, US"------ ", US"\n ",
US" ------\n");
if (addr->return_filename) break;
addr = addr->next;
}
- fputc('\n', f);
+ fputc('\n', fp);
/* Now copy the file */
if (!(fm = Ufopen(addr->return_filename, "rb")))
- fprintf(f, " +++ Exim error... failed to open text file: %s\n",
+ fprintf(fp, " +++ Exim error... failed to open text file: %s\n",
strerror(errno));
else
{
- while ((ch = fgetc(fm)) != EOF) fputc(ch, f);
+ while ((ch = fgetc(fm)) != EOF) fputc(ch, fp);
(void)fclose(fm);
}
Uunlink(addr->return_filename);
addr->next = handled_addr;
handled_addr = topaddr;
}
- fputc('\n', f);
+ fputc('\n', fp);
}
/* output machine readable part */
#ifdef SUPPORT_I18N
if (message_smtputf8)
- fprintf(f, "--%s\n"
+ fprintf(fp, "--%s\n"
"Content-type: message/global-delivery-status\n\n"
"Reporting-MTA: dns; %s\n",
bound, smtp_active_hostname);
else
#endif
- fprintf(f, "--%s\n"
+ fprintf(fp, "--%s\n"
"Content-type: message/delivery-status\n\n"
"Reporting-MTA: dns; %s\n",
bound, smtp_active_hostname);
/* must be decoded from xtext: see RFC 3461:6.3a */
uschar *xdec_envid;
if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0)
- fprintf(f, "Original-Envelope-ID: %s\n", dsn_envid);
+ fprintf(fp, "Original-Envelope-ID: %s\n", dsn_envid);
else
- fprintf(f, "X-Original-Envelope-ID: error decoding xtext formatted ENVID\n");
+ fprintf(fp, "X-Original-Envelope-ID: error decoding xtext formatted ENVID\n");
}
- fputc('\n', f);
+ fputc('\n', fp);
for (addr = handled_addr; addr; addr = addr->next)
{
host_item * hu;
- fprintf(f, "Action: failed\n"
- "Final-Recipient: rfc822;%s\n"
- "Status: 5.0.0\n",
- addr->address);
+
+ print_dsn_addr_action(fp, addr, US"failed", US"5.0.0");
+
if ((hu = addr->host_used) && hu->name)
{
- const uschar * s;
- fprintf(f, "Remote-MTA: dns; %s\n", hu->name);
+ fprintf(fp, "Remote-MTA: dns; %s\n", hu->name);
#ifdef EXPERIMENTAL_DSN_INFO
+ {
+ const uschar * s;
if (hu->address)
{
uschar * p = hu->port == 25
? US"" : string_sprintf(":%d", hu->port);
- fprintf(f, "Remote-MTA: X-ip; [%s]%s\n", hu->address, p);
+ fprintf(fp, "Remote-MTA: X-ip; [%s]%s\n", hu->address, p);
}
if ((s = addr->smtp_greeting) && *s)
- fprintf(f, "X-Remote-MTA-smtp-greeting: X-str; %s\n", s);
+ fprintf(fp, "X-Remote-MTA-smtp-greeting: X-str; %.900s\n", s);
if ((s = addr->helo_response) && *s)
- fprintf(f, "X-Remote-MTA-helo-response: X-str; %s\n", s);
+ fprintf(fp, "X-Remote-MTA-helo-response: X-str; %.900s\n", s);
if ((s = addr->message) && *s)
- fprintf(f, "X-Exim-Diagnostic: X-str; %s\n", s);
+ fprintf(fp, "X-Exim-Diagnostic: X-str; %.900s\n", s);
+ }
#endif
- print_dsn_diagnostic_code(addr, f);
+ print_dsn_diagnostic_code(addr, fp);
}
- fputc('\n', f);
+ fputc('\n', fp);
}
/* Now copy the message, trying to give an intelligible comment if
bounce_return_size_limit is always honored.
*/
- fprintf(f, "--%s\n", bound);
+ fprintf(fp, "--%s\n", bound);
dsnlimitmsg = US"X-Exim-DSN-Information: Due to administrative limits only headers are returned";
dsnnotifyhdr = NULL;
if (message_smtputf8)
fputs(topt & topt_no_body ? "Content-type: message/global-headers\n\n"
: "Content-type: message/global\n\n",
- f);
+ fp);
else
#endif
fputs(topt & topt_no_body ? "Content-type: text/rfc822-headers\n\n"
: "Content-type: message/rfc822\n\n",
- f);
+ fp);
- fflush(f);
+ fflush(fp);
transport_filter_argv = NULL; /* Just in case */
return_path = sender_address; /* In case not previously set */
{ /* Dummy transport for headers add */
transport_ctx tctx = {{0}};
transport_instance tb = {0};
- tctx.u.fd = fileno(f);
+ tctx.u.fd = fileno(fp);
tctx.tblock = &tb;
tctx.options = topt;
tb.add_headers = dsnnotifyhdr;
+ /*XXX no checking for failure! buggy! */
transport_write_message(&tctx, 0);
}
- fflush(f);
+ fflush(fp);
/* we never add the final text. close the file */
if (emf)
(void)fclose(emf);
- fprintf(f, "\n--%s--\n", bound);
+ fprintf(fp, "\n--%s--\n", bound);
/* Close the file, which should send an EOF to the child process
that is receiving the message. Wait for it to finish. */
- (void)fclose(f);
+ (void)fclose(fp);
rc = child_close(pid, 0); /* Waits for child to close, no timeout */
- /* In the test harness, let the child do it's thing first. */
-
- if (running_in_test_harness) millisleep(500);
-
/* If the process failed, there was some disaster in setting up the
error message. Unless the message is very old, ensure that addr_defer
is non-null, which will have the effect of leaving the message on the
if (now - received_time.tv_sec < retry_maximum_timeout && !addr_defer)
{
addr_defer = (address_item *)(+1);
- deliver_freeze = TRUE;
+ f.deliver_freeze = TRUE;
deliver_frozen_at = time(NULL);
/* Panic-dies on error */
(void)spool_write_header(message_id, SW_DELIVERING, NULL);
}
}
-disable_logging = FALSE; /* In case left set */
+f.disable_logging = FALSE; /* In case left set */
/* Come here from the mua_wrapper case if routing goes wrong */
log_write(0, LOG_MAIN, "Completed");
/* Unset deliver_freeze so that we won't try to move the spool files further down */
- deliver_freeze = FALSE;
+ f.deliver_freeze = FALSE;
#ifndef DISABLE_EVENT
(void) event_raise(event_action, US"msg:complete", NULL);
If all the deferred addresses have an error number that indicates "retry time
not reached", skip sending the warning message, because it won't contain the
reason for the delay. It will get sent at the next real delivery attempt.
+ Exception: for retries caused by a remote peer we use the error message
+ store in the retry DB as the reason.
However, if at least one address has tried, we'd better include all of them in
the message.
else if (addr_defer != (address_item *)(+1))
{
- address_item *addr;
uschar *recipients = US"";
- BOOL delivery_attempted = FALSE;
+ BOOL want_warning_msg = FALSE;
deliver_domain = testflag(addr_defer, af_pfr)
? addr_defer->parent->domain : addr_defer->domain;
- for (addr = addr_defer; addr; addr = addr->next)
+ for (address_item * addr = addr_defer; addr; addr = addr->next)
{
address_item *otaddr;
- if (addr->basic_errno > ERRNO_RETRY_BASE) delivery_attempted = TRUE;
+ if (addr->basic_errno > ERRNO_WARN_BASE) want_warning_msg = TRUE;
if (deliver_domain)
{
is not sent. Another attempt will be made at the next delivery attempt (if
it also defers). */
- if ( !queue_2stage
- && delivery_attempted
- && ( ((addr_defer->dsn_flags & rf_dsnflags) == 0)
- || (addr_defer->dsn_flags & rf_notify_delay) == rf_notify_delay
+ if ( !f.queue_2stage
+ && want_warning_msg
+ && ( !(addr_defer->dsn_flags & rf_dsnflags)
+ || addr_defer->dsn_flags & rf_notify_delay
)
&& delay_warning[1] > 0
&& sender_address[0] != 0
int show_time;
int queue_time = time(NULL) - received_time.tv_sec;
- /* When running in the test harness, there's an option that allows us to
- fudge this time so as to get repeatability of the tests. Take the first
- time off the list. In queue runs, the list pointer gets updated in the
- calling process. */
-
- if (running_in_test_harness && fudged_queue_times[0] != 0)
- {
- int qt = readconf_readtime(fudged_queue_times, '/', FALSE);
- if (qt >= 0)
- {
- DEBUG(D_deliver) debug_printf("fudged queue_times = %s\n",
- fudged_queue_times);
- queue_time = qt;
- }
- }
+ queue_time = test_harness_fudged_queue_time(queue_time);
/* See how many warnings we should have sent by now */
DEBUG(D_deliver)
{
- debug_printf("time on queue = %s\n", readconf_printtime(queue_time));
+ debug_printf("time on queue = %s id %s addr %s\n",
+ readconf_printtime(queue_time), message_id, addr_defer->address);
debug_printf("warning counts: required %d done %d\n", count,
warning_count);
}
{
header_line *h;
int fd;
- pid_t pid = child_open_exim(&fd);
+ pid_t pid = child_open_exim(&fd, US"delay-warning-message");
if (pid > 0)
{
- uschar *wmf_text;
- FILE *wmf = NULL;
- FILE *f = fdopen(fd, "wb");
+ uschar * wmf_text;
+ FILE * wmf = NULL;
+ FILE * f = fdopen(fd, "wb");
uschar * bound;
transport_ctx tctx = {{0}};
if (warn_message_file)
- if (!(wmf = Ufopen(warn_message_file, "rb")))
- log_write(0, LOG_MAIN|LOG_PANIC, "Failed to open %s for warning "
- "message texts: %s", warn_message_file, strerror(errno));
+ wmf = expand_open(warn_message_file,
+ US"warn_message_file", US"warning");
warnmsg_recipients = recipients;
warnmsg_delay = queue_time < 120*60
fprintf(f, "Auto-Submitted: auto-replied\n");
moan_write_from(f);
fprintf(f, "To: %s\n", recipients);
+ moan_write_references(f, NULL);
/* generated boundary string and output MIME-Headers */
bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
/* List the addresses, with error information if allowed */
- /* store addr_defer for machine readable part */
- address_item *addr_dsndefer = addr_defer;
fputc('\n', f);
- while (addr_defer)
+ for (address_item * addr = addr_defer; addr; addr = addr->next)
{
- address_item *addr = addr_defer;
- addr_defer = addr->next;
if (print_address_information(addr, f, US" ", US"\n ", US""))
print_address_error(addr, f, US"Delay reason: ");
fputc('\n', f);
}
fputc('\n', f);
- for ( ; addr_dsndefer; addr_dsndefer = addr_dsndefer->next)
+ for (address_item * addr = addr_defer; addr; addr = addr->next)
{
- if (addr_dsndefer->dsn_orcpt)
- fprintf(f, "Original-Recipient: %s\n", addr_dsndefer->dsn_orcpt);
-
- fprintf(f, "Action: delayed\n"
- "Final-Recipient: rfc822;%s\n"
- "Status: 4.0.0\n",
- addr_dsndefer->address);
- if (addr_dsndefer->host_used && addr_dsndefer->host_used->name)
+ host_item * hu;
+
+ print_dsn_addr_action(f, addr, US"delayed", US"4.0.0");
+
+ if ((hu = addr->host_used) && hu->name)
{
- fprintf(f, "Remote-MTA: dns; %s\n",
- addr_dsndefer->host_used->name);
- print_dsn_diagnostic_code(addr_dsndefer, f);
+ fprintf(f, "Remote-MTA: dns; %s\n", hu->name);
+ print_dsn_diagnostic_code(addr, f);
}
fputc('\n', f);
}
return_path = sender_address; /* In case not previously set */
/* Write the original email out */
+ /*XXX no checking for failure! buggy! */
transport_write_message(&tctx, 0);
fflush(f);
/* If this was a first delivery attempt, unset the first time flag, and
ensure that the spool gets updated. */
- if (deliver_firsttime)
+ if (f.deliver_firsttime)
{
- deliver_firsttime = FALSE;
+ f.deliver_firsttime = FALSE;
update_spool = TRUE;
}
For the "tell" message, we turn \n back into newline. Also, insert a newline
near the start instead of the ": " string. */
- if (deliver_freeze)
+ if (f.deliver_freeze)
{
- if (freeze_tell && freeze_tell[0] != 0 && !local_error_message)
+ if (freeze_tell && freeze_tell[0] != 0 && !f.local_error_message)
{
uschar *s = string_copy(frozen_info);
uschar *ss = Ustrstr(s, " by the system filter: ");
DEBUG(D_deliver)
debug_printf("delivery deferred: update_spool=%d header_rewritten=%d\n",
- update_spool, header_rewritten);
+ update_spool, f.header_rewritten);
- if (update_spool || header_rewritten)
+ if (update_spool || f.header_rewritten)
/* Panic-dies on error */
(void)spool_write_header(message_id, SW_DELIVERING, NULL);
}
/* Move the message off the spool if requested */
#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
- if (deliver_freeze && move_frozen_messages)
+ if (f.deliver_freeze && move_frozen_messages)
(void)spool_move_message(id, message_subdir, US"", US"F");
#endif
}
(void)close(deliver_datafile);
deliver_datafile = -1;
DEBUG(D_deliver) debug_printf("end delivery of %s\n", id);
+#ifdef MEASURE_TIMING
+report_time_since(×tamp_startup, US"delivery end"); /* testcase 0005 */
+#endif
/* It is unlikely that there will be any cached resources, since they are
released after routing, and in the delivery subprocesses. However, it's
void
-deliver_init(void)
+tcp_init(void)
{
#ifdef EXIM_TFO_PROBE
tfo_probe();
#else
-tcp_fastopen_ok = TRUE;
+f.tcp_fastopen_ok = TRUE;
#endif
-
-
-if (!regex_PIPELINING) regex_PIPELINING =
- regex_must_compile(US"\\n250[\\s\\-]PIPELINING(\\s|\\n|$)", FALSE, TRUE);
-
-if (!regex_SIZE) regex_SIZE =
- regex_must_compile(US"\\n250[\\s\\-]SIZE(\\s|\\n|$)", FALSE, TRUE);
-
-if (!regex_AUTH) regex_AUTH =
- regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
- FALSE, TRUE);
-
-#ifdef SUPPORT_TLS
-if (!regex_STARTTLS) regex_STARTTLS =
- regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
-#endif
-
-if (!regex_CHUNKING) regex_CHUNKING =
- regex_must_compile(US"\\n250[\\s\\-]CHUNKING(\\s|\\n|$)", FALSE, TRUE);
-
-#ifndef DISABLE_PRDR
-if (!regex_PRDR) regex_PRDR =
- regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE);
-#endif
-
-#ifdef SUPPORT_I18N
-if (!regex_UTF8) regex_UTF8 =
- regex_must_compile(US"\\n250[\\s\\-]SMTPUTF8(\\s|\\n|$)", FALSE, TRUE);
-#endif
-
-if (!regex_DSN) regex_DSN =
- regex_must_compile(US"\\n250[\\s\\-]DSN(\\s|\\n|$)", FALSE, TRUE);
-
-if (!regex_IGNOREQUOTA) regex_IGNOREQUOTA =
- regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE);
}
int rc;
uschar * new_sender_address,
* save_sender_address;
-BOOL save_qr = queue_running;
+BOOL save_qr = f.queue_running;
uschar * spoolname;
/* make spool_open_datafile non-noisy on fail */
-queue_running = TRUE;
+f.queue_running = TRUE;
/* Side effect: message_subdir is set for the (possibly split) spool directory */
deliver_datafile = spool_open_datafile(id);
-queue_running = save_qr;
+f.queue_running = save_qr;
if (deliver_datafile < 0)
return NULL;
{
uschar * where;
-if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
+if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
{
- int pfd[2], channel_fd = cutthrough.fd, pid;
+ int channel_fd = cutthrough.cctx.sock;
smtp_peer_options = cutthrough.peer_options;
continue_sequence = 0;
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
if (cutthrough.is_tls)
{
+ int pfd[2], pid;
+
smtp_peer_options |= OPTION_TLS;
sending_ip_address = cutthrough.snd_ip;
sending_port = cutthrough.snd_port;
goto fail;
where = US"fork";
- if ((pid = fork()) < 0)
+ testharness_pause_ms(150);
+ if ((pid = exim_fork(US"tls-proxy-interproc")) < 0)
goto fail;
- else if (pid == 0) /* child: fork again to totally disconnect */
+ if (pid == 0) /* child: will fork again to totally disconnect */
{
- if (running_in_test_harness) millisleep(100); /* let parent debug out */
+ smtp_proxy_tls(cutthrough.cctx.tls_ctx, big_buffer, big_buffer_size,
+ pfd, 5*60);
/* does not return */
- smtp_proxy_tls(big_buffer, big_buffer_size, pfd, 5*60);
}
- DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid);
close(pfd[0]);
waitpid(pid, NULL, 0);
(void) close(channel_fd); /* release the client socket */
}
return; /* compiler quietening; control does not reach here. */
+#ifndef DISABLE_TLS
fail:
log_write(0,
LOG_MAIN | (exec_type == CEE_EXEC_EXIT ? LOG_PANIC : LOG_PANIC_DIE),
Note: this must be _exit(), not exit(). */
_exit(EX_EXECFAILED);
+#endif
}
/* vi: aw ai sw=2