X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/d5b80e59458182b2d557a929a18cb8c70cd56b68..05c3a5a25488ae73043364a87dcf54c907a655d4:/src/src/deliver.c diff --git a/src/src/deliver.c b/src/src/deliver.c index 73921980e..5c5167b3a 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2016 */ +/* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ /* The main code for delivering a message. */ @@ -10,6 +10,7 @@ #include "exim.h" #include "transports/smtp.h" +#include #include @@ -65,7 +66,6 @@ static address_item *addr_new = NULL; 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; @@ -79,6 +79,52 @@ static uschar *used_return_path = NULL; +/************************************************* +* read as much as requested * +*************************************************/ + +/* The syscall read(2) doesn't always returns as much as we want. For +several reasons it might get less. (Not talking about signals, as syscalls +are restartable). When reading from a network or pipe connection the sender +might send in smaller chunks, with delays between these chunks. The read(2) +may return such a chunk. + +The more the writer writes and the smaller the pipe between write and read is, +the more we get the chance of reading leass than requested. (See bug 2130) + +This function read(2)s until we got all the data we *requested*. + +Note: This function may block. Use it only if you're sure about the +amount of data you will get. + +Argument: + fd the file descriptor to read from + buffer pointer to a buffer of size len + len the requested(!) amount of bytes + +Returns: the amount of bytes read +*/ +static ssize_t +readn(int fd, void * buffer, size_t len) +{ +uschar * next = buffer; +uschar * end = next + len; + +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 - US buffer; + next += got; + } + +return len; +} + + /************************************************* * Make a new address item * *************************************************/ @@ -98,7 +144,7 @@ Returns: a pointer to an initialized address_item 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; @@ -152,6 +198,7 @@ deliver_recipients = addr; 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 */ @@ -173,7 +220,9 @@ if (!addr->next) 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; @@ -213,7 +262,9 @@ if (!addr->next) 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; } } @@ -237,13 +288,12 @@ to the same pipe or file. */ 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; @@ -282,11 +332,13 @@ Returns: a file descriptor, or -1 (with errno set) 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 @@ -303,7 +355,7 @@ for (i = 2; i > 0; i--) #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; @@ -323,7 +375,7 @@ for (i = 2; i > 0; i--) MSGLOG_DIRECTORY_MODE, TRUE); } -*error = US"create"; +*error = US"create or open"; return -1; } @@ -375,14 +427,13 @@ Returns: nothing 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; @@ -610,8 +661,6 @@ Returns: nothing static void address_done(address_item *addr, uschar *now) { -address_item *dup; - update_spool = TRUE; /* Ensure spool gets updated */ /* Top-level address */ @@ -638,7 +687,7 @@ else tree_add_nonrecipient(addr->unique); /* 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); @@ -669,9 +718,10 @@ Returns: nothing 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); @@ -702,79 +752,80 @@ you can enable incoming_interface and disable outgoing_interface to get I= fields on incoming lines only. Arguments: - s The log line buffer - sizep Pointer to the buffer size - ptrp Pointer to current index into buffer + g The log line addr The address to be logged Returns: New value for s */ -static uschar * -d_log_interface(uschar *s, int *sizep, int *ptrp) +static gstring * +d_log_interface(gstring * g) { if (LOGGING(incoming_interface) && LOGGING(outgoing_interface) && sending_ip_address) { - s = string_append(s, sizep, ptrp, 2, US" I=[", sending_ip_address); - s = LOGGING(outgoing_port) - ? string_append(s, sizep, ptrp, 2, US"]:", - string_sprintf("%d", sending_port)) - : string_catn(s, sizep, ptrp, 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 s; +return g; } -static uschar * -d_hostlog(uschar * s, int * sp, int * pp, address_item * addr) +static gstring * +d_hostlog(gstring * g, address_item * addr) { host_item * h = addr->host_used; -s = string_append(s, sp, pp, 2, US" H=", h->name); +g = string_append(g, 2, US" H=", h->name); if (LOGGING(dnssec) && h->dnssec == DS_YES) - s = string_catn(s, sp, pp, US" DS", 3); + g = string_catn(g, US" DS", 3); -s = string_append(s, sp, pp, 3, US" [", h->address, US"]"); +g = string_append(g, 3, US" [", h->address, US"]"); if (LOGGING(outgoing_port)) - s = string_append(s, sp, pp, 2, US":", string_sprintf("%d", h->port)); + g = string_fmt_append(g, ":%d", h->port); #ifdef SUPPORT_SOCKS if (LOGGING(proxy) && proxy_local_address) { - s = string_append(s, sp, pp, 3, US" PRX=[", proxy_local_address, US"]"); + g = string_append(g, 3, US" PRX=[", proxy_local_address, US"]"); if (LOGGING(outgoing_port)) - s = string_append(s, sp, pp, 2, US":", string_sprintf("%d", - proxy_local_port)); + g = string_fmt_append(g, ":%d", proxy_local_port); } #endif -s = d_log_interface(s, sp, pp); +g = d_log_interface(g); if (testflag(addr, af_tcp_fastopen)) - s = string_catn(s, sp, pp, US" TFO", 4); + g = string_catn(g, US" TFO*", testflag(addr, af_tcp_fastopen_data) ? 5 : 4); -return s; +return g; } -#ifdef SUPPORT_TLS -static uschar * -d_tlslog(uschar * s, int * sizep, int * ptrp, address_item * addr) +#ifndef DISABLE_TLS +static gstring * +d_tlslog(gstring * g, address_item * addr) { if (LOGGING(tls_cipher) && addr->cipher) - s = string_append(s, sizep, ptrp, 2, US" X=", addr->cipher); + { + g = string_append(g, 2, US" X=", addr->cipher); +#ifdef EXPERIMENTAL_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, sizep, ptrp, 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" : @@ -782,9 +833,8 @@ if (LOGGING(tls_certificate_verified) && addr->cipher) "yes" : "no"); if (LOGGING(tls_peerdn) && addr->peerdn) - s = string_append(s, sizep, ptrp, 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 @@ -809,7 +859,7 @@ if (action) 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; @@ -820,6 +870,7 @@ if (action) { DEBUG(D_deliver) debug_printf("Event(%s): event_action returned \"%s\"\n", event, s); + errno = ERRNO_EVENT; return s; } } @@ -835,20 +886,33 @@ const uschar * save_host = deliver_host; 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; @@ -873,15 +937,12 @@ router_name = transport_name = NULL; Arguments: addr the address being logged yield the current dynamic buffer pointer - sizeptr points to current size - ptrptr points to current insert pointer Returns: the new value of the buffer pointer */ -static uschar * -string_get_localpart(address_item *addr, uschar *yield, int *sizeptr, - int *ptrptr) +static gstring * +string_get_localpart(address_item * addr, gstring * yield) { uschar * s; @@ -892,7 +953,7 @@ if (testflag(addr, af_include_affixes) && s) if (testflag(addr, af_utf8_downcvt)) s = string_localpart_utf8_to_alabel(s, NULL); #endif - yield = string_cat(yield, sizeptr, ptrptr, s); + yield = string_cat(yield, s); } s = addr->local_part; @@ -900,7 +961,7 @@ s = addr->local_part; if (testflag(addr, af_utf8_downcvt)) s = string_localpart_utf8_to_alabel(s, NULL); #endif -yield = string_cat(yield, sizeptr, ptrptr, s); +yield = string_cat(yield, s); s = addr->suffix; if (testflag(addr, af_include_affixes) && s) @@ -909,7 +970,7 @@ if (testflag(addr, af_include_affixes) && s) if (testflag(addr, af_utf8_downcvt)) s = string_localpart_utf8_to_alabel(s, NULL); #endif - yield = string_cat(yield, sizeptr, ptrptr, s); + yield = string_cat(yield, s); } return yield; @@ -927,9 +988,7 @@ affixes set, the af_include_affixes bit will be set in the address. In that case, we include the affixes here too. Arguments: - str points to start of growing string, or NULL - size points to current allocation for string - ptr points to offset for append point; updated on exit + g points to growing-string struct addr bottom (ultimate) address all_parents if TRUE, include all parents success TRUE for successful delivery @@ -937,8 +996,8 @@ Arguments: Returns: a growable string in dynamic store */ -static uschar * -string_log_address(uschar * str, int * size, int * ptr, +static gstring * +string_log_address(gstring * g, address_item *addr, BOOL all_parents, BOOL success) { BOOL add_topaddr = TRUE; @@ -962,8 +1021,8 @@ if ( testflag(addr, af_pfr) ) ) { if (testflag(addr, af_file) && addr->local_part[0] != '/') - str = string_catn(str, size, ptr, CUS"save ", 5); - str = string_get_localpart(addr, str, size, ptr); + g = string_catn(g, CUS"save ", 5); + g = string_get_localpart(addr, g); } /* Other deliveries start with the full address. It we have split it into local @@ -972,29 +1031,31 @@ splitting is done; in those cases use the original field. */ else { - uschar * cmp = str + *ptr; + uschar * cmp; + int off = g->ptr; /* start of the "full address" */ if (addr->local_part) { const uschar * s; - str = string_get_localpart(addr, str, size, ptr); - str = string_catn(str, size, ptr, US"@", 1); + g = string_get_localpart(addr, g); + g = string_catn(g, US"@", 1); s = addr->domain; #ifdef SUPPORT_I18N if (testflag(addr, af_utf8_downcvt)) s = string_localpart_utf8_to_alabel(s, NULL); #endif - str = string_cat(str, size, ptr, s); + g = string_cat(g, s); } else - str = string_cat(str, size, ptr, addr->address); + g = string_cat(g, addr->address); /* If the address we are going to print is the same as the top address, and all parents are not being included, don't add on the top address. First of all, do a caseless comparison; if this succeeds, do a caseful comparison on the local parts. */ - str[*ptr] = 0; + 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->onetime_parent @@ -1012,65 +1073,28 @@ if ( (all_parents || testflag(addr, af_pfr)) && 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) { - str = string_catn(str, size, ptr, s, 2); - str = string_cat (str, size, ptr, addr2->address); + g = string_catn(g, s, 2); + g = string_cat (g, addr2->address); if (!all_parents) break; s = US", "; } - str = string_catn(str, size, ptr, US")", 1); + g = string_catn(g, US")", 1); } /* Add the top address if it is required */ if (add_topaddr) - str = string_append(str, size, ptr, 3, + g = string_append(g, 3, US" <", addr->onetime_parent ? addr->onetime_parent : topaddr->address, US">"); -return str; -} - - - -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; - } -} - - - -static 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; +return g; } -uschar * -string_timesince(struct timeval * then) -{ -struct timeval diff; - -timesince(&diff, then); -return string_timediff(&diff); -} /******************************************************************************/ @@ -1086,10 +1110,8 @@ Arguments: void delivery_log(int flags, address_item * addr, int logchar, uschar * msg) { -int size = 256; /* Used for a temporary, */ -int ptr = 0; /* expanding buffer, for */ -uschar * s; /* building log lines; */ -void * reset_point; /* released afterwards. */ +gstring * g; /* Used for a temporary, expanding buffer, for building log lines */ +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 @@ -1101,19 +1123,20 @@ pointer to a single host item in their host list, for use by the transport. */ lookup_dnssec_authenticated = NULL; #endif -s = reset_point = store_get(size); +reset_point = store_mark(); +g = string_get_tainted(256, TRUE); /* addrs will be tainted, so avoid copy */ if (msg) - s = string_append(s, &size, &ptr, 2, host_and_ident(TRUE), US" "); + g = string_append(g, 2, host_and_ident(TRUE), US" "); else { - s[ptr++] = logchar; - s = string_catn(s, &size, &ptr, US"> ", 2); + g->s[0] = logchar; g->ptr = 1; + g = string_catn(g, US"> ", 2); } -s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), TRUE); +g = string_log_address(g, addr, LOGGING(all_parents), TRUE); if (LOGGING(sender_on_delivery) || msg) - s = string_append(s, &size, &ptr, 3, US" F=<", + g = string_append(g, 3, US" F=<", #ifdef SUPPORT_I18N testflag(addr, af_utf8_downcvt) ? string_address_utf8_to_alabel(sender_address, NULL) @@ -1123,11 +1146,11 @@ if (LOGGING(sender_on_delivery) || msg) US">"); if (*queue_name) - s = string_append(s, &size, &ptr, 2, US" Q=", queue_name); + g = string_append(g, 2, US" Q=", queue_name); #ifdef EXPERIMENTAL_SRS if(addr->prop.srs_sender) - s = string_append(s, &size, &ptr, 3, US" SRS=<", addr->prop.srs_sender, US">"); + 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 @@ -1136,30 +1159,29 @@ when it is not set is for a delivery to /dev/null which is optimised by not being run at all. */ if (used_return_path && LOGGING(return_path_on_delivery)) - s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">"); + g = string_append(g, 3, US" P=<", used_return_path, US">"); if (msg) - s = string_append(s, &size, &ptr, 2, US" ", msg); + g = string_append(g, 2, US" ", msg); /* For a delivery from a system filter, there may not be a router */ if (addr->router) - s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name); + g = string_append(g, 2, US" R=", addr->router->name); -s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name); +g = string_append(g, 2, US" T=", addr->transport->name); if (LOGGING(delivery_size)) - s = string_append(s, &size, &ptr, 2, US" S=", - string_sprintf("%d", transport_count)); + g = string_fmt_append(g, " S=%d", transport_count); /* Local delivery */ if (addr->transport->info->local) { if (addr->host_list) - s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name); - s = d_log_interface(s, &size, &ptr); + g = string_append(g, 2, US" H=", addr->host_list->name); + g = d_log_interface(g); if (addr->shadow_message) - s = string_cat(s, &size, &ptr, addr->shadow_message); + g = string_cat(g, addr->shadow_message); } /* Remote delivery */ @@ -1168,9 +1190,9 @@ else { if (addr->host_used) { - s = d_hostlog(s, &size, &ptr, addr); + g = d_hostlog(g, addr); if (continue_sequence > 1) - s = string_catn(s, &size, &ptr, US"*", 1); + g = string_catn(g, US"*", 1); #ifndef DISABLE_EVENT deliver_host_address = addr->host_used->address; @@ -1184,28 +1206,38 @@ else #endif } -#ifdef SUPPORT_TLS - s = d_tlslog(s, &size, &ptr, addr); +#ifndef DISABLE_TLS + g = d_tlslog(g, addr); #endif if (addr->authenticator) { - s = string_append(s, &size, &ptr, 2, US" A=", addr->authenticator); + g = string_append(g, 2, US" A=", addr->authenticator); if (addr->auth_id) { - s = string_append(s, &size, &ptr, 2, US":", addr->auth_id); + g = string_append(g, 2, US":", addr->auth_id); if (LOGGING(smtp_mailauth) && addr->auth_sndr) - s = string_append(s, &size, &ptr, 2, US":", addr->auth_sndr); + g = string_append(g, 2, US":", addr->auth_sndr); } } + 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)) - s = string_catn(s, &size, &ptr, US" PRDR", 5); + g = string_catn(g, US" PRDR", 5); #endif if (testflag(addr, af_chunking_used)) - s = string_catn(s, &size, &ptr, US" K", 2); + g = string_catn(g, US" K", 2); } /* confirmation message (SMTP (host_used) and LMTP (driver_name)) */ @@ -1215,38 +1247,33 @@ if ( LOGGING(smtp_confirmation) && (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]; } *p++ = '\"'; *p = 0; - s = string_append(s, &size, &ptr, 2, US" C=", big_buffer); + g = string_append(g, 2, US" C=", big_buffer); } /* Time on queue and actual time taken to deliver */ if (LOGGING(queue_time)) - s = string_append(s, &size, &ptr, 2, US" QT=", + g = string_append(g, 2, US" QT=", string_timesince(&received_time)); if (LOGGING(deliver_time)) - { - struct timeval diff = {.tv_sec = addr->more_errno, .tv_usec = addr->delivery_usec}; - s = string_append(s, &size, &ptr, 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. */ -s[ptr] = 0; -log_write(0, flags, "%s", s); +log_write(0, flags, "%s", string_from_gstring(g)); #ifndef DISABLE_EVENT if (!msg) msg_event_raise(US"msg:delivery", addr); @@ -1262,25 +1289,19 @@ static void deferral_log(address_item * addr, uschar * now, int logflags, uschar * driver_name, uschar * driver_kind) { -int size = 256; /* Used for a temporary, */ -int ptr = 0; /* expanding buffer, for */ -uschar * s; /* building log lines; */ -void * reset_point; /* released afterwards. */ - -uschar ss[32]; +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. */ -s = reset_point = store_get(size); - /* 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. */ -s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), FALSE); +g = string_log_address(g, addr, LOGGING(all_parents), FALSE); if (*queue_name) - s = string_append(s, &size, &ptr, 2, US" Q=", queue_name); + g = string_append(g, 2, US" Q=", queue_name); /* Either driver_name contains something and driver_kind contains " router" or " transport" (note the leading space), or driver_name is @@ -1291,45 +1312,43 @@ so nothing has been done at all, both variables contain null strings. */ if (driver_name) { if (driver_kind[1] == 't' && addr->router) - s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name); - Ustrcpy(ss, " ?="); - ss[1] = toupper(driver_kind[1]); - s = string_append(s, &size, &ptr, 2, ss, driver_name); + g = string_append(g, 2, US" R=", addr->router->name); + g = string_fmt_append(g, " %c=%s", toupper(driver_kind[1]), driver_name); } else if (driver_kind) - s = string_append(s, &size, &ptr, 2, US" ", driver_kind); + g = string_append(g, 2, US" ", driver_kind); -/*XXX need an s+s+p sprintf */ -sprintf(CS ss, " defer (%d)", addr->basic_errno); -s = string_cat(s, &size, &ptr, ss); +g = string_fmt_append(g, " defer (%d)", addr->basic_errno); if (addr->basic_errno > 0) - s = string_append(s, &size, &ptr, 2, US": ", + g = string_append(g, 2, US": ", US strerror(addr->basic_errno)); if (addr->host_used) { - s = string_append(s, &size, &ptr, 5, + g = string_append(g, 5, US" H=", addr->host_used->name, US" [", addr->host_used->address, US"]"); if (LOGGING(outgoing_port)) { int port = addr->host_used->port; - s = string_append(s, &size, &ptr, 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) - s = string_append(s, &size, &ptr, 2, US": ", addr->message); + g = string_append(g, 2, US": ", addr->message); -s[ptr] = 0; +(void) 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. */ -if (deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE) - deliver_msglog("%s %s\n", now, s); +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. For errors of the type "retry time not reached" (also remotes skipped @@ -1339,7 +1358,7 @@ others. */ log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags, - "== %s", s); + "== %s", g->s); store_reset(reset_point); return; @@ -1350,64 +1369,69 @@ return; static void failure_log(address_item * addr, uschar * driver_kind, uschar * now) { -int size = 256; /* Used for a temporary, */ -int ptr = 0; /* expanding buffer, for */ -uschar * s; /* building log lines; */ -void * reset_point; /* released afterwards. */ +rmark reset_point = store_mark(); +gstring * g = string_get(256); -/* Build up the log line for the message and main logs */ +#ifndef DISABLE_EVENT +/* Message failures for which we will send a DSN get their event raised +later so avoid doing it here. */ -s = reset_point = store_get(size); +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 */ /* 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. */ -s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), FALSE); +g = string_log_address(g, addr, LOGGING(all_parents), FALSE); if (LOGGING(sender_on_delivery)) - s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">"); + g = string_append(g, 3, US" F=<", sender_address, US">"); if (*queue_name) - s = string_append(s, &size, &ptr, 2, US" Q=", queue_name); + g = string_append(g, 2, US" Q=", queue_name); /* Return path may not be set if no delivery actually happened */ if (used_return_path && LOGGING(return_path_on_delivery)) - s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">"); + g = string_append(g, 3, US" P=<", used_return_path, US">"); if (addr->router) - s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name); + g = string_append(g, 2, US" R=", addr->router->name); if (addr->transport) - s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name); + g = string_append(g, 2, US" T=", addr->transport->name); if (addr->host_used) - s = d_hostlog(s, &size, &ptr, addr); + g = d_hostlog(g, addr); -#ifdef SUPPORT_TLS -s = d_tlslog(s, &size, &ptr, addr); +#ifndef DISABLE_TLS +g = d_tlslog(g, addr); #endif if (addr->basic_errno > 0) - s = string_append(s, &size, &ptr, 2, US": ", US strerror(addr->basic_errno)); + g = string_append(g, 2, US": ", US strerror(addr->basic_errno)); if (addr->message) - s = string_append(s, &size, &ptr, 2, US": ", 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)); -s[ptr] = 0; +(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, s); + deliver_msglog("%s %s failed for %s\n", now, driver_kind, g->s); else - deliver_msglog("%s %s\n", now, s); + deliver_msglog("%s %s\n", now, g->s); -log_write(0, LOG_MAIN, "** %s", s); - -#ifndef DISABLE_EVENT -msg_event_raise(US"msg:fail:delivery", addr); -#endif +log_write(0, LOG_MAIN, "** %s", g->s); store_reset(reset_point); return; @@ -1446,23 +1470,23 @@ DEBUG(D_deliver) debug_printf("post-process %s (%d)\n", addr->address, result); /* Set up driver kind and name for logging. Disable logging if the router or transport has disabled it. */ -if (driver_type == DTYPE_TRANSPORT) +if (driver_type == EXIM_DTYPE_TRANSPORT) { if (addr->transport) { 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"; } -else if (driver_type == DTYPE_ROUTER) +else if (driver_type == EXIM_DTYPE_ROUTER) { if (addr->router) { 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"; } @@ -1530,7 +1554,7 @@ if (addr->return_file >= 0 && addr->return_filename) 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 @@ -1587,29 +1611,31 @@ if (result == OK) } /* 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 @@ -1636,7 +1662,7 @@ else if (result == DEFER || result == PANIC) if (addr->special_action == SPECIAL_FREEZE) { - deliver_freeze = TRUE; + f.deliver_freeze = TRUE; deliver_frozen_at = time(NULL); update_spool = TRUE; } @@ -1644,7 +1670,7 @@ else if (result == DEFER || result == PANIC) /* 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); } @@ -1677,10 +1703,10 @@ else { 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; @@ -1705,7 +1731,7 @@ else /* Ensure logging is turned on again in all cases */ -disable_logging = FALSE; +f.disable_logging = FALSE; } @@ -1734,22 +1760,20 @@ Returns: nothing 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; @@ -1779,9 +1803,8 @@ Returns: TRUE if the uid is on the list 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; } @@ -2007,10 +2030,10 @@ Returns: TRUE if previously delivered by the transport 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", @@ -2131,7 +2154,7 @@ if (tp->return_path) uschar *new_return_path = expand_string(tp->return_path); if (!new_return_path) { - if (!expand_string_forcedfail) + if (!f.expand_string_forcedfail) { common_error(TRUE, addr, ERRNO_EXPANDFAIL, US"Failed to expand return path \"%s\" in %s transport: %s", @@ -2326,9 +2349,8 @@ if ((pid = fork()) == 0) 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); } @@ -2392,7 +2414,7 @@ if ((pid = fork()) == 0) || (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 *) @@ -2460,7 +2482,7 @@ for (addr2 = addr; addr2; addr2 = addr2->next) 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 *)); @@ -2530,7 +2552,7 @@ if (!shadowing) /* 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); @@ -2650,7 +2672,7 @@ if (max_parallel > 0) next = addr->next; addr->message = US"concurrency limit reached for transport"; addr->basic_errno = ERRNO_TRETRY; - post_process_one(addr, DEFER, LOG_MAIN, DTYPE_TRANSPORT, 0); + post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_TRANSPORT, 0); } while ((addr = next)); return TRUE; } @@ -2690,7 +2712,7 @@ while (addr_local) 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; @@ -2708,11 +2730,11 @@ while (addr_local) 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"); - post_process_one(addr, DEFER, logflags, DTYPE_TRANSPORT, 0); + : US"No transport set by system filter"; + post_process_one(addr, DEFER, logflags, EXIM_DTYPE_TRANSPORT, 0); continue; } @@ -2726,7 +2748,7 @@ while (addr_local) /* 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 @@ -2847,7 +2869,7 @@ while (addr_local) while (addr) { addr2 = addr->next; - post_process_one(addr, rc, logflags, DTYPE_TRANSPORT, 0); + post_process_one(addr, rc, logflags, EXIM_DTYPE_TRANSPORT, 0); addr = addr2; } continue; /* With next batch of addresses */ @@ -2862,7 +2884,7 @@ while (addr_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"); @@ -2913,7 +2935,7 @@ while (addr_local) 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) @@ -2949,7 +2971,7 @@ while (addr_local) this->basic_errno = ERRNO_LRETRY; addr2 = addr3 ? (addr3->next = addr2->next) : (addr = addr2->next); - post_process_one(this, DEFER, logflags, DTYPE_TRANSPORT, 0); + post_process_one(this, DEFER, logflags, EXIM_DTYPE_TRANSPORT, 0); } } @@ -2972,7 +2994,7 @@ while (addr_local) do { addr = addr->next; - post_process_one(addr, DEFER, logflags, DTYPE_TRANSPORT, 0); + post_process_one(addr, DEFER, logflags, EXIM_DTYPE_TRANSPORT, 0); } while ((addr = addr2)); } continue; /* Loop for the next set of addresses. */ @@ -3022,7 +3044,7 @@ while (addr_local) 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; @@ -3066,12 +3088,7 @@ while (addr_local) 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) @@ -3100,12 +3117,7 @@ while (addr_local) 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 @@ -3124,12 +3136,8 @@ while (addr_local) /* Done with this address */ - if (result == OK) - { - addr2->more_errno = deliver_time.tv_sec; - addr2->delivery_usec = deliver_time.tv_usec; - } - post_process_one(addr2, result, logflags, DTYPE_TRANSPORT, logchar); + 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 changed to FAIL, and we must copy this for subsequent addresses in the @@ -3238,9 +3246,8 @@ while ( *aptr 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); } } @@ -3270,8 +3277,8 @@ small items (less than PIPE_BUF, which seems to be at least 512 in any Unix and 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 @@ -3333,14 +3340,13 @@ while (!done) If we get less, we can assume the subprocess do be done and do not expect any further information from it. */ - got = readn(fd, pipeheader, required); - if (got != required) + if ((got = readn(fd, pipeheader, required)) != required) { - msg = string_sprintf("got %d of %d bytes (pipeheader) " - "from transport process %d for transport %s", - got, PIPE_HEADER_SIZE, pid, addr->transport->driver_name); - done = TRUE; - break; + msg = string_sprintf("got " SSIZE_T_FMT " of %d bytes (pipeheader) " + "from transport process %d for transport %s", + got, PIPE_HEADER_SIZE, pid, addr->transport->driver_name); + done = TRUE; + break; } pipeheader[PIPE_HEADER_SIZE] = '\0'; @@ -3370,14 +3376,13 @@ while (!done) /* Same as above, the transport process will write the bytes announced in a timely manner, so we can just wait for the bytes, getting less than expected is considered a problem of the subprocess, we do not expect anything else from it. */ - got = readn(fd, big_buffer, required); - if (got != required) + if ((got = readn(fd, big_buffer, required)) != required) { - msg = string_sprintf("got only %d of %d bytes (pipedata) " - "from transport process %d for transport %s", - got, required, pid, addr->transport->driver_name); - done = TRUE; - break; + msg = string_sprintf("got only " SSIZE_T_FMT " of " SIZE_T_FMT + " bytes (pipedata) from transport process %d for transport %s", + got, required, pid, addr->transport->driver_name); + done = TRUE; + break; } /* Handle each possible type of item, assuming the complete item is @@ -3433,7 +3438,7 @@ while (!done) 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++; @@ -3474,17 +3479,19 @@ while (!done) 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); @@ -3512,7 +3519,7 @@ while (!done) } while (*ptr++); break; -#endif /*SUPPORT_TLS*/ +#endif /*DISABLE_TLS*/ case 'C': /* client authenticator information */ switch (*subid) @@ -3530,12 +3537,24 @@ while (!done) 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); + 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': @@ -3591,8 +3610,8 @@ while (!done) 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; @@ -3604,7 +3623,7 @@ while (!done) 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); @@ -3735,12 +3754,10 @@ static void 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); @@ -3778,7 +3795,7 @@ while (addr) addr->transport_return = DEFER; } (void)post_process_one(addr, addr->transport_return, logflags, - DTYPE_TRANSPORT, addr->special_action); + EXIM_DTYPE_TRANSPORT, addr->special_action); } /* Next address */ @@ -4174,7 +4191,6 @@ static BOOL do_remote_deliveries(BOOL fallback) { int parmax; -int delivery_count; int poffset; parcount = 0; /* Number of executing subprocesses */ @@ -4191,14 +4207,14 @@ set up, do so. */ 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; @@ -4229,7 +4245,7 @@ for (delivery_count = 0; addr_remote; delivery_count++) if (!(tp = addr->transport)) { - disable_logging = FALSE; /* Jic */ + f.disable_logging = FALSE; /* Jic */ panicmsg = US"No transport set by router"; goto panic_continue; } @@ -4424,7 +4440,7 @@ for (delivery_count = 0; addr_remote; delivery_count++) 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); @@ -4456,7 +4472,7 @@ for (delivery_count = 0; addr_remote; delivery_count++) 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"); @@ -4475,7 +4491,7 @@ for (delivery_count = 0; addr_remote; delivery_count++) 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; @@ -4497,9 +4513,8 @@ for (delivery_count = 0; addr_remote; delivery_count++) && 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; } @@ -4550,13 +4565,10 @@ for (delivery_count = 0; addr_remote; delivery_count++) 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 @@ -4634,18 +4646,17 @@ all pipes, so I do not see a reason to use non-blocking IO here search_tidyup(); - + DEBUG(D_deliver) debug_printf("forking transport process\n"); if ((pid = fork()) == 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 */ @@ -4660,7 +4671,7 @@ all pipes, so I do not see a reason to use non-blocking IO here 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 @@ -4759,16 +4770,18 @@ all pipes, so I do not see a reason to use non-blocking IO here 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 +# ifdef EXPERIMENTAL_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; @@ -4789,7 +4802,7 @@ all pipes, so I do not see a reason to use non-blocking IO here 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; @@ -4798,7 +4811,7 @@ all pipes, so I do not see a reason to use non-blocking IO here 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; @@ -4811,7 +4824,7 @@ all pipes, so I do not see a reason to use non-blocking IO here rmt_dlv_checked_write(fd, 'X', '4', big_buffer, ptr - big_buffer); } # endif -#endif /*SUPPORT_TLS*/ +#endif /*DISABLE_TLS*/ if (client_authenticator) { @@ -4834,18 +4847,29 @@ all pipes, so I do not see a reason to use non-blocking IO here 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)) - rmt_dlv_checked_write(fd, 'T', '0', NULL, 0); + if (testflag(addr, af_tcp_fastopen_conn)) + rmt_dlv_checked_write(fd, 'T', + 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; @@ -4904,8 +4928,8 @@ all pipes, so I do not see a reason to use non-blocking IO here 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); @@ -4953,6 +4977,7 @@ all pipes, so I do not see a reason to use non-blocking IO here (void)close(fd); exit(EXIT_SUCCESS); } + DEBUG(D_deliver) debug_printf("forked transport process (%d)\n", pid); /* Back in the mainline: close the unwanted half of the pipe. */ @@ -4962,12 +4987,13 @@ all pipes, so I do not see a reason to use non-blocking IO here 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"); } @@ -5007,9 +5033,10 @@ all pipes, so I do not see a reason to use non-blocking IO here /* 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; @@ -5068,7 +5095,7 @@ where they are locally interpreted. [The new draft "821" is more explicit on 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++; @@ -5111,7 +5138,7 @@ if (percent_hack_domains) 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; @@ -5151,9 +5178,8 @@ Returns: NULL or an expanded string static uschar * next_emf(FILE *f, uschar *which) { -int size = 256; -int ptr = 0; -uschar *para, *yield; +uschar *yield; +gstring * para; uschar buffer[256]; if (!f) return NULL; @@ -5161,16 +5187,14 @@ if (!f) return NULL; if (!Ufgets(buffer, sizeof(buffer), f) || Ustrcmp(buffer, "****\n") == 0) return NULL; -para = store_get(size); +para = string_get(256); for (;;) { - para = string_cat(para, &size, &ptr, buffer); + para = string_cat(para, buffer); if (!Ufgets(buffer, sizeof(buffer), f) || Ustrcmp(buffer, "****\n") == 0) break; } -para[ptr] = 0; - -if ((yield = expand_string(para))) +if ((yield = expand_string(string_from_gstring(para)))) return yield; log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand string from " @@ -5198,15 +5222,12 @@ static int 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; } @@ -5314,7 +5335,7 @@ static void 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; @@ -5443,6 +5464,45 @@ while ((addr = *anchor)) +/************************************************/ + +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; +} + /************************************************* * Deliver one message * *************************************************/ @@ -5494,8 +5554,13 @@ int process_recipients = RECIP_ACCEPT; open_db dbblock; open_db *dbm_file; extern int acl_where; +uschar *info; + +#ifdef MEASURE_TIMING +report_time_since(×tamp_startup, US"delivery start"); /* testcase 0022, 2100 */ +#endif -uschar *info = queue_run_pid == (pid_t)0 +info = queue_run_pid == (pid_t)0 ? string_sprintf("delivering %s", id) : string_sprintf("delivering %s (queue run pid %d)", id, queue_run_pid); @@ -5535,8 +5600,9 @@ message size. This use of strcpy() is OK because the length id is checked when 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; @@ -5684,7 +5750,7 @@ Otherwise it might be needed again. */ 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 @@ -5727,8 +5793,8 @@ if (deliver_freeze) || 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); @@ -5742,7 +5808,7 @@ if (deliver_freeze) 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"); @@ -5750,7 +5816,7 @@ if (deliver_freeze) /* We get here if any of the rules for unfreezing have triggered. */ - deliver_freeze = FALSE; + f.deliver_freeze = FALSE; update_spool = TRUE; } @@ -5820,13 +5886,11 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT) 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. */ @@ -5874,8 +5938,8 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT) /* 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 @@ -5898,9 +5962,9 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT) 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", @@ -5967,8 +6031,8 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT) 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 @@ -6042,6 +6106,9 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT) 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 @@ -6071,7 +6138,7 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT) p = p->next; if (!addr_last) addr_new = p; else addr_last->next = p; badp->local_part = badp->address; /* Needed for log line */ - post_process_one(badp, DEFER, LOG_MAIN|LOG_PANIC, DTYPE_ROUTER, 0); + post_process_one(badp, DEFER, LOG_MAIN|LOG_PANIC, EXIM_DTYPE_ROUTER, 0); continue; } } /* End of pfr handling */ @@ -6126,30 +6193,31 @@ if (process_recipients != RECIP_IGNORE) 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 @@ -6159,15 +6227,15 @@ if (process_recipients != RECIP_IGNORE) 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 @@ -6175,11 +6243,11 @@ if (process_recipients != RECIP_IGNORE) 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; @@ -6188,44 +6256,49 @@ if (process_recipients != RECIP_IGNORE) 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, 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""); } @@ -6275,7 +6348,7 @@ deliver_out_buffer = store_malloc(DELIVER_OUT_BUFFER_SIZE); . 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; @@ -6283,7 +6356,7 @@ while (addr_new) /* Loop until all addresses dealt with */ /* 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"); @@ -6322,7 +6395,7 @@ while (addr_new) /* Loop until all addresses dealt with */ addr->message = US"filter autoreply generated syntactically invalid recipient"; addr->prop.ignore_error = TRUE; - (void) post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0); + (void) post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); continue; /* with the next new address */ } @@ -6345,10 +6418,8 @@ while (addr_new) /* Loop until all addresses dealt with */ 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))) { @@ -6389,7 +6460,7 @@ while (addr_new) /* Loop until all addresses dealt with */ { addr->basic_errno = ERRNO_FORBIDFILE; addr->message = US"delivery to file forbidden"; - (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0); + (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); continue; /* with the next new address */ } } @@ -6399,7 +6470,7 @@ while (addr_new) /* Loop until all addresses dealt with */ { addr->basic_errno = ERRNO_FORBIDPIPE; addr->message = US"delivery to pipe forbidden"; - (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0); + (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); continue; /* with the next new address */ } } @@ -6407,7 +6478,7 @@ while (addr_new) /* Loop until all addresses dealt with */ { addr->basic_errno = ERRNO_FORBIDREPLY; addr->message = US"autoreply forbidden"; - (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0); + (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); continue; /* with the next new address */ } @@ -6418,7 +6489,7 @@ while (addr_new) /* Loop until all addresses dealt with */ if (addr->basic_errno == ERRNO_BADTRANSPORT) { - (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0); + (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); continue; } @@ -6430,7 +6501,7 @@ while (addr_new) /* Loop until all addresses dealt with */ { uschar *save = addr->transport->name; addr->transport->name = US"**bypassed**"; - (void)post_process_one(addr, OK, LOG_MAIN, DTYPE_TRANSPORT, '='); + (void)post_process_one(addr, OK, LOG_MAIN, EXIM_DTYPE_TRANSPORT, '='); addr->transport->name = save; continue; /* with the next new address */ } @@ -6453,7 +6524,7 @@ while (addr_new) /* Loop until all addresses dealt with */ { addr->message = US"cannot check percent_hack_domains"; addr->basic_errno = ERRNO_LISTDEFER; - (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_NONE, 0); + (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_NONE, 0); continue; } @@ -6477,7 +6548,7 @@ while (addr_new) /* Loop until all addresses dealt with */ addr->message = US"domain is held"; addr->basic_errno = ERRNO_HELD; } - (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_NONE, 0); + (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_NONE, 0); continue; } @@ -6539,13 +6610,21 @@ while (addr_new) /* Loop until all addresses dealt with */ 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) { @@ -6554,7 +6633,11 @@ while (addr_new) /* Loop until all addresses dealt with */ 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 retry record present but expired\n"); address_retry_record = NULL; /* Ignore if too old */ + } } } else @@ -6563,9 +6646,18 @@ while (addr_new) /* Loop until all addresses dealt with */ 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 @@ -6586,7 +6678,10 @@ while (addr_new) /* Loop until all addresses dealt with */ { addr->message = US"reusing SMTP connection skips previous routing defer"; addr->basic_errno = ERRNO_RRETRY; - (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0); + (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 @@ -6621,7 +6716,7 @@ while (addr_new) /* Loop until all addresses dealt with */ 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 @@ -6639,7 +6734,17 @@ while (addr_new) /* Loop until all addresses dealt with */ { addr->message = US"retry time not reached"; addr->basic_errno = ERRNO_RRETRY; - (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0); + (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 @@ -6665,7 +6770,7 @@ while (addr_new) /* Loop until all addresses dealt with */ 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) @@ -6681,7 +6786,7 @@ while (addr_new) /* Loop until all addresses dealt with */ { addr->basic_errno = ERRNO_LISTDEFER; addr->message = US"queue_domains lookup deferred"; - (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0); + (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); } else { @@ -6692,7 +6797,7 @@ while (addr_new) /* Loop until all addresses dealt with */ { addr->basic_errno = ERRNO_QUEUE_DOMAIN; addr->message = US"domain is in queue_domains"; - (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_ROUTER, 0); + (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); } } @@ -6722,8 +6827,8 @@ while (addr_new) /* Loop until all addresses dealt with */ &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 @@ -6757,7 +6862,7 @@ while (addr_new) /* Loop until all addresses dealt with */ if (rc != OK) { - (void)post_process_one(addr, rc, LOG_MAIN, DTYPE_ROUTER, 0); + (void)post_process_one(addr, rc, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); continue; /* route next address */ } @@ -6835,22 +6940,21 @@ while (addr_new) /* Loop until all addresses dealt with */ 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); } @@ -6959,18 +7063,18 @@ remember them for all subsequent deliveries. This can be delayed till later if 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. @@ -7001,7 +7105,7 @@ if (addr_local || addr_remote) 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) @@ -7046,13 +7150,13 @@ if (addr_local) 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; @@ -7060,7 +7164,7 @@ if (queue_run_local) addr->next = NULL; addr->basic_errno = ERRNO_LOCAL_ONLY; addr->message = US"remote deliveries suppressed"; - (void)post_process_one(addr, DEFER, LOG_MAIN, DTYPE_TRANSPORT, 0); + (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_TRANSPORT, 0); } /* Handle remote deliveries */ @@ -7073,7 +7177,7 @@ if (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 @@ -7104,7 +7208,7 @@ if (addr_remote) if (remote_sort_domains) sort_remote_deliveries(); do_remote_deliveries(TRUE); } - disable_logging = FALSE; + f.disable_logging = FALSE; } @@ -7132,8 +7236,8 @@ if (mua_wrapper) { 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); @@ -7183,44 +7287,43 @@ retry cutoff time has expired for all alternative destinations. Bypass the 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 @@ -7240,20 +7343,20 @@ if (addr_senddsn) 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()); @@ -7262,11 +7365,14 @@ if (addr_senddsn) 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 \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" @@ -7274,17 +7380,14 @@ if (addr_senddsn) "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" @@ -7302,24 +7405,18 @@ if (addr_senddsn) } 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); @@ -7330,8 +7427,10 @@ if (addr_senddsn) /* 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); @@ -7362,9 +7461,9 @@ while (addr_failed) /* 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); @@ -7400,19 +7499,24 @@ while (addr_failed) 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); @@ -7446,8 +7550,8 @@ while (addr_failed) 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; @@ -7485,10 +7589,10 @@ while (addr_failed) 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 ", @@ -7496,20 +7600,21 @@ while (addr_failed) ? 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); @@ -7525,46 +7630,46 @@ while (addr_failed) /* 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 @@ -7575,12 +7680,12 @@ wording. */ 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. */ @@ -7601,7 +7706,7 @@ wording. */ } } - 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. */ @@ -7620,9 +7725,9 @@ wording. */ 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" : ""); @@ -7633,24 +7738,24 @@ wording. */ /* 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); @@ -7662,19 +7767,19 @@ wording. */ 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); @@ -7684,40 +7789,41 @@ wording. */ /* 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; %s\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; %s\n", s); if ((s = addr->message) && *s) - fprintf(f, "X-Exim-Diagnostic: X-str; %s\n", s); + fprintf(fp, "X-Exim-Diagnostic: X-str; %s\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 @@ -7738,7 +7844,7 @@ wording. */ 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; @@ -7776,44 +7882,45 @@ wording. */ 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); + testharness_pause_ms(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 @@ -7830,7 +7937,7 @@ wording. */ 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); @@ -7859,7 +7966,7 @@ wording. */ } } -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 */ @@ -7912,13 +8019,12 @@ if (!addr_defer) /* Log the end of this message, with queue time if requested. */ if (LOGGING(queue_time_overall)) - log_write(0, LOG_MAIN, "Completed QT=%s", - string_timesince(&received_time)); + log_write(0, LOG_MAIN, "Completed QT=%s", string_timesince(&received_time)); else 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); @@ -7937,6 +8043,8 @@ the parent's domain. 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. @@ -7954,18 +8062,17 @@ was set just to keep the message on the spool, so there is nothing to do here. 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) { @@ -8036,10 +8143,10 @@ else if (addr_defer != (address_item *)(+1)) 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 @@ -8053,21 +8160,7 @@ else if (addr_defer != (address_item *)(+1)) 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 */ @@ -8088,7 +8181,8 @@ else if (addr_defer != (address_item *)(+1)) 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); } @@ -8126,6 +8220,7 @@ else if (addr_defer != (address_item *)(+1)) 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()); @@ -8183,13 +8278,9 @@ else if (addr_defer != (address_item *)(+1)) /* 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); @@ -8232,20 +8323,16 @@ else if (addr_defer != (address_item *)(+1)) } 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); } @@ -8262,6 +8349,7 @@ else if (addr_defer != (address_item *)(+1)) 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); @@ -8289,9 +8377,9 @@ else if (addr_defer != (address_item *)(+1)) /* 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; } @@ -8302,9 +8390,9 @@ else if (addr_defer != (address_item *)(+1)) 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: "); @@ -8345,9 +8433,9 @@ else if (addr_defer != (address_item *)(+1)) 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); } @@ -8382,7 +8470,7 @@ if (remove_journal) /* 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 } @@ -8394,6 +8482,9 @@ to try delivery. */ (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 @@ -8409,41 +8500,13 @@ return final_yield; void -deliver_init(void) +tcp_init(void) { -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); +#ifdef EXIM_TFO_PROBE +tfo_probe(); +#else +f.tcp_fastopen_ok = 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); } @@ -8453,17 +8516,17 @@ deliver_get_sender_address (uschar * id) 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; @@ -8499,16 +8562,18 @@ delivery_re_exec(int exec_type) { 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; @@ -8523,13 +8588,13 @@ if (cutthrough.fd >= 0 && cutthrough.callout_hold_only) else if (pid == 0) /* child: fork again to totally disconnect */ { - close(pfd[1]); - if ((pid = fork())) - _exit(pid ? EXIT_FAILURE : EXIT_SUCCESS); - smtp_proxy_tls(big_buffer, big_buffer_size, pfd[0], 5*60); - exim_exit(0); + testharness_pause_ms(100); /* let parent debug out */ + /* does not return */ + smtp_proxy_tls(cutthrough.cctx.tls_ctx, 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 */ @@ -8547,6 +8612,7 @@ else } 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), @@ -8556,6 +8622,7 @@ fail: Note: this must be _exit(), not exit(). */ _exit(EX_EXECFAILED); +#endif } /* vi: aw ai sw=2