diff options
Diffstat (limited to '')
-rw-r--r-- | src/deliver.c | 8693 |
1 files changed, 8693 insertions, 0 deletions
diff --git a/src/deliver.c b/src/deliver.c new file mode 100644 index 0000000..664d004 --- /dev/null +++ b/src/deliver.c @@ -0,0 +1,8693 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) University of Cambridge 1995 - 2018 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* The main code for delivering a message. */ + + +#include "exim.h" +#include "transports/smtp.h" +#include <sys/uio.h> +#include <assert.h> + + +/* Data block for keeping track of subprocesses for parallel remote +delivery. */ + +typedef struct pardata { + address_item *addrlist; /* chain of addresses */ + address_item *addr; /* next address data expected for */ + pid_t pid; /* subprocess pid */ + int fd; /* pipe fd for getting result from subprocess */ + int transport_count; /* returned transport count value */ + BOOL done; /* no more data needed */ + uschar *msg; /* error message */ + uschar *return_path; /* return_path for these addresses */ +} pardata; + +/* Values for the process_recipients variable */ + +enum { RECIP_ACCEPT, RECIP_IGNORE, RECIP_DEFER, + RECIP_FAIL, RECIP_FAIL_FILTER, RECIP_FAIL_TIMEOUT, + RECIP_FAIL_LOOP}; + +/* Mutually recursive functions for marking addresses done. */ + +static void child_done(address_item *, uschar *); +static void address_done(address_item *, uschar *); + +/* Table for turning base-62 numbers into binary */ + +static uschar tab62[] = + {0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, /* 0-9 */ + 0,10,11,12,13,14,15,16,17,18,19,20, /* A-K */ + 21,22,23,24,25,26,27,28,29,30,31,32, /* L-W */ + 33,34,35, 0, 0, 0, 0, 0, /* X-Z */ + 0,36,37,38,39,40,41,42,43,44,45,46, /* a-k */ + 47,48,49,50,51,52,53,54,55,56,57,58, /* l-w */ + 59,60,61}; /* x-z */ + + +/************************************************* +* Local static variables * +*************************************************/ + +/* addr_duplicate is global because it needs to be seen from the Envelope-To +writing code. */ + +static address_item *addr_defer = NULL; +static address_item *addr_failed = NULL; +static address_item *addr_fallback = NULL; +static address_item *addr_local = NULL; +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; +static BOOL update_spool; +static BOOL remove_journal; +static int parcount = 0; +static pardata *parlist = NULL; +static int return_count; +static uschar *frozen_info = US""; +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) +{ + void * next = buffer; + void * end = buffer + 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 - buffer; + next += got; + } + + return len; +} + + +/************************************************* +* Make a new address item * +*************************************************/ + +/* This function gets the store and initializes with default values. The +transport_return value defaults to DEFER, so that any unexpected failure to +deliver does not wipe out the message. The default unique string is set to a +copy of the address, so that its domain can be lowercased. + +Argument: + address the RFC822 address string + copy force a copy of the address + +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)); +*addr = address_defaults; +if (copy) address = string_copy(address); +addr->address = address; +addr->unique = string_copy(address); +return addr; +} + + + + +/************************************************* +* Set expansion values for an address * +*************************************************/ + +/* Certain expansion variables are valid only when handling an address or +address list. This function sets them up or clears the values, according to its +argument. + +Arguments: + addr the address in question, or NULL to clear values +Returns: nothing +*/ + +void +deliver_set_expansions(address_item *addr) +{ +if (!addr) + { + const uschar ***p = address_expansions; + while (*p) **p++ = NULL; + return; + } + +/* Exactly what gets set depends on whether there is one or more addresses, and +what they contain. These first ones are always set, taking their values from +the first address. */ + +if (!addr->host_list) + { + deliver_host = deliver_host_address = US""; + deliver_host_port = 0; + } +else + { + deliver_host = addr->host_list->name; + deliver_host_address = addr->host_list->address; + deliver_host_port = addr->host_list->port; + } + +deliver_recipients = addr; +deliver_address_data = addr->prop.address_data; +deliver_domain_data = addr->prop.domain_data; +deliver_localpart_data = addr->prop.localpart_data; + +/* These may be unset for multiple addresses */ + +deliver_domain = addr->domain; +self_hostname = addr->self_hostname; + +#ifdef EXPERIMENTAL_BRIGHTMAIL +bmi_deliver = 1; /* deliver by default */ +bmi_alt_location = NULL; +bmi_base64_verdict = NULL; +bmi_base64_tracker_verdict = NULL; +#endif + +/* If there's only one address we can set everything. */ + +if (!addr->next) + { + address_item *addr_orig; + + deliver_localpart = addr->local_part; + deliver_localpart_prefix = addr->prefix; + deliver_localpart_suffix = addr->suffix; + + for (addr_orig = addr; addr_orig->parent; addr_orig = addr_orig->parent) ; + deliver_domain_orig = addr_orig->domain; + + /* Re-instate any prefix and suffix in the original local part. In all + normal cases, the address will have a router associated with it, and we can + choose the caseful or caseless version accordingly. However, when a system + filter sets up a pipe, file, or autoreply delivery, no router is involved. + In this case, though, there won't be any prefix or suffix to worry about. */ + + deliver_localpart_orig = !addr_orig->router + ? addr_orig->local_part + : addr_orig->router->caseful_local_part + ? addr_orig->cc_local_part + : addr_orig->lc_local_part; + + /* If there's a parent, make its domain and local part available, and if + delivering to a pipe or file, or sending an autoreply, get the local + part from the parent. For pipes and files, put the pipe or file string + into address_pipe and address_file. */ + + if (addr->parent) + { + deliver_domain_parent = addr->parent->domain; + deliver_localpart_parent = !addr->parent->router + ? addr->parent->local_part + : addr->parent->router->caseful_local_part + ? addr->parent->cc_local_part + : addr->parent->lc_local_part; + + /* File deliveries have their own flag because they need to be picked out + as special more often. */ + + if (testflag(addr, af_pfr)) + { + if (testflag(addr, af_file)) address_file = addr->local_part; + else if (deliver_localpart[0] == '|') address_pipe = addr->local_part; + deliver_localpart = addr->parent->local_part; + deliver_localpart_prefix = addr->parent->prefix; + deliver_localpart_suffix = addr->parent->suffix; + } + } + +#ifdef EXPERIMENTAL_BRIGHTMAIL + /* Set expansion variables related to Brightmail AntiSpam */ + bmi_base64_verdict = bmi_get_base64_verdict(deliver_localpart_orig, deliver_domain_orig); + bmi_base64_tracker_verdict = bmi_get_base64_tracker_verdict(bmi_base64_verdict); + /* get message delivery status (0 - don't deliver | 1 - deliver) */ + bmi_deliver = bmi_get_delivery_status(bmi_base64_verdict); + /* if message is to be delivered, get eventual alternate location */ + if (bmi_deliver == 1) + bmi_alt_location = bmi_get_alt_location(bmi_base64_verdict); +#endif + + } + +/* For multiple addresses, don't set local part, and leave the domain and +self_hostname set only if it is the same for all of them. It is possible to +have multiple pipe and file addresses, but only when all addresses have routed +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) + { + if (deliver_domain && Ustrcmp(deliver_domain, addr2->domain) != 0) + deliver_domain = NULL; + if ( self_hostname + && ( !addr2->self_hostname + || Ustrcmp(self_hostname, addr2->self_hostname) != 0 + ) ) + self_hostname = NULL; + if (!deliver_domain && !self_hostname) break; + } + } +} + + + + +/************************************************* +* Open a msglog file * +*************************************************/ + +/* This function is used both for normal message logs, and for files in the +msglog directory that are used to catch output from pipes. Try to create the +directory if it does not exist. From release 4.21, normal message logs should +be created when the message is received. + +Called from deliver_message(), can be operating as root. + +Argument: + filename the file name + mode the mode required + error used for saying what failed + +Returns: a file descriptor, or -1 (with errno set) +*/ + +static int +open_msglog_file(uschar *filename, int mode, uschar **error) +{ +int fd, i; + +for (i = 2; i > 0; i--) + { + fd = Uopen(filename, +#ifdef O_CLOEXEC + O_CLOEXEC | +#endif +#ifdef O_NOFOLLOW + O_NOFOLLOW | +#endif + O_WRONLY|O_APPEND|O_CREAT, mode); + if (fd >= 0) + { + /* Set the close-on-exec flag and change the owner to the exim uid/gid (this + function is called as root). Double check the mode, because the group setting + doesn't always get set automatically. */ + +#ifndef O_CLOEXEC + (void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); +#endif + if (fchown(fd, exim_uid, exim_gid) < 0) + { + *error = US"chown"; + return -1; + } + if (fchmod(fd, mode) < 0) + { + *error = US"chmod"; + return -1; + } + return fd; + } + if (errno != ENOENT) + break; + + (void)directory_make(spool_directory, + spool_sname(US"msglog", message_subdir), + MSGLOG_DIRECTORY_MODE, TRUE); + } + +*error = US"create"; +return -1; +} + + + + +/************************************************* +* Write to msglog if required * +*************************************************/ + +/* Write to the message log, if configured. This function may also be called +from transports. + +Arguments: + format a string format + +Returns: nothing +*/ + +void +deliver_msglog(const char *format, ...) +{ +va_list ap; +if (!message_logs) return; +va_start(ap, format); +vfprintf(message_log, format, ap); +fflush(message_log); +va_end(ap); +} + + + + +/************************************************* +* Replicate status for batch * +*************************************************/ + +/* When a transport handles a batch of addresses, it may treat them +individually, or it may just put the status in the first one, and return FALSE, +requesting that the status be copied to all the others externally. This is the +replication function. As well as the status, it copies the transport pointer, +which may have changed if appendfile passed the addresses on to a different +transport. + +Argument: pointer to the first address in a chain +Returns: nothing +*/ + +static void +replicate_status(address_item *addr) +{ +address_item *addr2; +for (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->special_action = addr->special_action; + addr2->message = addr->message; + addr2->user_message = addr->user_message; + } +} + + + +/************************************************* +* Compare lists of hosts * +*************************************************/ + +/* This function is given two pointers to chains of host items, and it yields +TRUE if the lists refer to the same hosts in the same order, except that + +(1) Multiple hosts with the same non-negative MX values are permitted to appear + in different orders. Round-robinning nameservers can cause this to happen. + +(2) Multiple hosts with the same negative MX values less than MX_NONE are also + permitted to appear in different orders. This is caused by randomizing + hosts lists. + +This enables Exim to use a single SMTP transaction for sending to two entirely +different domains that happen to end up pointing at the same hosts. + +Arguments: + one points to the first host list + two points to the second host list + +Returns: TRUE if the lists refer to the same host set +*/ + +static BOOL +same_hosts(host_item *one, host_item *two) +{ +while (one && two) + { + if (Ustrcmp(one->name, two->name) != 0) + { + int mx = one->mx; + host_item *end_one = one; + host_item *end_two = two; + + /* Batch up only if there was no MX and the list was not randomized */ + + if (mx == MX_NONE) return FALSE; + + /* Find the ends of the shortest sequence of identical MX values */ + + while ( end_one->next && end_one->next->mx == mx + && end_two->next && end_two->next->mx == mx) + { + end_one = end_one->next; + end_two = end_two->next; + } + + /* If there aren't any duplicates, there's no match. */ + + if (end_one == one) return FALSE; + + /* For each host in the 'one' sequence, check that it appears in the 'two' + sequence, returning FALSE if not. */ + + for (;;) + { + host_item *hi; + for (hi = two; hi != end_two->next; hi = hi->next) + if (Ustrcmp(one->name, hi->name) == 0) break; + if (hi == end_two->next) return FALSE; + if (one == end_one) break; + one = one->next; + } + + /* All the hosts in the 'one' sequence were found in the 'two' sequence. + Ensure both are pointing at the last host, and carry on as for equality. */ + + two = end_two; + } + + /* if the names matched but ports do not, mismatch */ + else if (one->port != two->port) + return FALSE; + + /* Hosts matched */ + + one = one->next; + two = two->next; + } + +/* True if both are NULL */ + +return (one == two); +} + + + +/************************************************* +* Compare header lines * +*************************************************/ + +/* This function is given two pointers to chains of header items, and it yields +TRUE if they are the same header texts in the same order. + +Arguments: + one points to the first header list + two points to the second header list + +Returns: TRUE if the lists refer to the same header set +*/ + +static BOOL +same_headers(header_line *one, header_line *two) +{ +for (;; one = one->next, two = two->next) + { + if (one == two) return TRUE; /* Includes the case where both NULL */ + if (!one || !two) return FALSE; + if (Ustrcmp(one->text, two->text) != 0) return FALSE; + } +} + + + +/************************************************* +* Compare string settings * +*************************************************/ + +/* This function is given two pointers to strings, and it returns +TRUE if they are the same pointer, or if the two strings are the same. + +Arguments: + one points to the first string + two points to the second string + +Returns: TRUE or FALSE +*/ + +static BOOL +same_strings(uschar *one, uschar *two) +{ +if (one == two) return TRUE; /* Includes the case where both NULL */ +if (!one || !two) return FALSE; +return (Ustrcmp(one, two) == 0); +} + + + +/************************************************* +* Compare uid/gid for addresses * +*************************************************/ + +/* This function is given a transport and two addresses. It yields TRUE if the +uid/gid/initgroups settings for the two addresses are going to be the same when +they are delivered. + +Arguments: + tp the transort + addr1 the first address + addr2 the second address + +Returns: TRUE or FALSE +*/ + +static BOOL +same_ugid(transport_instance *tp, address_item *addr1, address_item *addr2) +{ +if ( !tp->uid_set && !tp->expand_uid + && !tp->deliver_as_creator + && ( testflag(addr1, af_uid_set) != testflag(addr2, af_gid_set) + || ( testflag(addr1, af_uid_set) + && ( addr1->uid != addr2->uid + || testflag(addr1, af_initgroups) != testflag(addr2, af_initgroups) + ) ) ) ) + return FALSE; + +if ( !tp->gid_set && !tp->expand_gid + && ( testflag(addr1, af_gid_set) != testflag(addr2, af_gid_set) + || ( testflag(addr1, af_gid_set) + && addr1->gid != addr2->gid + ) ) ) + return FALSE; + +return TRUE; +} + + + + +/************************************************* +* Record that an address is complete * +*************************************************/ + +/* This function records that an address is complete. This is straightforward +for most addresses, where the unique address is just the full address with the +domain lower cased. For homonyms (addresses that are the same as one of their +ancestors) their are complications. Their unique addresses have \x\ prepended +(where x = 0, 1, 2...), so that de-duplication works correctly for siblings and +cousins. + +Exim used to record the unique addresses of homonyms as "complete". This, +however, fails when the pattern of redirection varies over time (e.g. if taking +unseen copies at only some times of day) because the prepended numbers may vary +from one delivery run to the next. This problem is solved by never recording +prepended unique addresses as complete. Instead, when a homonymic address has +actually been delivered via a transport, we record its basic unique address +followed by the name of the transport. This is checked in subsequent delivery +runs whenever an address is routed to a transport. + +If the completed address is a top-level one (has no parent, which means it +cannot be homonymic) we also add the original address to the non-recipients +tree, so that it gets recorded in the spool file and therefore appears as +"done" in any spool listings. The original address may differ from the unique +address in the case of the domain. + +Finally, this function scans the list of duplicates, marks as done any that +match this address, and calls child_done() for their ancestors. + +Arguments: + addr address item that has been completed + now current time as a string + +Returns: nothing +*/ + +static void +address_done(address_item *addr, uschar *now) +{ +address_item *dup; + +update_spool = TRUE; /* Ensure spool gets updated */ + +/* Top-level address */ + +if (!addr->parent) + { + tree_add_nonrecipient(addr->unique); + tree_add_nonrecipient(addr->address); + } + +/* Homonymous child address */ + +else if (testflag(addr, af_homonym)) + { + if (addr->transport) + tree_add_nonrecipient( + string_sprintf("%s/%s", addr->unique + 3, addr->transport->name)); + } + +/* Non-homonymous child address */ + +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) + if (Ustrcmp(addr->unique, dup->unique) == 0) + { + tree_add_nonrecipient(dup->unique); + child_done(dup, now); + } +} + + + + +/************************************************* +* Decrease counts in parents and mark done * +*************************************************/ + +/* This function is called when an address is complete. If there is a parent +address, its count of children is decremented. If there are still other +children outstanding, the function exits. Otherwise, if the count has become +zero, address_done() is called to mark the parent and its duplicates complete. +Then loop for any earlier ancestors. + +Arguments: + addr points to the completed address item + now the current time as a string, for writing to the message log + +Returns: nothing +*/ + +static void +child_done(address_item *addr, uschar *now) +{ +address_item *aa; +while (addr->parent) + { + addr = addr->parent; + if (--addr->child_count > 0) return; /* Incomplete parent */ + address_done(addr, now); + + /* Log the completion of all descendents only when there is no ancestor with + the same original address. */ + + for (aa = addr->parent; aa; aa = aa->parent) + if (Ustrcmp(aa->address, addr->address) == 0) break; + if (aa) continue; + + deliver_msglog("%s %s: children all complete\n", now, addr->address); + DEBUG(D_deliver) debug_printf("%s: children all complete\n", addr->address); + } +} + + + +/************************************************* +* Delivery logging support functions * +*************************************************/ + +/* The LOGGING() checks in d_log_interface() are complicated for backwards +compatibility. When outgoing interface logging was originally added, it was +conditional on just incoming_interface (which is off by default). The +outgoing_interface option is on by default to preserve this behaviour, but +you can enable incoming_interface and disable outgoing_interface to get I= +fields on incoming lines only. + +Arguments: + g The log line + addr The address to be logged + +Returns: New value for s +*/ + +static gstring * +d_log_interface(gstring * g) +{ +if (LOGGING(incoming_interface) && LOGGING(outgoing_interface) + && sending_ip_address) + { + g = string_fmt_append(g, " I=[%s]", sending_ip_address); + if (LOGGING(outgoing_port)) + g = string_fmt_append(g, "%d", sending_port); + } +return g; +} + + + +static gstring * +d_hostlog(gstring * g, address_item * addr) +{ +host_item * h = addr->host_used; + +g = string_append(g, 2, US" H=", h->name); + +if (LOGGING(dnssec) && h->dnssec == DS_YES) + g = string_catn(g, US" DS", 3); + +g = string_append(g, 3, US" [", h->address, US"]"); + +if (LOGGING(outgoing_port)) + g = string_fmt_append(g, ":%d", h->port); + +#ifdef SUPPORT_SOCKS +if (LOGGING(proxy) && proxy_local_address) + { + g = string_append(g, 3, US" PRX=[", proxy_local_address, US"]"); + if (LOGGING(outgoing_port)) + g = string_fmt_append(g, ":%d", proxy_local_port); + } +#endif + +g = d_log_interface(g); + +if (testflag(addr, af_tcp_fastopen)) + g = string_catn(g, US" TFO*", testflag(addr, af_tcp_fastopen_data) ? 5 : 4); + +return g; +} + + + + + +#ifdef SUPPORT_TLS +static gstring * +d_tlslog(gstring * s, address_item * addr) +{ +if (LOGGING(tls_cipher) && addr->cipher) + s = string_append(s, 2, US" X=", addr->cipher); +if (LOGGING(tls_certificate_verified) && addr->cipher) + s = string_append(s, 2, US" CV=", + testflag(addr, af_cert_verified) + ? +#ifdef SUPPORT_DANE + testflag(addr, af_dane_verified) + ? "dane" + : +#endif + "yes" + : "no"); +if (LOGGING(tls_peerdn) && addr->peerdn) + s = string_append(s, 3, US" DN=\"", string_printing(addr->peerdn), US"\""); +return s; +} +#endif + + + + +#ifndef DISABLE_EVENT +uschar * +event_raise(uschar * action, const uschar * event, uschar * ev_data) +{ +uschar * s; +if (action) + { + DEBUG(D_deliver) + debug_printf("Event(%s): event_action=|%s| delivery_IP=%s\n", + event, + action, deliver_host_address); + + event_name = event; + event_data = ev_data; + + 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 ? transport_name : US"main", expand_string_message); + + event_name = event_data = NULL; + + /* If the expansion returns anything but an empty string, flag for + the caller to modify his normal processing + */ + if (s && *s) + { + DEBUG(D_deliver) + debug_printf("Event(%s): event_action returned \"%s\"\n", event, s); + return s; + } + } +return NULL; +} + +void +msg_event_raise(const uschar * event, const address_item * addr) +{ +const uschar * save_domain = deliver_domain; +uschar * save_local = deliver_localpart; +const uschar * save_host = deliver_host; +const uschar * save_address = deliver_host_address; +const int save_port = deliver_host_port; + +router_name = addr->router ? addr->router->name : NULL; +deliver_domain = addr->domain; +deliver_localpart = addr->local_part; +deliver_host = addr->host_used ? addr->host_used->name : 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; +deliver_host = save_host; +deliver_localpart = save_local; +deliver_domain = save_domain; +router_name = transport_name = NULL; +} +#endif /*DISABLE_EVENT*/ + + + +/******************************************************************************/ + + +/************************************************* +* Generate local prt for logging * +*************************************************/ + +/* This function is a subroutine for use in string_log_address() below. + +Arguments: + addr the address being logged + yield the current dynamic buffer pointer + +Returns: the new value of the buffer pointer +*/ + +static gstring * +string_get_localpart(address_item * addr, gstring * yield) +{ +uschar * s; + +s = addr->prefix; +if (testflag(addr, af_include_affixes) && s) + { +#ifdef SUPPORT_I18N + if (testflag(addr, af_utf8_downcvt)) + s = string_localpart_utf8_to_alabel(s, NULL); +#endif + yield = string_cat(yield, s); + } + +s = addr->local_part; +#ifdef SUPPORT_I18N +if (testflag(addr, af_utf8_downcvt)) + s = string_localpart_utf8_to_alabel(s, NULL); +#endif +yield = string_cat(yield, s); + +s = addr->suffix; +if (testflag(addr, af_include_affixes) && s) + { +#ifdef SUPPORT_I18N + if (testflag(addr, af_utf8_downcvt)) + s = string_localpart_utf8_to_alabel(s, NULL); +#endif + yield = string_cat(yield, s); + } + +return yield; +} + + +/************************************************* +* Generate log address list * +*************************************************/ + +/* This function generates a list consisting of an address and its parents, for +use in logging lines. For saved onetime aliased addresses, the onetime parent +field is used. If the address was delivered by a transport with rcpt_include_ +affixes set, the af_include_affixes bit will be set in the address. In that +case, we include the affixes here too. + +Arguments: + g points to growing-string struct + addr bottom (ultimate) address + all_parents if TRUE, include all parents + success TRUE for successful delivery + +Returns: a growable string in dynamic store +*/ + +static gstring * +string_log_address(gstring * g, + address_item *addr, BOOL all_parents, BOOL success) +{ +BOOL add_topaddr = TRUE; +address_item *topaddr; + +/* Find the ultimate parent */ + +for (topaddr = addr; topaddr->parent; topaddr = topaddr->parent) ; + +/* We start with just the local part for pipe, file, and reply deliveries, and +for successful local deliveries from routers that have the log_as_local flag +set. File deliveries from filters can be specified as non-absolute paths in +cases where the transport is going to complete the path. If there is an error +before this happens (expansion failure) the local part will not be updated, and +so won't necessarily look like a path. Add extra text for this case. */ + +if ( testflag(addr, af_pfr) + || ( success + && addr->router && addr->router->log_as_local + && addr->transport && addr->transport->info->local + ) ) + { + if (testflag(addr, af_file) && addr->local_part[0] != '/') + 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 +part and domain, use those fields. Some early failures can happen before the +splitting is done; in those cases use the original field. */ + +else + { + uschar * cmp = g->s + g->ptr; + + if (addr->local_part) + { + const uschar * s; + 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 + g = string_cat(g, s); + } + else + 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. */ + + string_from_gstring(g); /* ensure nul-terminated */ + if ( strcmpic(cmp, topaddr->address) == 0 + && Ustrncmp(cmp, topaddr->address, Ustrchr(cmp, '@') - cmp) == 0 + && !addr->onetime_parent + && (!all_parents || !addr->parent || addr->parent == topaddr) + ) + add_topaddr = FALSE; + } + +/* If all parents are requested, or this is a local pipe/file/reply, and +there is at least one intermediate parent, show it in brackets, and continue +with all of them if all are wanted. */ + +if ( (all_parents || testflag(addr, af_pfr)) + && addr->parent + && addr->parent != topaddr) + { + uschar *s = US" ("; + address_item *addr2; + for (addr2 = addr->parent; addr2 != topaddr; addr2 = addr2->parent) + { + g = string_catn(g, s, 2); + g = string_cat (g, addr2->address); + if (!all_parents) break; + s = US", "; + } + g = string_catn(g, US")", 1); + } + +/* Add the top address if it is required */ + +if (add_topaddr) + g = string_append(g, 3, + US" <", + addr->onetime_parent ? addr->onetime_parent : topaddr->address, + US">"); + +return g; +} + + + +void +timesince(struct timeval * diff, struct timeval * then) +{ +gettimeofday(diff, NULL); +diff->tv_sec -= then->tv_sec; +if ((diff->tv_usec -= then->tv_usec) < 0) + { + diff->tv_sec--; + diff->tv_usec += 1000*1000; + } +} + + + +uschar * +string_timediff(struct timeval * diff) +{ +static uschar buf[sizeof("0.000s")]; + +if (diff->tv_sec >= 5 || !LOGGING(millisec)) + return readconf_printtime((int)diff->tv_sec); + +sprintf(CS buf, "%u.%03us", (uint)diff->tv_sec, (uint)diff->tv_usec/1000); +return buf; +} + + +uschar * +string_timesince(struct timeval * then) +{ +struct timeval diff; + +timesince(&diff, then); +return string_timediff(&diff); +} + +/******************************************************************************/ + + + +/* If msg is NULL this is a delivery log and logchar is used. Otherwise +this is a nonstandard call; no two-character delivery flag is written +but sender-host and sender are prefixed and "msg" is inserted in the log line. + +Arguments: + flags passed to log_write() +*/ +void +delivery_log(int flags, address_item * addr, int logchar, uschar * msg) +{ +gstring * g; /* Used for a temporary, expanding buffer, for building log lines */ +void * reset_point; /* released afterwards. */ + +/* 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 +have a pointer to the host item that succeeded; local deliveries can have a +pointer to a single host item in their host list, for use by the transport. */ + +#ifndef DISABLE_EVENT + /* presume no successful remote delivery */ + lookup_dnssec_authenticated = NULL; +#endif + +g = reset_point = string_get(256); + +if (msg) + g = string_append(g, 2, host_and_ident(TRUE), US" "); +else + { + g->s[0] = logchar; g->ptr = 1; + g = string_catn(g, US"> ", 2); + } +g = string_log_address(g, addr, LOGGING(all_parents), TRUE); + +if (LOGGING(sender_on_delivery) || msg) + g = string_append(g, 3, US" F=<", +#ifdef SUPPORT_I18N + testflag(addr, af_utf8_downcvt) + ? string_address_utf8_to_alabel(sender_address, NULL) + : +#endif + sender_address, + US">"); + +if (*queue_name) + g = string_append(g, 2, US" Q=", queue_name); + +#ifdef EXPERIMENTAL_SRS +if(addr->prop.srs_sender) + g = string_append(g, 3, US" SRS=<", addr->prop.srs_sender, US">"); +#endif + +/* You might think that the return path must always be set for a successful +delivery; indeed, I did for some time, until this statement crashed. The case +when it is not set is for a delivery to /dev/null which is optimised by not +being run at all. */ + +if (used_return_path && LOGGING(return_path_on_delivery)) + g = string_append(g, 3, US" P=<", used_return_path, US">"); + +if (msg) + g = string_append(g, 2, US" ", msg); + +/* For a delivery from a system filter, there may not be a router */ +if (addr->router) + g = string_append(g, 2, US" R=", addr->router->name); + +g = string_append(g, 2, US" T=", addr->transport->name); + +if (LOGGING(delivery_size)) + g = string_fmt_append(g, " S=%d", transport_count); + +/* Local delivery */ + +if (addr->transport->info->local) + { + if (addr->host_list) + g = string_append(g, 2, US" H=", addr->host_list->name); + g = d_log_interface(g); + if (addr->shadow_message) + g = string_cat(g, addr->shadow_message); + } + +/* Remote delivery */ + +else + { + if (addr->host_used) + { + g = d_hostlog(g, addr); + if (continue_sequence > 1) + g = string_catn(g, US"*", 1); + +#ifndef DISABLE_EVENT + deliver_host_address = addr->host_used->address; + deliver_host_port = addr->host_used->port; + deliver_host = addr->host_used->name; + + /* DNS lookup status */ + lookup_dnssec_authenticated = addr->host_used->dnssec==DS_YES ? US"yes" + : addr->host_used->dnssec==DS_NO ? US"no" + : NULL; +#endif + } + +#ifdef SUPPORT_TLS + g = d_tlslog(g, addr); +#endif + + if (addr->authenticator) + { + g = string_append(g, 2, US" A=", addr->authenticator); + if (addr->auth_id) + { + g = string_append(g, 2, US":", addr->auth_id); + if (LOGGING(smtp_mailauth) && 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); +#ifdef EXPERIMENTAL_PIPE_CONNECT + if (testflag(addr, af_early_pipe)) + g = string_catn(g, US"*", 1); +#endif + } + +#ifndef DISABLE_PRDR + if (testflag(addr, af_prdr_used)) + g = string_catn(g, US" PRDR", 5); +#endif + + if (testflag(addr, af_chunking_used)) + g = string_catn(g, US" K", 2); + } + +/* confirmation message (SMTP (host_used) and LMTP (driver_name)) */ + +if ( LOGGING(smtp_confirmation) + && addr->message + && (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 */ + { + if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\'; /* quote \ and " */ + *p++ = ss[i]; + } + *p++ = '\"'; + *p = 0; + g = string_append(g, 2, US" C=", big_buffer); + } + +/* Time on queue and actual time taken to deliver */ + +if (LOGGING(queue_time)) + 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}; + g = string_append(g, 2, US" DT=", string_timediff(&diff)); + } + +/* string_cat() always leaves room for the terminator. Release the +store we used to build the line after writing it. */ + +log_write(0, flags, "%s", string_from_gstring(g)); + +#ifndef DISABLE_EVENT +if (!msg) msg_event_raise(US"msg:delivery", addr); +#endif + +store_reset(reset_point); +return; +} + + + +static void +deferral_log(address_item * addr, uschar * now, + int logflags, uschar * driver_name, uschar * driver_kind) +{ +gstring * g; +void * reset_point; + +/* Build up the line that is used for both the message log and the main +log. */ + +g = reset_point = string_get(256); + +/* Create the address string for logging. Must not do this earlier, because +an OK result may be changed to FAIL when a pipe returns text. */ + +g = string_log_address(g, addr, LOGGING(all_parents), FALSE); + +if (*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 +a null string and driver_kind contains "routing" without the leading +space, if all routing has been deferred. When a domain has been held, +so nothing has been done at all, both variables contain null strings. */ + +if (driver_name) + { + if (driver_kind[1] == 't' && addr->router) + 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) + g = string_append(g, 2, US" ", driver_kind); + +g = string_fmt_append(g, " defer (%d)", addr->basic_errno); + +if (addr->basic_errno > 0) + g = string_append(g, 2, US": ", + US strerror(addr->basic_errno)); + +if (addr->host_used) + { + 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; + g = string_fmt_append(g, ":%d", port == PORT_NONE ? 25 : port); + } + } + +if (addr->message) + g = string_append(g, 2, US": ", addr->message); + +(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 (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 +on queue run), logging is controlled by L_retry_defer. Note that this kind +of error number is negative, and all the retry ones are less than any +others. */ + + +log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags, + "== %s", g->s); + +store_reset(reset_point); +return; +} + + + +static void +failure_log(address_item * addr, uschar * driver_kind, uschar * now) +{ +void * reset_point; +gstring * g = reset_point = string_get(256); + +#ifndef DISABLE_EVENT +/* Message failures for which we will send a DSN get their event raised +later so avoid doing it here. */ + +if ( !addr->prop.ignore_error + && !(addr->dsn_flags & (rf_dsnflags & ~rf_notify_failure)) + ) + msg_event_raise(US"msg:fail:delivery", addr); +#endif + +/* Build up the log line for the message and main logs */ + +/* 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. */ + +g = string_log_address(g, addr, LOGGING(all_parents), FALSE); + +if (LOGGING(sender_on_delivery)) + g = string_append(g, 3, US" F=<", sender_address, US">"); + +if (*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)) + g = string_append(g, 3, US" P=<", used_return_path, US">"); + +if (addr->router) + g = string_append(g, 2, US" R=", addr->router->name); +if (addr->transport) + g = string_append(g, 2, US" T=", addr->transport->name); + +if (addr->host_used) + g = d_hostlog(g, addr); + +#ifdef SUPPORT_TLS +g = d_tlslog(g, addr); +#endif + +if (addr->basic_errno > 0) + g = string_append(g, 2, US": ", US strerror(addr->basic_errno)); + +if (addr->message) + g = string_append(g, 2, US": ", addr->message); + +(void) string_from_gstring(g); + +/* Do the logging. For the message log, "routing failed" for those cases, +just to make it clearer. */ + +if (driver_kind) + deliver_msglog("%s %s failed for %s\n", now, driver_kind, g->s); +else + deliver_msglog("%s %s\n", now, g->s); + +log_write(0, LOG_MAIN, "** %s", g->s); + +store_reset(reset_point); +return; +} + + + +/************************************************* +* Actions at the end of handling an address * +*************************************************/ + +/* This is a function for processing a single address when all that can be done +with it has been done. + +Arguments: + addr points to the address block + result the result of the delivery attempt + logflags flags for log_write() (LOG_MAIN and/or LOG_PANIC) + driver_type indicates which type of driver (transport, or router) was last + to process the address + logchar '=' or '-' for use when logging deliveries with => or -> + +Returns: nothing +*/ + +static void +post_process_one(address_item *addr, int result, int logflags, int driver_type, + int logchar) +{ +uschar *now = tod_stamp(tod_log); +uschar *driver_kind = NULL; +uschar *driver_name = NULL; + +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 == EXIM_DTYPE_TRANSPORT) + { + if (addr->transport) + { + driver_name = addr->transport->name; + driver_kind = US" transport"; + f.disable_logging = addr->transport->disable_logging; + } + else driver_kind = US"transporting"; + } +else if (driver_type == EXIM_DTYPE_ROUTER) + { + if (addr->router) + { + driver_name = addr->router->name; + driver_kind = US" router"; + f.disable_logging = addr->router->disable_logging; + } + else driver_kind = US"routing"; + } + +/* If there's an error message set, ensure that it contains only printing +characters - it should, but occasionally things slip in and this at least +stops the log format from getting wrecked. We also scan the message for an LDAP +expansion item that has a password setting, and flatten the password. This is a +fudge, but I don't know a cleaner way of doing this. (If the item is badly +malformed, it won't ever have gone near LDAP.) */ + +if (addr->message) + { + const uschar * s = string_printing(addr->message); + + /* deconst cast ok as string_printing known to have alloc'n'copied */ + addr->message = expand_hide_passwords(US s); + } + +/* If we used a transport that has one of the "return_output" options set, and +if it did in fact generate some output, then for return_output we treat the +message as failed if it was not already set that way, so that the output gets +returned to the sender, provided there is a sender to send it to. For +return_fail_output, do this only if the delivery failed. Otherwise we just +unlink the file, and remove the name so that if the delivery failed, we don't +try to send back an empty or unwanted file. The log_output options operate only +on a non-empty file. + +In any case, we close the message file, because we cannot afford to leave a +file-descriptor for one address while processing (maybe very many) others. */ + +if (addr->return_file >= 0 && addr->return_filename) + { + BOOL return_output = FALSE; + struct stat statbuf; + (void)EXIMfsync(addr->return_file); + + /* If there is no output, do nothing. */ + + if (fstat(addr->return_file, &statbuf) == 0 && statbuf.st_size > 0) + { + transport_instance *tb = addr->transport; + + /* Handle logging options */ + + if ( tb->log_output + || result == FAIL && tb->log_fail_output + || result == DEFER && tb->log_defer_output + ) + { + uschar *s; + FILE *f = Ufopen(addr->return_filename, "rb"); + if (!f) + log_write(0, LOG_MAIN|LOG_PANIC, "failed to open %s to log output " + "from %s transport: %s", addr->return_filename, tb->name, + strerror(errno)); + else + if ((s = US Ufgets(big_buffer, big_buffer_size, f))) + { + uschar *p = big_buffer + Ustrlen(big_buffer); + const uschar * sp; + while (p > big_buffer && isspace(p[-1])) p--; + *p = 0; + sp = string_printing(big_buffer); + log_write(0, LOG_MAIN, "<%s>: %s transport output: %s", + addr->address, tb->name, sp); + } + (void)fclose(f); + } + + /* Handle returning options, but only if there is an address to return + the text to. */ + + if (sender_address[0] != 0 || addr->prop.errors_address) + if (tb->return_output) + { + addr->transport_return = result = FAIL; + if (addr->basic_errno == 0 && !addr->message) + addr->message = US"return message generated"; + return_output = TRUE; + } + else + if (tb->return_fail_output && result == FAIL) return_output = TRUE; + } + + /* Get rid of the file unless it might be returned, but close it in + all cases. */ + + if (!return_output) + { + Uunlink(addr->return_filename); + addr->return_filename = NULL; + addr->return_file = -1; + } + + (void)close(addr->return_file); + } + +/* The success case happens only after delivery by a transport. */ + +if (result == OK) + { + addr->next = addr_succeed; + addr_succeed = addr; + + /* Call address_done() to ensure that we don't deliver to this address again, + and write appropriate things to the message log. If it is a child address, we + call child_done() to scan the ancestors and mark them complete if this is the + last child to complete. */ + + address_done(addr, now); + DEBUG(D_deliver) debug_printf("%s delivered\n", addr->address); + + if (!addr->parent) + deliver_msglog("%s %s: %s%s succeeded\n", now, addr->address, + driver_name, driver_kind); + else + { + deliver_msglog("%s %s <%s>: %s%s succeeded\n", now, addr->address, + addr->parent->address, driver_name, driver_kind); + child_done(addr, now); + } + + /* Certificates for logging (via events) */ +#ifdef SUPPORT_TLS + tls_out.ourcert = addr->ourcert; + addr->ourcert = NULL; + tls_out.peercert = addr->peercert; + addr->peercert = NULL; + + tls_out.cipher = addr->cipher; + tls_out.peerdn = addr->peerdn; + tls_out.ocsp = addr->ocsp; +# ifdef SUPPORT_DANE + tls_out.dane_verified = testflag(addr, af_dane_verified); +# endif +#endif + + delivery_log(LOG_MAIN, addr, logchar, NULL); + +#ifdef SUPPORT_TLS + tls_free_cert(&tls_out.ourcert); + tls_free_cert(&tls_out.peercert); + tls_out.cipher = NULL; + tls_out.peerdn = NULL; + tls_out.ocsp = OCSP_NOT_REQ; +# ifdef SUPPORT_DANE + tls_out.dane_verified = FALSE; +# endif +#endif + } + + +/* Soft failure, or local delivery process failed; freezing may be +requested. */ + +else if (result == DEFER || result == PANIC) + { + if (result == PANIC) logflags |= LOG_PANIC; + + /* This puts them on the chain in reverse order. Do not change this, because + the code for handling retries assumes that the one with the retry + information is last. */ + + addr->next = addr_defer; + addr_defer = addr; + + /* The only currently implemented special action is to freeze the + message. Logging of this is done later, just before the -H file is + updated. */ + + if (addr->special_action == SPECIAL_FREEZE) + { + f.deliver_freeze = TRUE; + deliver_frozen_at = time(NULL); + update_spool = TRUE; + } + + /* If doing a 2-stage queue run, we skip writing to either the message + log or the main log for SMTP defers. */ + + if (!f.queue_2stage || addr->basic_errno != 0) + deferral_log(addr, now, logflags, driver_name, driver_kind); + } + + +/* Hard failure. If there is an address to which an error message can be sent, +put this address on the failed list. If not, put it on the deferred list and +freeze the mail message for human attention. The latter action can also be +explicitly requested by a router or transport. */ + +else + { + /* If this is a delivery error, or a message for which no replies are + wanted, and the message's age is greater than ignore_bounce_errors_after, + force the af_ignore_error flag. This will cause the address to be discarded + later (with a log entry). */ + + if (!*sender_address && message_age >= ignore_bounce_errors_after) + addr->prop.ignore_error = TRUE; + + /* Freeze the message if requested, or if this is a bounce message (or other + message with null sender) and this address does not have its own errors + address. However, don't freeze if errors are being ignored. The actual code + to ignore occurs later, instead of sending a message. Logging of freezing + occurs later, just before writing the -H file. */ + + if ( !addr->prop.ignore_error + && ( addr->special_action == SPECIAL_FREEZE + || (sender_address[0] == 0 && !addr->prop.errors_address) + ) ) + { + frozen_info = addr->special_action == SPECIAL_FREEZE + ? US"" + : f.sender_local && !f.local_error_message + ? US" (message created with -f <>)" + : US" (delivery error message)"; + f.deliver_freeze = TRUE; + deliver_frozen_at = time(NULL); + update_spool = TRUE; + + /* The address is put on the defer rather than the failed queue, because + the message is being retained. */ + + addr->next = addr_defer; + addr_defer = addr; + } + + /* Don't put the address on the nonrecipients tree yet; wait until an + error message has been successfully sent. */ + + else + { + addr->next = addr_failed; + addr_failed = addr; + } + + failure_log(addr, driver_name ? NULL : driver_kind, now); + } + +/* Ensure logging is turned on again in all cases */ + +f.disable_logging = FALSE; +} + + + + +/************************************************* +* Address-independent error * +*************************************************/ + +/* This function is called when there's an error that is not dependent on a +particular address, such as an expansion string failure. It puts the error into +all the addresses in a batch, logs the incident on the main and panic logs, and +clears the expansions. It is mostly called from local_deliver(), but can be +called for a remote delivery via findugid(). + +Arguments: + logit TRUE if (MAIN+PANIC) logging required + addr the first of the chain of addresses + code the error code + format format string for error message, or NULL if already set in addr + ... arguments for the format + +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; + gstring * g; + + va_start(ap, format); + g = string_vformat(NULL, TRUE, CS format, ap); + va_end(ap); + addr->message = string_from_gstring(g); + } + +for (addr2 = addr->next; addr2; addr2 = addr2->next) + { + addr2->basic_errno = code; + addr2->message = addr->message; + } + +if (logit) log_write(0, LOG_MAIN|LOG_PANIC, "%s", addr->message); +deliver_set_expansions(NULL); +} + + + + +/************************************************* +* Check a "never users" list * +*************************************************/ + +/* This function is called to check whether a uid is on one of the two "never +users" lists. + +Arguments: + uid the uid to be checked + nusers the list to be scanned; the first item in the list is the count + +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; +return FALSE; +} + + + +/************************************************* +* Find uid and gid for a transport * +*************************************************/ + +/* This function is called for both local and remote deliveries, to find the +uid/gid under which to run the delivery. The values are taken preferentially +from the transport (either explicit or deliver_as_creator), then from the +address (i.e. the router), and if nothing is set, the exim uid/gid are used. If +the resulting uid is on the "never_users" or the "fixed_never_users" list, a +panic error is logged, and the function fails (which normally leads to delivery +deferral). + +Arguments: + addr the address (possibly a chain) + tp the transport + uidp pointer to uid field + gidp pointer to gid field + igfp pointer to the use_initgroups field + +Returns: FALSE if failed - error has been set in address(es) +*/ + +static BOOL +findugid(address_item *addr, transport_instance *tp, uid_t *uidp, gid_t *gidp, + BOOL *igfp) +{ +uschar *nuname; +BOOL gid_set = FALSE; + +/* Default initgroups flag comes from the transport */ + +*igfp = tp->initgroups; + +/* First see if there's a gid on the transport, either fixed or expandable. +The expanding function always logs failure itself. */ + +if (tp->gid_set) + { + *gidp = tp->gid; + gid_set = TRUE; + } +else if (tp->expand_gid) + { + if (!route_find_expanded_group(tp->expand_gid, tp->name, US"transport", gidp, + &(addr->message))) + { + common_error(FALSE, addr, ERRNO_GIDFAIL, NULL); + return FALSE; + } + gid_set = TRUE; + } + +/* If the transport did not set a group, see if the router did. */ + +if (!gid_set && testflag(addr, af_gid_set)) + { + *gidp = addr->gid; + gid_set = TRUE; + } + +/* Pick up a uid from the transport if one is set. */ + +if (tp->uid_set) *uidp = tp->uid; + +/* Otherwise, try for an expandable uid field. If it ends up as a numeric id, +it does not provide a passwd value from which a gid can be taken. */ + +else if (tp->expand_uid) + { + struct passwd *pw; + if (!route_find_expanded_user(tp->expand_uid, tp->name, US"transport", &pw, + uidp, &(addr->message))) + { + common_error(FALSE, addr, ERRNO_UIDFAIL, NULL); + return FALSE; + } + if (!gid_set && pw) + { + *gidp = pw->pw_gid; + gid_set = TRUE; + } + } + +/* If the transport doesn't set the uid, test the deliver_as_creator flag. */ + +else if (tp->deliver_as_creator) + { + *uidp = originator_uid; + if (!gid_set) + { + *gidp = originator_gid; + gid_set = TRUE; + } + } + +/* Otherwise see if the address specifies the uid and if so, take it and its +initgroups flag. */ + +else if (testflag(addr, af_uid_set)) + { + *uidp = addr->uid; + *igfp = testflag(addr, af_initgroups); + } + +/* Nothing has specified the uid - default to the Exim user, and group if the +gid is not set. */ + +else + { + *uidp = exim_uid; + if (!gid_set) + { + *gidp = exim_gid; + gid_set = TRUE; + } + } + +/* If no gid is set, it is a disaster. We default to the Exim gid only if +defaulting to the Exim uid. In other words, if the configuration has specified +a uid, it must also provide a gid. */ + +if (!gid_set) + { + common_error(TRUE, addr, ERRNO_GIDFAIL, US"User set without group for " + "%s transport", tp->name); + return FALSE; + } + +/* Check that the uid is not on the lists of banned uids that may not be used +for delivery processes. */ + +nuname = check_never_users(*uidp, never_users) + ? US"never_users" + : check_never_users(*uidp, fixed_never_users) + ? US"fixed_never_users" + : NULL; +if (nuname) + { + common_error(TRUE, addr, ERRNO_UIDFAIL, US"User %ld set for %s transport " + "is on the %s list", (long int)(*uidp), tp->name, nuname); + return FALSE; + } + +/* All is well */ + +return TRUE; +} + + + + +/************************************************* +* Check the size of a message for a transport * +*************************************************/ + +/* Checks that the message isn't too big for the selected transport. +This is called only when it is known that the limit is set. + +Arguments: + tp the transport + addr the (first) address being delivered + +Returns: OK + DEFER expansion failed or did not yield an integer + FAIL message too big +*/ + +int +check_message_size(transport_instance *tp, address_item *addr) +{ +int rc = OK; +int size_limit; + +deliver_set_expansions(addr); +size_limit = expand_string_integer(tp->message_size_limit, TRUE); +deliver_set_expansions(NULL); + +if (expand_string_message) + { + rc = DEFER; + addr->message = size_limit == -1 + ? string_sprintf("failed to expand message_size_limit " + "in %s transport: %s", tp->name, expand_string_message) + : string_sprintf("invalid message_size_limit " + "in %s transport: %s", tp->name, expand_string_message); + } +else if (size_limit > 0 && message_size > size_limit) + { + rc = FAIL; + addr->message = + string_sprintf("message is too big (transport limit = %d)", + size_limit); + } + +return rc; +} + + + +/************************************************* +* Transport-time check for a previous delivery * +*************************************************/ + +/* Check that this base address hasn't previously been delivered to its routed +transport. If it has been delivered, mark it done. The check is necessary at +delivery time in order to handle homonymic addresses correctly in cases where +the pattern of redirection changes between delivery attempts (so the unique +fields change). Non-homonymic previous delivery is detected earlier, at routing +time (which saves unnecessary routing). + +Arguments: + addr the address item + testing TRUE if testing wanted only, without side effects + +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", + addr->unique + (testflag(addr, af_homonym)? 3:0), addr->transport->name); + +if (tree_search(tree_nonrecipients, big_buffer) != 0) + { + DEBUG(D_deliver|D_route|D_transport) + debug_printf("%s was previously delivered (%s transport): discarded\n", + addr->address, addr->transport->name); + if (!testing) child_done(addr, tod_stamp(tod_log)); + return TRUE; + } + +return FALSE; +} + + + +/****************************************************** +* Check for a given header in a header string * +******************************************************/ + +/* This function is used when generating quota warnings. The configuration may +specify any header lines it likes in quota_warn_message. If certain of them are +missing, defaults are inserted, so we need to be able to test for the presence +of a given header. + +Arguments: + hdr the required header name + hstring the header string + +Returns: TRUE the header is in the string + FALSE the header is not in the string +*/ + +static BOOL +contains_header(uschar *hdr, uschar *hstring) +{ +int len = Ustrlen(hdr); +uschar *p = hstring; +while (*p != 0) + { + if (strncmpic(p, hdr, len) == 0) + { + p += len; + while (*p == ' ' || *p == '\t') p++; + if (*p == ':') return TRUE; + } + while (*p != 0 && *p != '\n') p++; + if (*p == '\n') p++; + } +return FALSE; +} + + + + +/************************************************* +* Perform a local delivery * +*************************************************/ + +/* Each local delivery is performed in a separate process which sets its +uid and gid as specified. This is a safer way than simply changing and +restoring using seteuid(); there is a body of opinion that seteuid() cannot be +used safely. From release 4, Exim no longer makes any use of it. Besides, not +all systems have seteuid(). + +If the uid/gid are specified in the transport_instance, they are used; the +transport initialization must ensure that either both or neither are set. +Otherwise, the values associated with the address are used. If neither are set, +it is a configuration error. + +The transport or the address may specify a home directory (transport over- +rides), and if they do, this is set as $home. If neither have set a working +directory, this value is used for that as well. Otherwise $home is left unset +and the cwd is set to "/" - a directory that should be accessible to all users. + +Using a separate process makes it more complicated to get error information +back. We use a pipe to pass the return code and also an error code and error +text string back to the parent process. + +Arguments: + addr points to an address block for this delivery; for "normal" local + deliveries this is the only address to be delivered, but for + pseudo-remote deliveries (e.g. by batch SMTP to a file or pipe) + a number of addresses can be handled simultaneously, and in this + case addr will point to a chain of addresses with the same + characteristics. + + shadowing TRUE if running a shadow transport; this causes output from pipes + to be ignored. + +Returns: nothing +*/ + +static void +deliver_local(address_item *addr, BOOL shadowing) +{ +BOOL use_initgroups; +uid_t uid; +gid_t gid; +int status, len, rc; +int pfd[2]; +pid_t pid; +uschar *working_directory; +address_item *addr2; +transport_instance *tp = addr->transport; + +/* Set up the return path from the errors or sender address. If the transport +has its own return path setting, expand it and replace the existing value. */ + +if(addr->prop.errors_address) + return_path = addr->prop.errors_address; +#ifdef EXPERIMENTAL_SRS +else if (addr->prop.srs_sender) + return_path = addr->prop.srs_sender; +#endif +else + return_path = sender_address; + +if (tp->return_path) + { + uschar *new_return_path = expand_string(tp->return_path); + if (!new_return_path) + { + if (!f.expand_string_forcedfail) + { + common_error(TRUE, addr, ERRNO_EXPANDFAIL, + US"Failed to expand return path \"%s\" in %s transport: %s", + tp->return_path, tp->name, expand_string_message); + return; + } + } + else return_path = new_return_path; + } + +/* For local deliveries, one at a time, the value used for logging can just be +set directly, once and for all. */ + +used_return_path = return_path; + +/* Sort out the uid, gid, and initgroups flag. If an error occurs, the message +gets put into the address(es), and the expansions are unset, so we can just +return. */ + +if (!findugid(addr, tp, &uid, &gid, &use_initgroups)) return; + +/* See if either the transport or the address specifies a home directory. A +home directory set in the address may already be expanded; a flag is set to +indicate that. In other cases we must expand it. */ + +if ( (deliver_home = tp->home_dir) /* Set in transport, or */ + || ( (deliver_home = addr->home_dir) /* Set in address and */ + && !testflag(addr, af_home_expanded) /* not expanded */ + ) ) + { + uschar *rawhome = deliver_home; + deliver_home = NULL; /* in case it contains $home */ + if (!(deliver_home = expand_string(rawhome))) + { + common_error(TRUE, addr, ERRNO_EXPANDFAIL, US"home directory \"%s\" failed " + "to expand for %s transport: %s", rawhome, tp->name, + expand_string_message); + return; + } + if (*deliver_home != '/') + { + common_error(TRUE, addr, ERRNO_NOTABSOLUTE, US"home directory path \"%s\" " + "is not absolute for %s transport", deliver_home, tp->name); + return; + } + } + +/* See if either the transport or the address specifies a current directory, +and if so, expand it. If nothing is set, use the home directory, unless it is +also unset in which case use "/", which is assumed to be a directory to which +all users have access. It is necessary to be in a visible directory for some +operating systems when running pipes, as some commands (e.g. "rm" under Solaris +2.5) require this. */ + +working_directory = tp->current_dir ? tp->current_dir : addr->current_dir; +if (working_directory) + { + uschar *raw = working_directory; + if (!(working_directory = expand_string(raw))) + { + common_error(TRUE, addr, ERRNO_EXPANDFAIL, US"current directory \"%s\" " + "failed to expand for %s transport: %s", raw, tp->name, + expand_string_message); + return; + } + if (*working_directory != '/') + { + common_error(TRUE, addr, ERRNO_NOTABSOLUTE, US"current directory path " + "\"%s\" is not absolute for %s transport", working_directory, tp->name); + return; + } + } +else working_directory = deliver_home ? deliver_home : US"/"; + +/* If one of the return_output flags is set on the transport, create and open a +file in the message log directory for the transport to write its output onto. +This is mainly used by pipe transports. The file needs to be unique to the +address. This feature is not available for shadow transports. */ + +if ( !shadowing + && ( tp->return_output || tp->return_fail_output + || tp->log_output || tp->log_fail_output || tp->log_defer_output + ) ) + { + uschar * error; + + addr->return_filename = + spool_fname(US"msglog", message_subdir, message_id, + string_sprintf("-%d-%d", getpid(), return_count++)); + + if ((addr->return_file = open_msglog_file(addr->return_filename, 0400, &error)) < 0) + { + common_error(TRUE, addr, errno, US"Unable to %s file for %s transport " + "to return message: %s", error, tp->name, strerror(errno)); + return; + } + } + +/* Create the pipe for inter-process communication. */ + +if (pipe(pfd) != 0) + { + common_error(TRUE, addr, ERRNO_PIPEFAIL, US"Creation of pipe failed: %s", + strerror(errno)); + return; + } + +/* Now fork the process to do the real work in the subprocess, but first +ensure that all cached resources are freed so that the subprocess starts with +a clean slate and doesn't interfere with the parent process. */ + +search_tidyup(); + +if ((pid = fork()) == 0) + { + BOOL replicate = TRUE; + + /* Prevent core dumps, as we don't want them in users' home directories. + HP-UX doesn't have RLIMIT_CORE; I don't know how to do this in that + system. Some experimental/developing systems (e.g. GNU/Hurd) may define + RLIMIT_CORE but not support it in setrlimit(). For such systems, do not + complain if the error is "not supported". + + There are two scenarios where changing the max limit has an effect. In one, + the user is using a .forward and invoking a command of their choice via pipe; + for these, we do need the max limit to be 0 unless the admin chooses to + permit an increased limit. In the other, the command is invoked directly by + the transport and is under administrator control, thus being able to raise + the limit aids in debugging. So there's no general always-right answer. + + Thus we inhibit core-dumps completely but let individual transports, while + still root, re-raise the limits back up to aid debugging. We make the + default be no core-dumps -- few enough people can use core dumps in + diagnosis that it's reasonable to make them something that has to be explicitly requested. + */ + +#ifdef RLIMIT_CORE + struct rlimit rl; + rl.rlim_cur = 0; + rl.rlim_max = 0; + if (setrlimit(RLIMIT_CORE, &rl) < 0) + { +# ifdef SETRLIMIT_NOT_SUPPORTED + if (errno != ENOSYS && errno != ENOTSUP) +# endif + log_write(0, LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_CORE) failed: %s", + strerror(errno)); + } +#endif + + /* Reset the random number generator, so different processes don't all + have the same sequence. */ + + random_seed = 0; + + /* If the transport has a setup entry, call this first, while still + privileged. (Appendfile uses this to expand quota, for example, while + able to read private files.) */ + + if (addr->transport->setup) + switch((addr->transport->setup)(addr->transport, addr, NULL, uid, gid, + &(addr->message))) + { + case DEFER: + addr->transport_return = DEFER; + goto PASS_BACK; + + case FAIL: + addr->transport_return = PANIC; + goto PASS_BACK; + } + + /* Ignore SIGINT and SIGTERM during delivery. Also ignore SIGUSR1, as + when the process becomes unprivileged, it won't be able to write to the + process log. SIGHUP is ignored throughout exim, except when it is being + run as a daemon. */ + + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGUSR1, SIG_IGN); + + /* Close the unwanted half of the pipe, and set close-on-exec for the other + half - for transports that exec things (e.g. pipe). Then set the required + gid/uid. */ + + (void)close(pfd[pipe_read]); + (void)fcntl(pfd[pipe_write], F_SETFD, fcntl(pfd[pipe_write], F_GETFD) | + FD_CLOEXEC); + exim_setugid(uid, gid, use_initgroups, + string_sprintf("local delivery to %s <%s> transport=%s", addr->local_part, + addr->address, addr->transport->name)); + + 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) + debug_printf("additional batched address: %s\n", batched->address); + } + + /* Set an appropriate working directory. */ + + if (Uchdir(working_directory) < 0) + { + addr->transport_return = DEFER; + addr->basic_errno = errno; + addr->message = string_sprintf("failed to chdir to %s", working_directory); + } + + /* If successful, call the transport */ + + else + { + BOOL ok = TRUE; + set_process_info("delivering %s to %s using %s", message_id, + addr->local_part, addr->transport->name); + + /* Setting this global in the subprocess means we need never clear it */ + transport_name = addr->transport->name; + + /* If a transport filter has been specified, set up its argument list. + Any errors will get put into the address, and FALSE yielded. */ + + if (addr->transport->filter_command) + { + ok = transport_set_up_command(&transport_filter_argv, + addr->transport->filter_command, + TRUE, PANIC, addr, US"transport filter", NULL); + transport_filter_timeout = addr->transport->filter_timeout; + } + else transport_filter_argv = NULL; + + if (ok) + { + debug_print_string(addr->transport->debug_string); + replicate = !(addr->transport->info->code)(addr->transport, addr); + } + } + + /* Pass the results back down the pipe. If necessary, first replicate the + status in the top address to the others in the batch. The label is the + subject of a goto when a call to the transport's setup function fails. We + pass the pointer to the transport back in case it got changed as a result of + file_format in appendfile. */ + + PASS_BACK: + + if (replicate) replicate_status(addr); + for (addr2 = addr; addr2; addr2 = addr2->next) + { + int i; + int local_part_length = Ustrlen(addr2->local_part); + uschar *s; + int ret; + + if( (ret = write(pfd[pipe_write], &addr2->transport_return, sizeof(int))) != sizeof(int) + || (ret = write(pfd[pipe_write], &transport_count, sizeof(transport_count))) != sizeof(transport_count) + || (ret = write(pfd[pipe_write], &addr2->flags, sizeof(addr2->flags))) != sizeof(addr2->flags) + || (ret = write(pfd[pipe_write], &addr2->basic_errno, sizeof(int))) != sizeof(int) + || (ret = write(pfd[pipe_write], &addr2->more_errno, sizeof(int))) != sizeof(int) + || (ret = write(pfd[pipe_write], &addr2->delivery_usec, sizeof(int))) != sizeof(int) + || (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 *) + + /* For a file delivery, pass back the local part, in case the original + was only part of the final delivery path. This gives more complete + logging. */ + + || (testflag(addr2, af_file) + && ( (ret = write(pfd[pipe_write], &local_part_length, sizeof(int))) != sizeof(int) + || (ret = write(pfd[pipe_write], addr2->local_part, local_part_length)) != local_part_length + ) + ) + ) + log_write(0, LOG_MAIN|LOG_PANIC, "Failed writing transport results to pipe: %s", + ret == -1 ? strerror(errno) : "short write"); + + /* Now any messages */ + + for (i = 0, s = addr2->message; i < 2; i++, s = addr2->user_message) + { + int message_length = s ? Ustrlen(s) + 1 : 0; + if( (ret = write(pfd[pipe_write], &message_length, sizeof(int))) != sizeof(int) + || message_length > 0 && (ret = write(pfd[pipe_write], s, message_length)) != message_length + ) + log_write(0, LOG_MAIN|LOG_PANIC, "Failed writing transport results to pipe: %s", + ret == -1 ? strerror(errno) : "short write"); + } + } + + /* OK, this process is now done. Free any cached resources that it opened, + and close the pipe we were writing down before exiting. */ + + (void)close(pfd[pipe_write]); + search_tidyup(); + exit(EXIT_SUCCESS); + } + +/* Back in the main process: panic if the fork did not succeed. This seems +better than returning an error - if forking is failing it is probably best +not to try other deliveries for this message. */ + +if (pid < 0) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Fork failed for local delivery to %s", + addr->address); + +/* Read the pipe to get the delivery status codes and error messages. Our copy +of the writing end must be closed first, as otherwise read() won't return zero +on an empty pipe. We check that a status exists for each address before +overwriting the address structure. If data is missing, the default DEFER status +will remain. Afterwards, close the reading end. */ + +(void)close(pfd[pipe_write]); + +for (addr2 = addr; addr2; addr2 = addr2->next) + { + if ((len = read(pfd[pipe_read], &status, sizeof(int))) > 0) + { + int i; + uschar **sptr; + + addr2->transport_return = status; + len = read(pfd[pipe_read], &transport_count, + sizeof(transport_count)); + 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->special_action, sizeof(int)); + len = read(pfd[pipe_read], &addr2->transport, + sizeof(transport_instance *)); + + if (testflag(addr2, af_file)) + { + int llen; + if ( read(pfd[pipe_read], &llen, sizeof(int)) != sizeof(int) + || llen > 64*4 /* limit from rfc 5821, times I18N factor */ + ) + { + log_write(0, LOG_MAIN|LOG_PANIC, "bad local_part length read" + " from delivery subprocess"); + break; + } + /* sanity-checked llen so disable the Coverity error */ + /* coverity[tainted_data] */ + if (read(pfd[pipe_read], big_buffer, llen) != llen) + { + log_write(0, LOG_MAIN|LOG_PANIC, "bad local_part read" + " from delivery subprocess"); + break; + } + big_buffer[llen] = 0; + addr2->local_part = string_copy(big_buffer); + } + + for (i = 0, sptr = &addr2->message; i < 2; i++, sptr = &addr2->user_message) + { + int message_length; + len = read(pfd[pipe_read], &message_length, sizeof(int)); + if (message_length > 0) + { + len = read(pfd[pipe_read], big_buffer, message_length); + big_buffer[big_buffer_size-1] = '\0'; /* guard byte */ + if (len > 0) *sptr = string_copy(big_buffer); + } + } + } + + else + { + log_write(0, LOG_MAIN|LOG_PANIC, "failed to read delivery status for %s " + "from delivery subprocess", addr2->unique); + break; + } + } + +(void)close(pfd[pipe_read]); + +/* Unless shadowing, write all successful addresses immediately to the journal +file, to ensure they are recorded asap. For homonymic addresses, use the base +address plus the transport name. Failure to write the journal is panic-worthy, +but don't stop, as it may prove possible subsequently to update the spool file +in order to record the delivery. */ + +if (!shadowing) + { + for (addr2 = addr; addr2; addr2 = addr2->next) + if (addr2->transport_return == OK) + { + if (testflag(addr2, af_homonym)) + sprintf(CS big_buffer, "%.500s/%s\n", addr2->unique + 3, tp->name); + else + sprintf(CS big_buffer, "%.500s\n", addr2->unique); + + /* In the test harness, wait just a bit to let the subprocess finish off + any debug output etc first. */ + + if (f.running_in_test_harness) millisleep(300); + + DEBUG(D_deliver) debug_printf("journalling %s", big_buffer); + len = Ustrlen(big_buffer); + if (write(journal_fd, big_buffer, len) != len) + log_write(0, LOG_MAIN|LOG_PANIC, "failed to update journal for %s: %s", + big_buffer, strerror(errno)); + } + + /* Ensure the journal file is pushed out to disk. */ + + if (EXIMfsync(journal_fd) < 0) + log_write(0, LOG_MAIN|LOG_PANIC, "failed to fsync journal: %s", + strerror(errno)); + } + +/* Wait for the process to finish. If it terminates with a non-zero code, +freeze the message (except for SIGTERM, SIGKILL and SIGQUIT), but leave the +status values of all the addresses as they are. Take care to handle the case +when the subprocess doesn't seem to exist. This has been seen on one system +when Exim was called from an MUA that set SIGCHLD to SIG_IGN. When that +happens, wait() doesn't recognize the termination of child processes. Exim now +resets SIGCHLD to SIG_DFL, but this code should still be robust. */ + +while ((rc = wait(&status)) != pid) + if (rc < 0 && errno == ECHILD) /* Process has vanished */ + { + log_write(0, LOG_MAIN, "%s transport process vanished unexpectedly", + addr->transport->driver_name); + status = 0; + break; + } + +if ((status & 0xffff) != 0) + { + int msb = (status >> 8) & 255; + int lsb = status & 255; + int code = (msb == 0)? (lsb & 0x7f) : msb; + if (msb != 0 || (code != SIGTERM && code != SIGKILL && code != SIGQUIT)) + addr->special_action = SPECIAL_FREEZE; + log_write(0, LOG_MAIN|LOG_PANIC, "%s transport process returned non-zero " + "status 0x%04x: %s %d", + addr->transport->driver_name, + status, + msb == 0 ? "terminated by signal" : "exit code", + code); + } + +/* If SPECIAL_WARN is set in the top address, send a warning message. */ + +if (addr->special_action == SPECIAL_WARN && addr->transport->warn_message) + { + int fd; + uschar *warn_message; + pid_t pid; + + DEBUG(D_deliver) debug_printf("Warning message requested by transport\n"); + + if (!(warn_message = expand_string(addr->transport->warn_message))) + log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand \"%s\" (warning " + "message for %s transport): %s", addr->transport->warn_message, + addr->transport->name, expand_string_message); + + else if ((pid = child_open_exim(&fd)) > 0) + { + FILE *f = fdopen(fd, "wb"); + if (errors_reply_to && !contains_header(US"Reply-To", warn_message)) + fprintf(f, "Reply-To: %s\n", errors_reply_to); + fprintf(f, "Auto-Submitted: auto-replied\n"); + if (!contains_header(US"From", warn_message)) + moan_write_from(f); + fprintf(f, "%s", CS warn_message); + + /* Close and wait for child process to complete, without a timeout. */ + + (void)fclose(f); + (void)child_close(pid, 0); + } + + addr->special_action = SPECIAL_NONE; + } +} + + + + +/* Check transport for the given concurrency limit. Return TRUE if over +the limit (or an expansion failure), else FALSE and if there was a limit, +the key for the hints database used for the concurrency count. */ + +static BOOL +tpt_parallel_check(transport_instance * tp, address_item * addr, uschar ** key) +{ +unsigned max_parallel; + +if (!tp->max_parallel) return FALSE; + +max_parallel = (unsigned) expand_string_integer(tp->max_parallel, TRUE); +if (expand_string_message) + { + log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand max_parallel option " + "in %s transport (%s): %s", tp->name, addr->address, + expand_string_message); + return TRUE; + } + +if (max_parallel > 0) + { + uschar * serialize_key = string_sprintf("tpt-serialize-%s", tp->name); + if (!enq_start(serialize_key, max_parallel)) + { + address_item * next; + DEBUG(D_transport) + debug_printf("skipping tpt %s because concurrency limit %u reached\n", + tp->name, max_parallel); + do + { + next = addr->next; + addr->message = US"concurrency limit reached for transport"; + addr->basic_errno = ERRNO_TRETRY; + post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_TRANSPORT, 0); + } while ((addr = next)); + return TRUE; + } + *key = serialize_key; + } +return FALSE; +} + + + +/************************************************* +* Do local deliveries * +*************************************************/ + +/* This function processes the list of addresses in addr_local. True local +deliveries are always done one address at a time. However, local deliveries can +be batched up in some cases. Typically this is when writing batched SMTP output +files for use by some external transport mechanism, or when running local +deliveries over LMTP. + +Arguments: None +Returns: Nothing +*/ + +static void +do_local_deliveries(void) +{ +open_db dbblock; +open_db *dbm_file = NULL; +time_t now = time(NULL); + +/* Loop until we have exhausted the supply of local deliveries */ + +while (addr_local) + { + struct timeval delivery_start; + struct timeval deliver_time; + address_item *addr2, *addr3, *nextaddr; + int logflags = LOG_MAIN; + int logchar = f.dont_deliver? '*' : '='; + transport_instance *tp; + uschar * serialize_key = NULL; + + /* Pick the first undelivered address off the chain */ + + address_item *addr = addr_local; + addr_local = addr->next; + addr->next = NULL; + + DEBUG(D_deliver|D_transport) + debug_printf("--------> %s <--------\n", addr->address); + + /* An internal disaster if there is no transport. Should not occur! */ + + if (!(tp = addr->transport)) + { + logflags |= LOG_PANIC; + 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, EXIM_DTYPE_TRANSPORT, 0); + continue; + } + + /* Check that this base address hasn't previously been delivered to this + transport. The check is necessary at this point to handle homonymic addresses + correctly in cases where the pattern of redirection changes between delivery + attempts. Non-homonymic previous delivery is detected earlier, at routing + time. */ + + if (previously_transported(addr, FALSE)) continue; + + /* There are weird cases where logging is disabled */ + + 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 + delivery. */ + + if (tp->batch_max > 1 && addr_local) + { + int batch_count = 1; + BOOL uses_dom = readconf_depends((driver_instance *)tp, US"domain"); + BOOL uses_lp = ( testflag(addr, af_pfr) + && (testflag(addr, af_file) || addr->local_part[0] == '|') + ) + || readconf_depends((driver_instance *)tp, US"local_part"); + uschar *batch_id = NULL; + address_item **anchor = &addr_local; + address_item *last = addr; + address_item *next; + + /* Expand the batch_id string for comparison with other addresses. + Expansion failure suppresses batching. */ + + if (tp->batch_id) + { + deliver_set_expansions(addr); + batch_id = expand_string(tp->batch_id); + deliver_set_expansions(NULL); + if (!batch_id) + { + log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand batch_id option " + "in %s transport (%s): %s", tp->name, addr->address, + expand_string_message); + batch_count = tp->batch_max; + } + } + + /* Until we reach the batch_max limit, pick off addresses which have the + same characteristics. These are: + + same transport + not previously delivered (see comment about 50 lines above) + same local part if the transport's configuration contains $local_part + or if this is a file or pipe delivery from a redirection + same domain if the transport's configuration contains $domain + same errors address + same additional headers + same headers to be removed + same uid/gid for running the transport + same first host if a host list is set + */ + + while ((next = *anchor) && batch_count < tp->batch_max) + { + BOOL ok = + tp == next->transport + && !previously_transported(next, TRUE) + && testflag(addr, af_pfr) == testflag(next, af_pfr) + && testflag(addr, af_file) == testflag(next, af_file) + && (!uses_lp || Ustrcmp(next->local_part, addr->local_part) == 0) + && (!uses_dom || Ustrcmp(next->domain, addr->domain) == 0) + && same_strings(next->prop.errors_address, addr->prop.errors_address) + && same_headers(next->prop.extra_headers, addr->prop.extra_headers) + && same_strings(next->prop.remove_headers, addr->prop.remove_headers) + && same_ugid(tp, addr, next) + && ( !addr->host_list && !next->host_list + || addr->host_list + && next->host_list + && Ustrcmp(addr->host_list->name, next->host_list->name) == 0 + ); + + /* If the transport has a batch_id setting, batch_id will be non-NULL + from the expansion outside the loop. Expand for this address and compare. + Expansion failure makes this address ineligible for batching. */ + + if (ok && batch_id) + { + uschar *bid; + address_item *save_nextnext = next->next; + next->next = NULL; /* Expansion for a single address */ + deliver_set_expansions(next); + next->next = save_nextnext; + bid = expand_string(tp->batch_id); + deliver_set_expansions(NULL); + if (!bid) + { + log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand batch_id option " + "in %s transport (%s): %s", tp->name, next->address, + expand_string_message); + ok = FALSE; + } + else ok = (Ustrcmp(batch_id, bid) == 0); + } + + /* Take address into batch if OK. */ + + if (ok) + { + *anchor = next->next; /* Include the address */ + next->next = NULL; + last->next = next; + last = next; + batch_count++; + } + else anchor = &next->next; /* Skip the address */ + } + } + + /* We now have one or more addresses that can be delivered in a batch. Check + whether the transport is prepared to accept a message of this size. If not, + fail them all forthwith. If the expansion fails, or does not yield an + integer, defer delivery. */ + + if (tp->message_size_limit) + { + int rc = check_message_size(tp, addr); + if (rc != OK) + { + replicate_status(addr); + while (addr) + { + addr2 = addr->next; + post_process_one(addr, rc, logflags, EXIM_DTYPE_TRANSPORT, 0); + addr = addr2; + } + continue; /* With next batch of addresses */ + } + } + + /* If we are not running the queue, or if forcing, all deliveries will be + attempted. Otherwise, we must respect the retry times for each address. Even + when not doing this, we need to set up the retry key string, and determine + whether a retry record exists, because after a successful delivery, a delete + retry item must be set up. Keep the retry database open only for the duration + 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))) + { + DEBUG(D_deliver|D_retry|D_hints_lookup) + debug_printf("no retry data available\n"); + } + + addr2 = addr; + addr3 = NULL; + while (addr2) + { + BOOL ok = TRUE; /* to deliver this address */ + uschar *retry_key; + + /* Set up the retry key to include the domain or not, and change its + leading character from "R" to "T". Must make a copy before doing this, + because the old key may be pointed to from a "delete" retry item after + a routing delay. */ + + retry_key = string_copy( + tp->retry_use_local_part ? addr2->address_retry_key : + addr2->domain_retry_key); + *retry_key = 'T'; + + /* Inspect the retry data. If there is no hints file, delivery happens. */ + + if (dbm_file) + { + dbdata_retry *retry_record = dbfn_read(dbm_file, retry_key); + + /* If there is no retry record, delivery happens. If there is, + remember it exists so it can be deleted after a successful delivery. */ + + if (retry_record) + { + setflag(addr2, af_lt_retry_exists); + + /* A retry record exists for this address. If queue running and not + forcing, inspect its contents. If the record is too old, or if its + retry time has come, or if it has passed its cutoff time, delivery + will go ahead. */ + + DEBUG(D_retry) + { + debug_printf("retry record exists: age=%s ", + readconf_printtime(now - retry_record->time_stamp)); + debug_printf("(max %s)\n", readconf_printtime(retry_data_expire)); + debug_printf(" time to retry = %s expired = %d\n", + readconf_printtime(retry_record->next_try - now), + retry_record->expired); + } + + if (f.queue_running && !f.deliver_force) + { + ok = (now - retry_record->time_stamp > retry_data_expire) + || (now >= retry_record->next_try) + || retry_record->expired; + + /* If we haven't reached the retry time, there is one more check + to do, which is for the ultimate address timeout. */ + + if (!ok) + ok = retry_ultimate_address_timeout(retry_key, addr2->domain, + retry_record, now); + } + } + else DEBUG(D_retry) debug_printf("no retry record exists\n"); + } + + /* This address is to be delivered. Leave it on the chain. */ + + if (ok) + { + addr3 = addr2; + addr2 = addr2->next; + } + + /* This address is to be deferred. Take it out of the chain, and + post-process it as complete. Must take it out of the chain first, + because post processing puts it on another chain. */ + + else + { + address_item *this = addr2; + this->message = US"Retry time not yet reached"; + this->basic_errno = ERRNO_LRETRY; + addr2 = addr3 ? (addr3->next = addr2->next) + : (addr = addr2->next); + post_process_one(this, DEFER, logflags, EXIM_DTYPE_TRANSPORT, 0); + } + } + + if (dbm_file) dbfn_close(dbm_file); + + /* If there are no addresses left on the chain, they all deferred. Loop + for the next set of addresses. */ + + if (!addr) continue; + + /* If the transport is limited for parallellism, enforce that here. + We use a hints DB entry, incremented here and decremented after + the transport (and any shadow transport) completes. */ + + if (tpt_parallel_check(tp, addr, &serialize_key)) + { + if (expand_string_message) + { + logflags |= LOG_PANIC; + do + { + addr = addr->next; + post_process_one(addr, DEFER, logflags, EXIM_DTYPE_TRANSPORT, 0); + } while ((addr = addr2)); + } + continue; /* Loop for the next set of addresses. */ + } + + + /* So, finally, we do have some addresses that can be passed to the + transport. Before doing so, set up variables that are relevant to a + single delivery. */ + + deliver_set_expansions(addr); + + gettimeofday(&delivery_start, NULL); + deliver_local(addr, FALSE); + timesince(&deliver_time, &delivery_start); + + /* If a shadow transport (which must perforce be another local transport), is + defined, and its condition is met, we must pass the message to the shadow + too, but only those addresses that succeeded. We do this by making a new + chain of addresses - also to keep the original chain uncontaminated. We must + use a chain rather than doing it one by one, because the shadow transport may + batch. + + NOTE: if the condition fails because of a lookup defer, there is nothing we + can do! */ + + if ( tp->shadow + && ( !tp->shadow_condition + || expand_check_condition(tp->shadow_condition, tp->name, US"transport") + ) ) + { + transport_instance *stp; + address_item *shadow_addr = NULL; + address_item **last = &shadow_addr; + + for (stp = transports; stp; stp = stp->next) + if (Ustrcmp(stp->name, tp->shadow) == 0) break; + + if (!stp) + log_write(0, LOG_MAIN|LOG_PANIC, "shadow transport \"%s\" not found ", + tp->shadow); + + /* Pick off the addresses that have succeeded, and make clones. Put into + the shadow_message field a pointer to the shadow_message field of the real + address. */ + + else for (addr2 = addr; addr2; addr2 = addr2->next) + if (addr2->transport_return == OK) + { + addr3 = store_get(sizeof(address_item)); + *addr3 = *addr2; + addr3->next = NULL; + addr3->shadow_message = US &addr2->shadow_message; + addr3->transport = stp; + addr3->transport_return = DEFER; + addr3->return_filename = NULL; + addr3->return_file = -1; + *last = addr3; + last = &addr3->next; + } + + /* If we found any addresses to shadow, run the delivery, and stick any + message back into the shadow_message field in the original. */ + + if (shadow_addr) + { + int save_count = transport_count; + + DEBUG(D_deliver|D_transport) + debug_printf(">>>>>>>>>>>>>>>> Shadow delivery >>>>>>>>>>>>>>>>\n"); + deliver_local(shadow_addr, TRUE); + + for(; shadow_addr; shadow_addr = shadow_addr->next) + { + int sresult = shadow_addr->transport_return; + *(uschar **)shadow_addr->shadow_message = + sresult == OK + ? string_sprintf(" ST=%s", stp->name) + : string_sprintf(" ST=%s (%s%s%s)", stp->name, + shadow_addr->basic_errno <= 0 + ? US"" + : US strerror(shadow_addr->basic_errno), + shadow_addr->basic_errno <= 0 || !shadow_addr->message + ? US"" + : US": ", + shadow_addr->message + ? shadow_addr->message + : shadow_addr->basic_errno <= 0 + ? US"unknown error" + : US""); + + 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); + } + + DEBUG(D_deliver|D_transport) + debug_printf(">>>>>>>>>>>>>>>> End shadow delivery >>>>>>>>>>>>>>>>\n"); + + transport_count = save_count; /* Restore original transport count */ + } + } + + /* Cancel the expansions that were set up for the delivery. */ + + deliver_set_expansions(NULL); + + /* If the transport was parallelism-limited, decrement the hints DB record. */ + + if (serialize_key) enq_end(serialize_key); + + /* Now we can process the results of the real transport. We must take each + address off the chain first, because post_process_one() puts it on another + chain. */ + + for (addr2 = addr; addr2; addr2 = nextaddr) + { + int result = addr2->transport_return; + nextaddr = addr2->next; + + 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); + + /* 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 + the database. These items are handled all together after all addresses + have been handled (so the database is open just for a short time for + updating). */ + + if (result == DEFER || testflag(addr2, af_lt_retry_exists)) + { + int flags = result == DEFER ? 0 : rf_delete; + uschar *retry_key = string_copy(tp->retry_use_local_part + ? addr2->address_retry_key : addr2->domain_retry_key); + *retry_key = 'T'; + retry_add_item(addr2, retry_key, flags); + } + + /* 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, 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 + batch. */ + + if (addr2->transport_return != result) + { + for (addr3 = nextaddr; addr3; addr3 = addr3->next) + { + addr3->transport_return = addr2->transport_return; + addr3->basic_errno = addr2->basic_errno; + addr3->message = addr2->message; + } + result = addr2->transport_return; + } + + /* Whether or not the result was changed to FAIL, we need to copy the + return_file value from the first address into all the addresses of the + batch, so they are all listed in the error message. */ + + addr2->return_file = addr->return_file; + + /* Change log character for recording successful deliveries. */ + + if (result == OK) logchar = '-'; + } + } /* Loop back for next batch of addresses */ +} + + + + +/************************************************* +* Sort remote deliveries * +*************************************************/ + +/* This function is called if remote_sort_domains is set. It arranges that the +chain of addresses for remote deliveries is ordered according to the strings +specified. Try to make this shuffling reasonably efficient by handling +sequences of addresses rather than just single ones. + +Arguments: None +Returns: Nothing +*/ + +static void +sort_remote_deliveries(void) +{ +int sep = 0; +address_item **aptr = &addr_remote; +const uschar *listptr = remote_sort_domains; +uschar *pattern; +uschar patbuf[256]; + +while ( *aptr + && (pattern = string_nextinlist(&listptr, &sep, patbuf, sizeof(patbuf))) + ) + { + address_item *moved = NULL; + address_item **bptr = &moved; + + while (*aptr) + { + address_item **next; + deliver_domain = (*aptr)->domain; /* set $domain */ + if (match_isinlist(deliver_domain, (const uschar **)&pattern, UCHAR_MAX+1, + &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK) + { + aptr = &(*aptr)->next; + continue; + } + + next = &(*aptr)->next; + while ( *next + && (deliver_domain = (*next)->domain, /* Set $domain */ + match_isinlist(deliver_domain, (const uschar **)&pattern, UCHAR_MAX+1, + &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL)) != OK + ) + next = &(*next)->next; + + /* If the batch of non-matchers is at the end, add on any that were + extracted further up the chain, and end this iteration. Otherwise, + extract them from the chain and hang on the moved chain. */ + + if (!*next) + { + *next = moved; + break; + } + + *bptr = *aptr; + *aptr = *next; + *next = NULL; + bptr = next; + aptr = &(*aptr)->next; + } + + /* If the loop ended because the final address matched, *aptr will + be NULL. Add on to the end any extracted non-matching addresses. If + *aptr is not NULL, the loop ended via "break" when *next is null, that + is, there was a string of non-matching addresses at the end. In this + case the extracted addresses have already been added on the end. */ + + if (!*aptr) *aptr = moved; + } + +DEBUG(D_deliver) + { + address_item *addr; + debug_printf("remote addresses after sorting:\n"); + for (addr = addr_remote; addr; addr = addr->next) + debug_printf(" %s\n", addr->address); + } +} + + + +/************************************************* +* Read from pipe for remote delivery subprocess * +*************************************************/ + +/* This function is called when the subprocess is complete, but can also be +called before it is complete, in order to empty a pipe that is full (to prevent +deadlock). It must therefore keep track of its progress in the parlist data +block. + +We read the pipe to get the delivery status codes and a possible error message +for each address, optionally preceded by unusability data for the hosts and +also by optional retry data. + +Read in large chunks into the big buffer and then scan through, interpreting +the data therein. In most cases, only a single read will be necessary. No +individual item will ever be anywhere near 2500 bytes in length, so by ensuring +that we read the next chunk when there is less than 2500 bytes left in the +non-final chunk, we can assume each item is complete in the buffer before +handling it. Each item is written using a single write(), which is atomic for +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 get quite large items (certificate +information and such). + +Argument: + poffset the offset of the parlist item + eop TRUE if the process has completed + +Returns: TRUE if the terminating 'Z' item has been read, + or there has been a disaster (i.e. no more data needed); + FALSE otherwise +*/ + +static BOOL +par_read_pipe(int poffset, BOOL eop) +{ +host_item *h; +pardata *p = parlist + poffset; +address_item *addrlist = p->addrlist; +address_item *addr = p->addr; +pid_t pid = p->pid; +int fd = p->fd; + +uschar *msg = p->msg; +BOOL done = p->done; + +/* Loop through all items, reading from the pipe when necessary. The pipe +used to be non-blocking. But I do not see a reason for using non-blocking I/O +here, as the preceding select() tells us, if data is available for reading. + +A read() on a "selected" handle should never block, but(!) it may return +less data then we expected. (The buffer size we pass to read() shouldn't be +understood as a "request", but as a "limit".) + +Each separate item is written to the pipe in a timely manner. But, especially for +larger items, the read(2) may already return partial data from the write(2). + +The write is atomic mostly (depending on the amount written), but atomic does +not imply "all or noting", it just is "not intermixed" with other writes on the +same channel (pipe). + +*/ + +DEBUG(D_deliver) debug_printf("reading pipe for subprocess %d (%s)\n", + (int)p->pid, eop? "ended" : "not ended yet"); + +while (!done) + { + retry_item *r, **rp; + uschar pipeheader[PIPE_HEADER_SIZE+1]; + uschar *id = &pipeheader[0]; + uschar *subid = &pipeheader[1]; + uschar *ptr = big_buffer; + size_t required = PIPE_HEADER_SIZE; /* first the pipehaeder, later the data */ + ssize_t got; + + DEBUG(D_deliver) debug_printf( + "expect %lu bytes (pipeheader) from tpt process %d\n", (u_long)required, pid); + + /* We require(!) all the PIPE_HEADER_SIZE bytes here, as we know, + they're written in a timely manner, so waiting for the write shouldn't hurt a lot. + If we get less, we can assume the subprocess do be done and do not expect any further + information from it. */ + + if ((got = readn(fd, pipeheader, required)) != required) + { + 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'; + DEBUG(D_deliver) + debug_printf("got %ld bytes (pipeheader) from transport process %d\n", + (long) got, pid); + + { + /* If we can't decode the pipeheader, the subprocess seems to have a + problem, we do not expect any furher information from it. */ + char *endc; + required = Ustrtol(pipeheader+2, &endc, 10); + if (*endc) + { + msg = string_sprintf("failed to read pipe " + "from transport process %d for transport %s: error decoding size from header", + pid, addr->transport->driver_name); + done = TRUE; + break; + } + } + + DEBUG(D_deliver) + debug_printf("expect %lu bytes (pipedata) from transport process %d\n", + (u_long)required, pid); + + /* 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. */ + if ((got = readn(fd, big_buffer, required)) != required) + { + 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 + available in store. */ + + switch (*id) + { + /* Host items exist only if any hosts were marked unusable. Match + up by checking the IP address. */ + + case 'H': + for (h = addrlist->host_list; h; h = h->next) + { + if (!h->address || Ustrcmp(h->address, ptr+2) != 0) continue; + h->status = ptr[0]; + h->why = ptr[1]; + } + ptr += 2; + while (*ptr++); + break; + + /* Retry items are sent in a preceding R item for each address. This is + kept separate to keep each message short enough to guarantee it won't + be split in the pipe. Hopefully, in the majority of cases, there won't in + fact be any retry items at all. + + The complete set of retry items might include an item to delete a + routing retry if there was a previous routing delay. However, routing + retries are also used when a remote transport identifies an address error. + In that case, there may also be an "add" item for the same key. Arrange + that a "delete" item is dropped in favour of an "add" item. */ + + case 'R': + if (!addr) goto ADDR_MISMATCH; + + DEBUG(D_deliver|D_retry) + debug_printf("reading retry information for %s from subprocess\n", + ptr+1); + + /* Cut out any "delete" items on the list. */ + + for (rp = &addr->retries; (r = *rp); rp = &r->next) + if (Ustrcmp(r->key, ptr+1) == 0) /* Found item with same key */ + { + if (!(r->flags & rf_delete)) break; /* It was not "delete" */ + *rp = r->next; /* Excise a delete item */ + DEBUG(D_deliver|D_retry) + debug_printf(" existing delete item dropped\n"); + } + + /* We want to add a delete item only if there is no non-delete item; + however we still have to step ptr through the data. */ + + if (!r || !(*ptr & rf_delete)) + { + r = store_get(sizeof(retry_item)); + r->next = addr->retries; + addr->retries = r; + r->flags = *ptr++; + r->key = string_copy(ptr); + while (*ptr++); + memcpy(&r->basic_errno, ptr, sizeof(r->basic_errno)); + ptr += sizeof(r->basic_errno); + memcpy(&r->more_errno, ptr, sizeof(r->more_errno)); + ptr += sizeof(r->more_errno); + r->message = *ptr ? string_copy(ptr) : NULL; + DEBUG(D_deliver|D_retry) debug_printf(" added %s item\n", + r->flags & rf_delete ? "delete" : "retry"); + } + + else + { + DEBUG(D_deliver|D_retry) + debug_printf(" delete item not added: non-delete item exists\n"); + ptr++; + while(*ptr++); + ptr += sizeof(r->basic_errno) + sizeof(r->more_errno); + } + + while(*ptr++); + break; + + /* Put the amount of data written into the parlist block */ + + case 'S': + memcpy(&(p->transport_count), ptr, sizeof(transport_count)); + ptr += sizeof(transport_count); + break; + + /* Address items are in the order of items on the address chain. We + remember the current address value in case this function is called + several times to empty the pipe in stages. Information about delivery + over TLS is sent in a preceding X item for each address. We don't put + 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 + case 'X': + if (!addr) goto ADDR_MISMATCH; /* Below, in 'A' handler */ + switch (*subid) + { + case '1': + addr->cipher = NULL; + addr->peerdn = NULL; + + if (*ptr) + addr->cipher = string_copy(ptr); + while (*ptr++); + if (*ptr) + addr->peerdn = string_copy(ptr); + break; + + case '2': + if (*ptr) + (void) tls_import_cert(ptr, &addr->peercert); + else + addr->peercert = NULL; + break; + + case '3': + if (*ptr) + (void) tls_import_cert(ptr, &addr->ourcert); + else + addr->ourcert = NULL; + break; + +# ifndef DISABLE_OCSP + case '4': + addr->ocsp = *ptr ? *ptr - '0' : OCSP_NOT_REQ; + break; +# endif + } + while (*ptr++); + break; +#endif /*SUPPORT_TLS*/ + + case 'C': /* client authenticator information */ + switch (*subid) + { + case '1': addr->authenticator = *ptr ? string_copy(ptr) : NULL; break; + case '2': addr->auth_id = *ptr ? string_copy(ptr) : NULL; break; + case '3': addr->auth_sndr = *ptr ? string_copy(ptr) : NULL; break; + } + while (*ptr++); + break; + +#ifndef DISABLE_PRDR + case 'P': + setflag(addr, af_prdr_used); + break; +#endif + + case 'L': + switch (*subid) + { +#ifdef EXPERIMENTAL_PIPE_CONNECT + case 2: setflag(addr, af_early_pipe); /*FALLTHROUGH*/ +#endif + case 1: setflag(addr, af_pipelining); break; + } + break; + + case 'K': + setflag(addr, af_chunking_used); + break; + + case 'T': + setflag(addr, af_tcp_fastopen_conn); + if (*subid > '0') setflag(addr, af_tcp_fastopen); + if (*subid > '1') setflag(addr, af_tcp_fastopen_data); + break; + + case 'D': + if (!addr) goto ADDR_MISMATCH; + memcpy(&(addr->dsn_aware), ptr, sizeof(addr->dsn_aware)); + ptr += sizeof(addr->dsn_aware); + DEBUG(D_deliver) debug_printf("DSN read: addr->dsn_aware = %d\n", addr->dsn_aware); + break; + + case 'A': + if (!addr) + { + ADDR_MISMATCH: + msg = string_sprintf("address count mismatch for data read from pipe " + "for transport process %d for transport %s", pid, + addrlist->transport->driver_name); + done = TRUE; + break; + } + + switch (*subid) + { + #ifdef SUPPORT_SOCKS + case '2': /* proxy information; must arrive before A0 and applies to that addr XXX oops*/ + proxy_session = TRUE; /*XXX should this be cleared somewhere? */ + if (*ptr == 0) + ptr++; + else + { + proxy_local_address = string_copy(ptr); + while(*ptr++); + memcpy(&proxy_local_port, ptr, sizeof(proxy_local_port)); + ptr += sizeof(proxy_local_port); + } + break; + #endif + + #ifdef EXPERIMENTAL_DSN_INFO + case '1': /* must arrive before A0, and applies to that addr */ + /* Two strings: smtp_greeting and helo_response */ + addr->smtp_greeting = string_copy(ptr); + while(*ptr++); + addr->helo_response = string_copy(ptr); + while(*ptr++); + break; + #endif + + case '0': + DEBUG(D_deliver) debug_printf("A0 %s tret %d\n", addr->address, *ptr); + addr->transport_return = *ptr++; + addr->special_action = *ptr++; + memcpy(&addr->basic_errno, ptr, sizeof(addr->basic_errno)); + 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->flags, ptr, sizeof(addr->flags)); + ptr += sizeof(addr->flags); + addr->message = *ptr ? string_copy(ptr) : NULL; + while(*ptr++); + addr->user_message = *ptr ? string_copy(ptr) : NULL; + while(*ptr++); + + /* Always two strings for host information, followed by the port number and DNSSEC mark */ + + if (*ptr) + { + h = store_get(sizeof(host_item)); + h->name = string_copy(ptr); + while (*ptr++); + h->address = string_copy(ptr); + while(*ptr++); + memcpy(&h->port, ptr, sizeof(h->port)); + ptr += sizeof(h->port); + h->dnssec = *ptr == '2' ? DS_YES + : *ptr == '1' ? DS_NO + : DS_UNK; + ptr++; + addr->host_used = h; + } + else ptr++; + + /* Finished with this address */ + + addr = addr->next; + break; + } + break; + + /* Local interface address/port */ + case 'I': + if (*ptr) sending_ip_address = string_copy(ptr); + while (*ptr++) ; + if (*ptr) sending_port = atoi(CS ptr); + while (*ptr++) ; + break; + + /* Z marks the logical end of the data. It is followed by '0' if + continue_transport was NULL at the end of transporting, otherwise '1'. + We need to know when it becomes NULL during a delivery down a passed SMTP + channel so that we don't try to pass anything more down it. Of course, for + most normal messages it will remain NULL all the time. */ + + case 'Z': + if (*ptr == '0') + { + continue_transport = NULL; + continue_hostname = NULL; + } + done = TRUE; + DEBUG(D_deliver) debug_printf("Z0%c item read\n", *ptr); + break; + + /* Anything else is a disaster. */ + + default: + msg = string_sprintf("malformed data (%d) read from pipe for transport " + "process %d for transport %s", ptr[-1], pid, + addr->transport->driver_name); + done = TRUE; + break; + } + } + +/* The done flag is inspected externally, to determine whether or not to +call the function again when the process finishes. */ + +p->done = done; + +/* If the process hadn't finished, and we haven't seen the end of the data +or if we suffered a disaster, update the rest of the state, and return FALSE to +indicate "not finished". */ + +if (!eop && !done) + { + p->addr = addr; + p->msg = msg; + return FALSE; + } + +/* Close our end of the pipe, to prevent deadlock if the far end is still +pushing stuff into it. */ + +(void)close(fd); +p->fd = -1; + +/* If we have finished without error, but haven't had data for every address, +something is wrong. */ + +if (!msg && addr) + msg = string_sprintf("insufficient address data read from pipe " + "for transport process %d for transport %s", pid, + addr->transport->driver_name); + +/* If an error message is set, something has gone wrong in getting back +the delivery data. Put the message into each address and freeze it. */ + +if (msg) + for (addr = addrlist; addr; addr = addr->next) + { + addr->transport_return = DEFER; + addr->special_action = SPECIAL_FREEZE; + addr->message = msg; + log_write(0, LOG_MAIN|LOG_PANIC, "Delivery status for %s: %s\n", addr->address, addr->message); + } + +/* Return TRUE to indicate we have got all we need from this process, even +if it hasn't actually finished yet. */ + +return TRUE; +} + + + +/************************************************* +* Post-process a set of remote addresses * +*************************************************/ + +/* Do what has to be done immediately after a remote delivery for each set of +addresses, then re-write the spool if necessary. Note that post_process_one +puts the address on an appropriate queue; hence we must fish off the next +one first. This function is also called if there is a problem with setting +up a subprocess to do a remote delivery in parallel. In this case, the final +argument contains a message, and the action must be forced to DEFER. + +Argument: + addr pointer to chain of address items + logflags flags for logging + msg NULL for normal cases; -> error message for unexpected problems + fallback TRUE if processing fallback hosts + +Returns: nothing +*/ + +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) + if (h->address) + if (h->status >= hstatus_unusable) tree_add_unusable(h); + +/* Now handle each address on the chain. The transport has placed '=' or '-' +into the special_action field for each successful delivery. */ + +while (addr) + { + address_item *next = addr->next; + + /* If msg == NULL (normal processing) and the result is DEFER and we are + processing the main hosts and there are fallback hosts available, put the + address on the list for fallback delivery. */ + + if ( addr->transport_return == DEFER + && addr->fallback_hosts + && !fallback + && !msg + ) + { + addr->host_list = addr->fallback_hosts; + addr->next = addr_fallback; + addr_fallback = addr; + DEBUG(D_deliver) debug_printf("%s queued for fallback host(s)\n", addr->address); + } + + /* If msg is set (=> unexpected problem), set it in the address before + doing the ordinary post processing. */ + + else + { + if (msg) + { + addr->message = msg; + addr->transport_return = DEFER; + } + (void)post_process_one(addr, addr->transport_return, logflags, + EXIM_DTYPE_TRANSPORT, addr->special_action); + } + + /* Next address */ + + addr = next; + } + +/* If we have just delivered down a passed SMTP channel, and that was +the last address, the channel will have been closed down. Now that +we have logged that delivery, set continue_sequence to 1 so that +any subsequent deliveries don't get "*" incorrectly logged. */ + +if (!continue_transport) continue_sequence = 1; +} + + + +/************************************************* +* Wait for one remote delivery subprocess * +*************************************************/ + +/* This function is called while doing remote deliveries when either the +maximum number of processes exist and we need one to complete so that another +can be created, or when waiting for the last ones to complete. It must wait for +the completion of one subprocess, empty the control block slot, and return a +pointer to the address chain. + +Arguments: none +Returns: pointer to the chain of addresses handled by the process; + NULL if no subprocess found - this is an unexpected error +*/ + +static address_item * +par_wait(void) +{ +int poffset, status; +address_item *addr, *addrlist; +pid_t pid; + +set_process_info("delivering %s: waiting for a remote delivery subprocess " + "to finish", message_id); + +/* Loop until either a subprocess completes, or there are no subprocesses in +existence - in which case give an error return. We cannot proceed just by +waiting for a completion, because a subprocess may have filled up its pipe, and +be waiting for it to be emptied. Therefore, if no processes have finished, we +wait for one of the pipes to acquire some data by calling select(), with a +timeout just in case. + +The simple approach is just to iterate after reading data from a ready pipe. +This leads to non-ideal behaviour when the subprocess has written its final Z +item, closed the pipe, and is in the process of exiting (the common case). A +call to waitpid() yields nothing completed, but select() shows the pipe ready - +reading it yields EOF, so you end up with busy-waiting until the subprocess has +actually finished. + +To avoid this, if all the data that is needed has been read from a subprocess +after select(), an explicit wait() for it is done. We know that all it is doing +is writing to the pipe and then exiting, so the wait should not be long. + +The non-blocking waitpid() is to some extent just insurance; if we could +reliably detect end-of-file on the pipe, we could always know when to do a +blocking wait() for a completed process. However, because some systems use +NDELAY, which doesn't distinguish between EOF and pipe empty, it is easier to +use code that functions without the need to recognize EOF. + +There's a double loop here just in case we end up with a process that is not in +the list of remote delivery processes. Something has obviously gone wrong if +this is the case. (For example, a process that is incorrectly left over from +routing or local deliveries might be found.) The damage can be minimized by +looping back and looking for another process. If there aren't any, the error +return will happen. */ + +for (;;) /* Normally we do not repeat this loop */ + { + while ((pid = waitpid(-1, &status, WNOHANG)) <= 0) + { + struct timeval tv; + fd_set select_pipes; + int maxpipe, readycount; + + /* A return value of -1 can mean several things. If errno != ECHILD, it + either means invalid options (which we discount), or that this process was + interrupted by a signal. Just loop to try the waitpid() again. + + If errno == ECHILD, waitpid() is telling us that there are no subprocesses + in existence. This should never happen, and is an unexpected error. + However, there is a nasty complication when running under Linux. If "strace + -f" is being used under Linux to trace this process and its children, + subprocesses are "stolen" from their parents and become the children of the + tracing process. A general wait such as the one we've just obeyed returns + as if there are no children while subprocesses are running. Once a + subprocess completes, it is restored to the parent, and waitpid(-1) finds + it. Thanks to Joachim Wieland for finding all this out and suggesting a + palliative. + + This does not happen using "truss" on Solaris, nor (I think) with other + tracing facilities on other OS. It seems to be specific to Linux. + + What we do to get round this is to use kill() to see if any of our + subprocesses are still in existence. If kill() gives an OK return, we know + it must be for one of our processes - it can't be for a re-use of the pid, + because if our process had finished, waitpid() would have found it. If any + of our subprocesses are in existence, we proceed to use select() as if + waitpid() had returned zero. I think this is safe. */ + + if (pid < 0) + { + if (errno != ECHILD) continue; /* Repeats the waitpid() */ + + DEBUG(D_deliver) + debug_printf("waitpid() returned -1/ECHILD: checking explicitly " + "for process existence\n"); + + for (poffset = 0; poffset < remote_max_parallel; poffset++) + { + if ((pid = parlist[poffset].pid) != 0 && kill(pid, 0) == 0) + { + DEBUG(D_deliver) debug_printf("process %d still exists: assume " + "stolen by strace\n", (int)pid); + break; /* With poffset set */ + } + } + + if (poffset >= remote_max_parallel) + { + DEBUG(D_deliver) debug_printf("*** no delivery children found\n"); + return NULL; /* This is the error return */ + } + } + + /* A pid value greater than 0 breaks the "while" loop. A negative value has + been handled above. A return value of zero means that there is at least one + subprocess, but there are no completed subprocesses. See if any pipes are + ready with any data for reading. */ + + DEBUG(D_deliver) debug_printf("selecting on subprocess pipes\n"); + + maxpipe = 0; + FD_ZERO(&select_pipes); + for (poffset = 0; poffset < remote_max_parallel; poffset++) + if (parlist[poffset].pid != 0) + { + int fd = parlist[poffset].fd; + FD_SET(fd, &select_pipes); + if (fd > maxpipe) maxpipe = fd; + } + + /* Stick in a 60-second timeout, just in case. */ + + tv.tv_sec = 60; + tv.tv_usec = 0; + + readycount = select(maxpipe + 1, (SELECT_ARG2_TYPE *)&select_pipes, + NULL, NULL, &tv); + + /* Scan through the pipes and read any that are ready; use the count + returned by select() to stop when there are no more. Select() can return + with no processes (e.g. if interrupted). This shouldn't matter. + + If par_read_pipe() returns TRUE, it means that either the terminating Z was + read, or there was a disaster. In either case, we are finished with this + process. Do an explicit wait() for the process and break the main loop if + it succeeds. + + It turns out that we have to deal with the case of an interrupted system + call, which can happen on some operating systems if the signal handling is + set up to do that by default. */ + + for (poffset = 0; + readycount > 0 && poffset < remote_max_parallel; + poffset++) + { + if ( (pid = parlist[poffset].pid) != 0 + && FD_ISSET(parlist[poffset].fd, &select_pipes) + ) + { + readycount--; + if (par_read_pipe(poffset, FALSE)) /* Finished with this pipe */ + for (;;) /* Loop for signals */ + { + pid_t endedpid = waitpid(pid, &status, 0); + if (endedpid == pid) goto PROCESS_DONE; + if (endedpid != (pid_t)(-1) || errno != EINTR) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Unexpected error return " + "%d (errno = %d) from waitpid() for process %d", + (int)endedpid, errno, (int)pid); + } + } + } + + /* Now go back and look for a completed subprocess again. */ + } + + /* A completed process was detected by the non-blocking waitpid(). Find the + data block that corresponds to this subprocess. */ + + for (poffset = 0; poffset < remote_max_parallel; poffset++) + if (pid == parlist[poffset].pid) break; + + /* Found the data block; this is a known remote delivery process. We don't + need to repeat the outer loop. This should be what normally happens. */ + + if (poffset < remote_max_parallel) break; + + /* This situation is an error, but it's probably better to carry on looking + for another process than to give up (as we used to do). */ + + log_write(0, LOG_MAIN|LOG_PANIC, "Process %d finished: not found in remote " + "transport process list", pid); + } /* End of the "for" loop */ + +/* Come here when all the data was completely read after a select(), and +the process in pid has been wait()ed for. */ + +PROCESS_DONE: + +DEBUG(D_deliver) + { + if (status == 0) + debug_printf("remote delivery process %d ended\n", (int)pid); + else + debug_printf("remote delivery process %d ended: status=%04x\n", (int)pid, + status); + } + +set_process_info("delivering %s", message_id); + +/* Get the chain of processed addresses */ + +addrlist = parlist[poffset].addrlist; + +/* If the process did not finish cleanly, record an error and freeze (except +for SIGTERM, SIGKILL and SIGQUIT), and also ensure the journal is not removed, +in case the delivery did actually happen. */ + +if ((status & 0xffff) != 0) + { + uschar *msg; + int msb = (status >> 8) & 255; + int lsb = status & 255; + int code = (msb == 0)? (lsb & 0x7f) : msb; + + msg = string_sprintf("%s transport process returned non-zero status 0x%04x: " + "%s %d", + addrlist->transport->driver_name, + status, + (msb == 0)? "terminated by signal" : "exit code", + code); + + if (msb != 0 || (code != SIGTERM && code != SIGKILL && code != SIGQUIT)) + addrlist->special_action = SPECIAL_FREEZE; + + for (addr = addrlist; addr; addr = addr->next) + { + addr->transport_return = DEFER; + addr->message = msg; + } + + remove_journal = FALSE; + } + +/* Else complete reading the pipe to get the result of the delivery, if all +the data has not yet been obtained. */ + +else if (!parlist[poffset].done) (void)par_read_pipe(poffset, TRUE); + +/* Put the data count and return path into globals, mark the data slot unused, +decrement the count of subprocesses, and return the address chain. */ + +transport_count = parlist[poffset].transport_count; +used_return_path = parlist[poffset].return_path; +parlist[poffset].pid = 0; +parcount--; +return addrlist; +} + + + +/************************************************* +* Wait for subprocesses and post-process * +*************************************************/ + +/* This function waits for subprocesses until the number that are still running +is below a given threshold. For each complete subprocess, the addresses are +post-processed. If we can't find a running process, there is some shambles. +Better not bomb out, as that might lead to multiple copies of the message. Just +log and proceed as if all done. + +Arguments: + max maximum number of subprocesses to leave running + fallback TRUE if processing fallback hosts + +Returns: nothing +*/ + +static void +par_reduce(int max, BOOL fallback) +{ +while (parcount > max) + { + address_item *doneaddr = par_wait(); + if (!doneaddr) + { + log_write(0, LOG_MAIN|LOG_PANIC, + "remote delivery process count got out of step"); + parcount = 0; + } + else + { + transport_instance * tp = doneaddr->transport; + if (tp->max_parallel) + enq_end(string_sprintf("tpt-serialize-%s", tp->name)); + + remote_post_process(doneaddr, LOG_MAIN, NULL, fallback); + } + } +} + +static void +rmt_dlv_checked_write(int fd, char id, char subid, void * buf, ssize_t size) +{ +uschar pipe_header[PIPE_HEADER_SIZE+1]; +size_t total_len = PIPE_HEADER_SIZE + size; + +struct iovec iov[2] = { + { pipe_header, PIPE_HEADER_SIZE }, /* indication about the data to expect */ + { buf, size } /* *the* data */ +}; + +ssize_t ret; + +/* we assume that size can't get larger then BIG_BUFFER_SIZE which currently is set to 16k */ +/* complain to log if someone tries with buffer sizes we can't handle*/ + +if (size > BIG_BUFFER_SIZE-1) + { + log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "Failed writing transport result to pipe: can't handle buffers > %d bytes. truncating!\n", + BIG_BUFFER_SIZE-1); + size = BIG_BUFFER_SIZE; + } + +/* Should we check that we do not write more than PIPE_BUF? What would +that help? */ + +/* convert size to human readable string prepended by id and subid */ +if (PIPE_HEADER_SIZE != snprintf(CS pipe_header, PIPE_HEADER_SIZE+1, "%c%c%05ld", + id, subid, (long)size)) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "header snprintf failed\n"); + +DEBUG(D_deliver) debug_printf("header write id:%c,subid:%c,size:%ld,final:%s\n", + id, subid, (long)size, pipe_header); + +if ((ret = writev(fd, iov, 2)) != total_len) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "Failed writing transport result to pipe (%ld of %ld bytes): %s", + (long)ret, (long)total_len, ret == -1 ? strerror(errno) : "short write"); +} + +/************************************************* +* Do remote deliveries * +*************************************************/ + +/* This function is called to process the addresses in addr_remote. We must +pick off the queue all addresses that have the same transport, remote +destination, and errors address, and hand them to the transport in one go, +subject to some configured limitations. If this is a run to continue delivering +to an existing delivery channel, skip all but those addresses that can go to +that channel. The skipped addresses just get deferred. + +If mua_wrapper is set, all addresses must be able to be sent in a single +transaction. If not, this function yields FALSE. + +In Exim 4, remote deliveries are always done in separate processes, even +if remote_max_parallel = 1 or if there's only one delivery to do. The reason +is so that the base process can retain privilege. This makes the +implementation of fallback transports feasible (though not initially done.) + +We create up to the configured number of subprocesses, each of which passes +back the delivery state via a pipe. (However, when sending down an existing +connection, remote_max_parallel is forced to 1.) + +Arguments: + fallback TRUE if processing fallback hosts + +Returns: TRUE normally + FALSE if mua_wrapper is set and the addresses cannot all be sent + in one transaction +*/ + +static BOOL +do_remote_deliveries(BOOL fallback) +{ +int parmax; +int delivery_count; +int poffset; + +parcount = 0; /* Number of executing subprocesses */ + +/* When sending down an existing channel, only do one delivery at a time. +We use a local variable (parmax) to hold the maximum number of processes; +this gets reduced from remote_max_parallel if we can't create enough pipes. */ + +if (continue_transport) remote_max_parallel = 1; +parmax = remote_max_parallel; + +/* If the data for keeping a list of processes hasn't yet been +set up, do so. */ + +if (!parlist) + { + parlist = store_get(remote_max_parallel * sizeof(pardata)); + 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++) + { + pid_t pid; + uid_t uid; + gid_t gid; + int pfd[2]; + int address_count = 1; + int address_count_max; + BOOL multi_domain; + BOOL use_initgroups; + BOOL pipe_done = FALSE; + transport_instance *tp; + address_item **anchor = &addr_remote; + address_item *addr = addr_remote; + address_item *last = addr; + address_item *next; + uschar * panicmsg; + uschar * serialize_key = NULL; + + /* Pull the first address right off the list. */ + + addr_remote = addr->next; + addr->next = NULL; + + DEBUG(D_deliver|D_transport) + debug_printf("--------> %s <--------\n", addr->address); + + /* If no transport has been set, there has been a big screw-up somewhere. */ + + if (!(tp = addr->transport)) + { + f.disable_logging = FALSE; /* Jic */ + panicmsg = US"No transport set by router"; + goto panic_continue; + } + + /* Check that this base address hasn't previously been delivered to this + transport. The check is necessary at this point to handle homonymic addresses + correctly in cases where the pattern of redirection changes between delivery + attempts. Non-homonymic previous delivery is detected earlier, at routing + time. */ + + if (previously_transported(addr, FALSE)) continue; + + /* Force failure if the message is too big. */ + + if (tp->message_size_limit) + { + int rc = check_message_size(tp, addr); + if (rc != OK) + { + addr->transport_return = rc; + remote_post_process(addr, LOG_MAIN, NULL, fallback); + continue; + } + } + + /* Get the flag which specifies whether the transport can handle different + domains that nevertheless resolve to the same set of hosts. If it needs + expanding, get variables set: $address_data, $domain_data, $localpart_data, + $host, $host_address, $host_port. */ + if (tp->expand_multi_domain) + deliver_set_expansions(addr); + + if (exp_bool(addr, US"transport", tp->name, D_transport, + US"multi_domain", tp->multi_domain, tp->expand_multi_domain, + &multi_domain) != OK) + { + deliver_set_expansions(NULL); + panicmsg = addr->message; + goto panic_continue; + } + + /* Get the maximum it can handle in one envelope, with zero meaning + unlimited, which is forced for the MUA wrapper case. */ + + address_count_max = tp->max_addresses; + if (address_count_max == 0 || mua_wrapper) address_count_max = 999999; + + + /************************************************************************/ + /***** This is slightly experimental code, but should be safe. *****/ + + /* The address_count_max value is the maximum number of addresses that the + transport can send in one envelope. However, the transport must be capable of + dealing with any number of addresses. If the number it gets exceeds its + envelope limitation, it must send multiple copies of the message. This can be + done over a single connection for SMTP, so uses less resources than making + multiple connections. On the other hand, if remote_max_parallel is greater + than one, it is perhaps a good idea to use parallel processing to move the + message faster, even if that results in multiple simultaneous connections to + the same host. + + How can we come to some compromise between these two ideals? What we do is to + limit the number of addresses passed to a single instance of a transport to + the greater of (a) its address limit (rcpt_max for SMTP) and (b) the total + number of addresses routed to remote transports divided by + remote_max_parallel. For example, if the message has 100 remote recipients, + remote max parallel is 2, and rcpt_max is 10, we'd never send more than 50 at + once. But if rcpt_max is 100, we could send up to 100. + + Of course, not all the remotely addresses in a message are going to go to the + same set of hosts (except in smarthost configurations), so this is just a + heuristic way of dividing up the work. + + Furthermore (1), because this may not be wanted in some cases, and also to + cope with really pathological cases, there is also a limit to the number of + messages that are sent over one connection. This is the same limit that is + used when sending several different messages over the same connection. + Continue_sequence is set when in this situation, to the number sent so + far, including this message. + + Furthermore (2), when somebody explicitly sets the maximum value to 1, it + is probably because they are using VERP, in which case they want to pass only + one address at a time to the transport, in order to be able to use + $local_part and $domain in constructing a new return path. We could test for + the use of these variables, but as it is so likely they will be used when the + maximum is 1, we don't bother. Just leave the value alone. */ + + if ( address_count_max != 1 + && address_count_max < remote_delivery_count/remote_max_parallel + ) + { + int new_max = remote_delivery_count/remote_max_parallel; + int message_max = tp->connection_max_messages; + if (connection_max_messages >= 0) message_max = connection_max_messages; + message_max -= continue_sequence - 1; + if (message_max > 0 && new_max > address_count_max * message_max) + new_max = address_count_max * message_max; + address_count_max = new_max; + } + + /************************************************************************/ + + + /* Pick off all addresses which have the same transport, errors address, + destination, and extra headers. In some cases they point to the same host + list, but we also need to check for identical host lists generated from + entirely different domains. The host list pointers can be NULL in the case + where the hosts are defined in the transport. There is also a configured + maximum limit of addresses that can be handled at once (see comments above + for how it is computed). + If the transport does not handle multiple domains, enforce that also, + and if it might need a per-address check for this, re-evaluate it. + */ + + while ((next = *anchor) && address_count < address_count_max) + { + BOOL md; + if ( (multi_domain || Ustrcmp(next->domain, addr->domain) == 0) + && tp == next->transport + && same_hosts(next->host_list, addr->host_list) + && same_strings(next->prop.errors_address, addr->prop.errors_address) + && same_headers(next->prop.extra_headers, addr->prop.extra_headers) + && same_ugid(tp, next, addr) + && ( next->prop.remove_headers == addr->prop.remove_headers + || ( next->prop.remove_headers + && addr->prop.remove_headers + && Ustrcmp(next->prop.remove_headers, addr->prop.remove_headers) == 0 + ) ) + && ( !multi_domain + || ( ( + (void)(!tp->expand_multi_domain || ((void)deliver_set_expansions(next), 1)), + exp_bool(addr, + US"transport", next->transport->name, D_transport, + US"multi_domain", next->transport->multi_domain, + next->transport->expand_multi_domain, &md) == OK + ) + && md + ) ) ) + { + *anchor = next->next; + next->next = NULL; + next->first = addr; /* remember top one (for retry processing) */ + last->next = next; + last = next; + address_count++; + } + else anchor = &(next->next); + deliver_set_expansions(NULL); + } + + /* If we are acting as an MUA wrapper, all addresses must go in a single + transaction. If not, put them back on the chain and yield FALSE. */ + + if (mua_wrapper && addr_remote) + { + last->next = addr_remote; + addr_remote = addr; + return FALSE; + } + + /* If the transport is limited for parallellism, enforce that here. + The hints DB entry is decremented in par_reduce(), when we reap the + transport process. */ + + if (tpt_parallel_check(tp, addr, &serialize_key)) + if ((panicmsg = expand_string_message)) + goto panic_continue; + else + continue; /* Loop for the next set of addresses. */ + + /* Set up the expansion variables for this set of addresses */ + + deliver_set_expansions(addr); + + /* Ensure any transport-set auth info is fresh */ + addr->authenticator = addr->auth_id = addr->auth_sndr = NULL; + + /* Compute the return path, expanding a new one if required. The old one + must be set first, as it might be referred to in the expansion. */ + + if(addr->prop.errors_address) + return_path = addr->prop.errors_address; +#ifdef EXPERIMENTAL_SRS + else if(addr->prop.srs_sender) + return_path = addr->prop.srs_sender; +#endif + else + return_path = sender_address; + + if (tp->return_path) + { + uschar *new_return_path = expand_string(tp->return_path); + if (new_return_path) + return_path = new_return_path; + else if (!f.expand_string_forcedfail) + { + panicmsg = string_sprintf("Failed to expand return path \"%s\": %s", + tp->return_path, expand_string_message); + goto enq_continue; + } + } + + /* Find the uid, gid, and use_initgroups setting for this transport. Failure + logs and sets up error messages, so we just post-process and continue with + the next address. */ + + if (!findugid(addr, tp, &uid, &gid, &use_initgroups)) + { + panicmsg = NULL; + goto enq_continue; + } + + /* If this transport has a setup function, call it now so that it gets + run in this process and not in any subprocess. That way, the results of + any setup that are retained by the transport can be reusable. One of the + things the setup does is to set the fallback host lists in the addresses. + That is why it is called at this point, before the continue delivery + processing, because that might use the fallback hosts. */ + + if (tp->setup) + (void)((tp->setup)(addr->transport, addr, NULL, uid, gid, NULL)); + + /* If we have a connection still open from a verify stage (lazy-close) + treat it as if it is a continued connection (apart from the counter used + for the log line mark). */ + + if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only) + { + DEBUG(D_deliver) + debug_printf("lazy-callout-close: have conn still open from verification\n"); + continue_transport = cutthrough.transport; + continue_hostname = string_copy(cutthrough.host.name); + continue_host_address = string_copy(cutthrough.host.address); + continue_sequence = 1; + sending_ip_address = cutthrough.snd_ip; + sending_port = cutthrough.snd_port; + smtp_peer_options = cutthrough.peer_options; + } + + /* If this is a run to continue delivery down an already-established + channel, check that this set of addresses matches the transport and + the channel. If it does not, defer the addresses. If a host list exists, + we must check that the continue host is on the list. Otherwise, the + host is set in the transport. */ + + f.continue_more = FALSE; /* In case got set for the last lot */ + if (continue_transport) + { + BOOL ok = Ustrcmp(continue_transport, tp->name) == 0; + + /* If the transport is about to override the host list do not check + it here but take the cost of running the transport process to discover + if the continued_hostname connection is suitable. This is a layering + violation which is unfortunate as it requires we haul in the smtp + include file. */ + + if (ok) + { + smtp_transport_options_block * ob; + + if ( !( Ustrcmp(tp->info->driver_name, "smtp") == 0 + && (ob = (smtp_transport_options_block *)tp->options_block) + && ob->hosts_override && ob->hosts + ) + && addr->host_list + ) + { + host_item * h; + ok = FALSE; + for (h = addr->host_list; h; h = h->next) + if (Ustrcmp(h->name, continue_hostname) == 0) + /*XXX should also check port here */ + { ok = TRUE; break; } + } + } + + /* Addresses not suitable; defer or queue for fallback hosts (which + might be the continue host) and skip to next address. */ + + if (!ok) + { + DEBUG(D_deliver) debug_printf("not suitable for continue_transport (%s)\n", + Ustrcmp(continue_transport, tp->name) != 0 + ? string_sprintf("tpt %s vs %s", continue_transport, tp->name) + : string_sprintf("no host matching %s", continue_hostname)); + if (serialize_key) enq_end(serialize_key); + + if (addr->fallback_hosts && !fallback) + { + for (next = addr; ; next = next->next) + { + next->host_list = next->fallback_hosts; + DEBUG(D_deliver) debug_printf("%s queued for fallback host(s)\n", next->address); + if (!next->next) break; + } + next->next = addr_fallback; + addr_fallback = addr; + } + + else + { + for (next = addr; ; next = next->next) + { + DEBUG(D_deliver) debug_printf(" %s to def list\n", next->address); + if (!next->next) break; + } + next->next = addr_defer; + addr_defer = addr; + } + + continue; + } + + /* Set a flag indicating whether there are further addresses that list + the continued host. This tells the transport to leave the channel open, + but not to pass it to another delivery process. We'd like to do that + for non-continue_transport cases too but the knowlege of which host is + connected to is too hard to manage. Perhaps we need a finer-grain + interface to the transport. */ + + for (next = addr_remote; next && !f.continue_more; next = next->next) + { + host_item *h; + for (h = next->host_list; h; h = h->next) + if (Ustrcmp(h->name, continue_hostname) == 0) + { f.continue_more = TRUE; break; } + } + } + + /* The transports set up the process info themselves as they may connect + to more than one remote machine. They also have to set up the filter + arguments, if required, so that the host name and address are available + for expansion. */ + + transport_filter_argv = NULL; + + /* Create the pipe for inter-process communication. If pipe creation + fails, it is probably because the value of remote_max_parallel is so + large that too many file descriptors for pipes have been created. Arrange + to wait for a process to finish, and then try again. If we still can't + create a pipe when all processes have finished, break the retry loop. */ + + while (!pipe_done) + { + if (pipe(pfd) == 0) pipe_done = TRUE; + else if (parcount > 0) parmax = parcount; + else break; + + /* We need to make the reading end of the pipe non-blocking. There are + two different options for this. Exim is cunningly (I hope!) coded so + that it can use either of them, though it prefers O_NONBLOCK, which + distinguishes between EOF and no-more-data. */ + +/* The data appears in a timely manner and we already did a select on +all pipes, so I do not see a reason to use non-blocking IO here + +#ifdef O_NONBLOCK + (void)fcntl(pfd[pipe_read], F_SETFL, O_NONBLOCK); +#else + (void)fcntl(pfd[pipe_read], F_SETFL, O_NDELAY); +#endif +*/ + + /* If the maximum number of subprocesses already exist, wait for a process + to finish. If we ran out of file descriptors, parmax will have been reduced + from its initial value of remote_max_parallel. */ + + par_reduce(parmax - 1, fallback); + } + + /* If we failed to create a pipe and there were no processes to wait + for, we have to give up on this one. Do this outside the above loop + so that we can continue the main loop. */ + + if (!pipe_done) + { + panicmsg = string_sprintf("unable to create pipe: %s", strerror(errno)); + goto enq_continue; + } + + /* Find a free slot in the pardata list. Must do this after the possible + waiting for processes to finish, because a terminating process will free + up a slot. */ + + for (poffset = 0; poffset < remote_max_parallel; poffset++) + if (parlist[poffset].pid == 0) + break; + + /* If there isn't one, there has been a horrible disaster. */ + + if (poffset >= remote_max_parallel) + { + (void)close(pfd[pipe_write]); + (void)close(pfd[pipe_read]); + panicmsg = US"Unexpectedly no free subprocess slot"; + goto enq_continue; + } + + /* Now fork a subprocess to do the remote delivery, but before doing so, + ensure that any cached resources are released so as not to interfere with + what happens in the subprocess. */ + + search_tidyup(); + + if ((pid = fork()) == 0) + { + int fd = pfd[pipe_write]; + host_item *h; + + /* 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 */ + f.disable_logging = tp->disable_logging; + + /* Show pids on debug output if parallelism possible */ + + if (parmax > 1 && (parcount > 0 || addr_remote)) + { + DEBUG(D_any|D_v) debug_selector |= D_pid; + DEBUG(D_deliver) debug_printf("Remote delivery process started\n"); + } + + /* Reset the random number generator, so different processes don't all + have the same sequence. In the test harness we want different, but + predictable settings for each delivery process, so do something explicit + here rather they rely on the fixed reset in the random number function. */ + + random_seed = 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 + SMTP connection. */ + + (void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); + + /* Close open file descriptors for the pipes of other processes + that are running in parallel. */ + + for (poffset = 0; poffset < remote_max_parallel; poffset++) + if (parlist[poffset].pid != 0) (void)close(parlist[poffset].fd); + + /* This process has inherited a copy of the file descriptor + for the data file, but its file pointer is shared with all the + other processes running in parallel. Therefore, we have to re-open + the file in order to get a new file descriptor with its own + file pointer. We don't need to lock it, as the lock is held by + the parent process. There doesn't seem to be any way of doing + a dup-with-new-file-pointer. */ + + (void)close(deliver_datafile); + { + uschar * fname = spool_fname(US"input", message_subdir, message_id, US"-D"); + + if ((deliver_datafile = Uopen(fname, +#ifdef O_CLOEXEC + O_CLOEXEC | +#endif + O_RDWR | O_APPEND, 0)) < 0) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed to reopen %s for remote " + "parallel delivery: %s", fname, strerror(errno)); + } + + /* Set the close-on-exec flag */ +#ifndef O_CLOEXEC + (void)fcntl(deliver_datafile, F_SETFD, fcntl(deliver_datafile, F_GETFD) | + FD_CLOEXEC); +#endif + + /* Set the uid/gid of this process; bombs out on failure. */ + + exim_setugid(uid, gid, use_initgroups, + string_sprintf("remote delivery to %s with transport=%s", + addr->address, tp->name)); + + /* Close the unwanted half of this process' pipe, set the process state, + and run the transport. Afterwards, transport_count will contain the number + of bytes written. */ + + (void)close(pfd[pipe_read]); + set_process_info("delivering %s using %s", message_id, tp->name); + debug_print_string(tp->debug_string); + if (!(tp->info->code)(addr->transport, addr)) replicate_status(addr); + + set_process_info("delivering %s (just run %s for %s%s in subprocess)", + message_id, tp->name, addr->address, addr->next ? ", ..." : ""); + + /* Ensure any cached resources that we used are now released */ + + search_tidyup(); + + /* Pass the result back down the pipe. This is a lot more information + than is needed for a local delivery. We have to send back the error + status for each address, the usability status for each host that is + flagged as unusable, and all the retry items. When TLS is in use, we + send also the cipher and peerdn information. Each type of information + is flagged by an identifying byte, and is then in a fixed format (with + strings terminated by zeros), and there is a final terminator at the + end. The host information and retry information is all attached to + the first address, so that gets sent at the start. */ + + /* Host unusability information: for most success cases this will + be null. */ + + for (h = addr->host_list; h; h = h->next) + { + if (!h->address || h->status < hstatus_unusable) continue; + sprintf(CS big_buffer, "%c%c%s", h->status, h->why, h->address); + rmt_dlv_checked_write(fd, 'H', '0', big_buffer, Ustrlen(big_buffer+2) + 3); + } + + /* The number of bytes written. This is the same for each address. Even + if we sent several copies of the message down the same connection, the + size of each one is the same, and it's that value we have got because + transport_count gets reset before calling transport_write_message(). */ + + memcpy(big_buffer, &transport_count, sizeof(transport_count)); + rmt_dlv_checked_write(fd, 'S', '0', big_buffer, sizeof(transport_count)); + + /* Information about what happened to each address. Four item types are + used: an optional 'X' item first, for TLS information, then an optional "C" + item for any client-auth info followed by 'R' items for any retry settings, + and finally an 'A' item for the remaining data. */ + + 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 SUPPORT_DANE + if (tls_out.dane_verified) setflag(addr, af_dane_verified); +#endif + + /* Use an X item only if there's something to send */ +#ifdef SUPPORT_TLS + if (addr->cipher) + { + ptr = big_buffer + sprintf(CS big_buffer, "%.128s", addr->cipher) + 1; + if (!addr->peerdn) + *ptr++ = 0; + else + ptr += sprintf(CS ptr, "%.512s", addr->peerdn) + 1; + + rmt_dlv_checked_write(fd, 'X', '1', big_buffer, ptr - big_buffer); + } + else if (continue_proxy_cipher) + { + ptr = big_buffer + sprintf(CS big_buffer, "%.128s", continue_proxy_cipher) + 1; + *ptr++ = 0; + rmt_dlv_checked_write(fd, 'X', '1', big_buffer, ptr - big_buffer); + } + + if (addr->peercert) + { + ptr = big_buffer; + if (!tls_export_cert(ptr, big_buffer_size-2, addr->peercert)) + while(*ptr++); + else + *ptr++ = 0; + rmt_dlv_checked_write(fd, 'X', '2', big_buffer, ptr - big_buffer); + } + if (addr->ourcert) + { + ptr = big_buffer; + if (!tls_export_cert(ptr, big_buffer_size-2, addr->ourcert)) + while(*ptr++); + else + *ptr++ = 0; + rmt_dlv_checked_write(fd, 'X', '3', big_buffer, ptr - big_buffer); + } +# ifndef DISABLE_OCSP + if (addr->ocsp > OCSP_NOT_REQ) + { + ptr = big_buffer + sprintf(CS big_buffer, "%c", addr->ocsp + '0') + 1; + rmt_dlv_checked_write(fd, 'X', '4', big_buffer, ptr - big_buffer); + } +# endif +#endif /*SUPPORT_TLS*/ + + if (client_authenticator) + { + ptr = big_buffer + sprintf(CS big_buffer, "%.64s", client_authenticator) + 1; + rmt_dlv_checked_write(fd, 'C', '1', big_buffer, ptr - big_buffer); + } + if (client_authenticated_id) + { + ptr = big_buffer + sprintf(CS big_buffer, "%.64s", client_authenticated_id) + 1; + rmt_dlv_checked_write(fd, 'C', '2', big_buffer, ptr - big_buffer); + } + if (client_authenticated_sender) + { + ptr = big_buffer + sprintf(CS big_buffer, "%.64s", client_authenticated_sender) + 1; + rmt_dlv_checked_write(fd, 'C', '3', big_buffer, ptr - big_buffer); + } + +#ifndef DISABLE_PRDR + if (testflag(addr, af_prdr_used)) + rmt_dlv_checked_write(fd, 'P', '0', NULL, 0); +#endif + + if (testflag(addr, af_pipelining)) +#ifdef EXPERIMENTAL_PIPE_CONNECT + if (testflag(addr, af_early_pipe)) + rmt_dlv_checked_write(fd, 'L', '2', NULL, 0); + else +#endif + rmt_dlv_checked_write(fd, 'L', '1', NULL, 0); + + if (testflag(addr, af_chunking_used)) + rmt_dlv_checked_write(fd, 'K', '0', NULL, 0); + + if (testflag(addr, af_tcp_fastopen_conn)) + rmt_dlv_checked_write(fd, 'T', + testflag(addr, af_tcp_fastopen) ? 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) + { + sprintf(CS big_buffer, "%c%.500s", r->flags, r->key); + ptr = big_buffer + Ustrlen(big_buffer+2) + 3; + memcpy(ptr, &r->basic_errno, sizeof(r->basic_errno)); + ptr += sizeof(r->basic_errno); + memcpy(ptr, &r->more_errno, sizeof(r->more_errno)); + ptr += sizeof(r->more_errno); + if (!r->message) *ptr++ = 0; else + { + sprintf(CS ptr, "%.512s", r->message); + while(*ptr++); + } + rmt_dlv_checked_write(fd, 'R', '0', big_buffer, ptr - big_buffer); + } + +#ifdef SUPPORT_SOCKS + if (LOGGING(proxy) && proxy_session) + { + ptr = big_buffer; + if (proxy_local_address) + { + DEBUG(D_deliver) debug_printf("proxy_local_address '%s'\n", proxy_local_address); + ptr = big_buffer + sprintf(CS ptr, "%.128s", proxy_local_address) + 1; + DEBUG(D_deliver) debug_printf("proxy_local_port %d\n", proxy_local_port); + memcpy(ptr, &proxy_local_port, sizeof(proxy_local_port)); + ptr += sizeof(proxy_local_port); + } + else + *ptr++ = '\0'; + rmt_dlv_checked_write(fd, 'A', '2', big_buffer, ptr - big_buffer); + } +#endif + +#ifdef EXPERIMENTAL_DSN_INFO +/*um, are they really per-addr? Other per-conn stuff is not (auth, tls). But host_used is! */ + if (addr->smtp_greeting) + { + DEBUG(D_deliver) debug_printf("smtp_greeting '%s'\n", addr->smtp_greeting); + ptr = big_buffer + sprintf(CS big_buffer, "%.128s", addr->smtp_greeting) + 1; + if (addr->helo_response) + { + DEBUG(D_deliver) debug_printf("helo_response '%s'\n", addr->helo_response); + ptr += sprintf(CS ptr, "%.128s", addr->helo_response) + 1; + } + else + *ptr++ = '\0'; + rmt_dlv_checked_write(fd, 'A', '1', big_buffer, ptr - big_buffer); + } +#endif + + /* The rest of the information goes in an 'A0' item. */ + + sprintf(CS big_buffer, "%c%c", addr->transport_return, addr->special_action); + ptr = big_buffer + 2; + memcpy(ptr, &addr->basic_errno, sizeof(addr->basic_errno)); + 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->flags, sizeof(addr->flags)); + ptr += sizeof(addr->flags); + + if (!addr->message) *ptr++ = 0; else + ptr += sprintf(CS ptr, "%.1024s", addr->message) + 1; + + if (!addr->user_message) *ptr++ = 0; else + ptr += sprintf(CS ptr, "%.1024s", addr->user_message) + 1; + + if (!addr->host_used) *ptr++ = 0; else + { + ptr += sprintf(CS ptr, "%.256s", addr->host_used->name) + 1; + ptr += sprintf(CS ptr, "%.64s", addr->host_used->address) + 1; + memcpy(ptr, &addr->host_used->port, sizeof(addr->host_used->port)); + ptr += sizeof(addr->host_used->port); + + /* DNS lookup status */ + *ptr++ = addr->host_used->dnssec==DS_YES ? '2' + : addr->host_used->dnssec==DS_NO ? '1' : '0'; + + } + rmt_dlv_checked_write(fd, 'A', '0', big_buffer, ptr - big_buffer); + } + + /* Local interface address/port */ +#ifdef EXPERIMENTAL_DSN_INFO + if (sending_ip_address) +#else + if (LOGGING(incoming_interface) && sending_ip_address) +#endif + { + uschar * ptr; + ptr = big_buffer + sprintf(CS big_buffer, "%.128s", sending_ip_address) + 1; + ptr += sprintf(CS ptr, "%d", sending_port) + 1; + rmt_dlv_checked_write(fd, 'I', '0', big_buffer, ptr - big_buffer); + } + + /* Add termination flag, close the pipe, and that's it. The character + after 'Z' indicates whether continue_transport is now NULL or not. + A change from non-NULL to NULL indicates a problem with a continuing + connection. */ + + big_buffer[0] = continue_transport ? '1' : '0'; + rmt_dlv_checked_write(fd, 'Z', '0', big_buffer, 1); + (void)close(fd); + exit(EXIT_SUCCESS); + } + + /* Back in the mainline: close the unwanted half of the pipe. */ + + (void)close(pfd[pipe_write]); + + /* If we have a connection still open from a verify stage (lazy-close) + release its TLS library context (if any) as responsibility was passed to + the delivery child process. */ + + if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only) + { +#ifdef SUPPORT_TLS + if (cutthrough.is_tls) + tls_close(cutthrough.cctx.tls_ctx, TLS_NO_SHUTDOWN); +#endif + (void) close(cutthrough.cctx.sock); + release_cutthrough_connection(US"passed to transport proc"); + } + + /* Fork failed; defer with error message */ + + if (pid == -1) + { + (void)close(pfd[pipe_read]); + panicmsg = string_sprintf("fork failed for remote delivery to %s: %s", + addr->domain, strerror(errno)); + goto enq_continue; + } + + /* Fork succeeded; increment the count, and remember relevant data for + when the process finishes. */ + + parcount++; + parlist[poffset].addrlist = parlist[poffset].addr = addr; + parlist[poffset].pid = pid; + parlist[poffset].fd = pfd[pipe_read]; + parlist[poffset].done = FALSE; + parlist[poffset].msg = NULL; + parlist[poffset].return_path = return_path; + + /* If the process we've just started is sending a message down an existing + channel, wait for it now. This ensures that only one such process runs at + once, whatever the value of remote_max parallel. Otherwise, we might try to + send two or more messages simultaneously down the same channel. This could + happen if there are different domains that include the same host in otherwise + different host lists. + + Also, if the transport closes down the channel, this information gets back + (continue_transport gets set to NULL) before we consider any other addresses + in this message. */ + + if (continue_transport) par_reduce(0, fallback); + + /* 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. */ + + else if (f.running_in_test_harness) millisleep(500); + + continue; + +enq_continue: + if (serialize_key) enq_end(serialize_key); +panic_continue: + remote_post_process(addr, LOG_MAIN|LOG_PANIC, panicmsg, fallback); + continue; + } + +/* Reached the end of the list of addresses. Wait for all the subprocesses that +are still running and post-process their addresses. */ + +par_reduce(0, fallback); +return TRUE; +} + + + + +/************************************************* +* Split an address into local part and domain * +*************************************************/ + +/* This function initializes an address for routing by splitting it up into a +local part and a domain. The local part is set up twice - once in its original +casing, and once in lower case, and it is dequoted. We also do the "percent +hack" for configured domains. This may lead to a DEFER result if a lookup +defers. When a percent-hacking takes place, we insert a copy of the original +address as a new parent of this address, as if we have had a redirection. + +Argument: + addr points to an addr_item block containing the address + +Returns: OK + DEFER - could not determine if domain is %-hackable +*/ + +int +deliver_split_address(address_item * addr) +{ +uschar * address = addr->address; +uschar * domain; +uschar * t; +int len; + +if (!(domain = Ustrrchr(address, '@'))) + return DEFER; /* should always have a domain, but just in case... */ + +len = domain - address; +addr->domain = string_copylc(domain+1); /* Domains are always caseless */ + +/* The implication in the RFCs (though I can't say I've seen it spelled out +explicitly) is that quoting should be removed from local parts at the point +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); +while(len-- > 0) + { + int c = *address++; + if (c == '\"') continue; + if (c == '\\') + { + *t++ = *address++; + len--; + } + else *t++ = c; + } +*t = 0; + +/* We do the percent hack only for those domains that are listed in +percent_hack_domains. A loop is required, to copy with multiple %-hacks. */ + +if (percent_hack_domains) + { + int rc; + uschar *new_address = NULL; + uschar *local_part = addr->cc_local_part; + + deliver_domain = addr->domain; /* set $domain */ + + while ( (rc = match_isinlist(deliver_domain, (const uschar **)&percent_hack_domains, 0, + &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL)) + == OK + && (t = Ustrrchr(local_part, '%')) != NULL + ) + { + new_address = string_copy(local_part); + new_address[t - local_part] = '@'; + deliver_domain = string_copylc(t+1); + local_part = string_copyn(local_part, t - local_part); + } + + if (rc == DEFER) return DEFER; /* lookup deferred */ + + /* If hackery happened, set up new parent and alter the current address. */ + + if (new_address) + { + address_item *new_parent = store_get(sizeof(address_item)); + *new_parent = *addr; + addr->parent = new_parent; + new_parent->child_count = 1; + addr->address = new_address; + addr->unique = string_copy(new_address); + addr->domain = deliver_domain; + addr->cc_local_part = local_part; + DEBUG(D_deliver) debug_printf("%%-hack changed address to: %s\n", + addr->address); + } + } + +/* Create the lowercased version of the final local part, and make that the +default one to be used. */ + +addr->local_part = addr->lc_local_part = string_copylc(addr->cc_local_part); +return OK; +} + + + + +/************************************************* +* Get next error message text * +*************************************************/ + +/* If f is not NULL, read the next "paragraph", from a customized error message +text file, terminated by a line containing ****, and expand it. + +Arguments: + f NULL or a file to read from + which string indicating which string (for errors) + +Returns: NULL or an expanded string +*/ + +static uschar * +next_emf(FILE *f, uschar *which) +{ +uschar *yield; +gstring * para; +uschar buffer[256]; + +if (!f) return NULL; + +if (!Ufgets(buffer, sizeof(buffer), f) || Ustrcmp(buffer, "****\n") == 0) + return NULL; + +para = string_get(256); +for (;;) + { + para = string_cat(para, buffer); + if (!Ufgets(buffer, sizeof(buffer), f) || Ustrcmp(buffer, "****\n") == 0) + break; + } +if ((yield = expand_string(string_from_gstring(para)))) + return yield; + +log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand string from " + "bounce_message_file or warn_message_file (%s): %s", which, + expand_string_message); +return NULL; +} + + + + +/************************************************* +* Close down a passed transport channel * +*************************************************/ + +/* This function is called when a passed transport channel cannot be used. +It attempts to close it down tidily. The yield is always DELIVER_NOT_ATTEMPTED +so that the function call can be the argument of a "return" statement. + +Arguments: None +Returns: DELIVER_NOT_ATTEMPTED +*/ + +static int +continue_closedown(void) +{ +if (continue_transport) + { + transport_instance *t; + for (t = transports; t; t = t->next) + if (Ustrcmp(t->name, continue_transport) == 0) + { + if (t->info->closedown) (t->info->closedown)(t); + break; + } + } +return DELIVER_NOT_ATTEMPTED; +} + + + + +/************************************************* +* Print address information * +*************************************************/ + +/* This function is called to output an address, or information about an +address, for bounce or defer messages. If the hide_child flag is set, all we +output is the original ancestor address. + +Arguments: + addr points to the address + f the FILE to print to + si an initial string + sc a continuation string for before "generated" + se an end string + +Returns: TRUE if the address is not hidden +*/ + +static BOOL +print_address_information(address_item *addr, FILE *f, uschar *si, uschar *sc, + uschar *se) +{ +BOOL yield = TRUE; +uschar *printed = US""; +address_item *ancestor = addr; +while (ancestor->parent) ancestor = ancestor->parent; + +fprintf(f, "%s", CS si); + +if (addr->parent && testflag(addr, af_hide_child)) + { + printed = US"an undisclosed address"; + yield = FALSE; + } +else if (!testflag(addr, af_pfr) || !addr->parent) + printed = addr->address; + +else + { + uschar *s = addr->address; + uschar *ss; + + if (addr->address[0] == '>') { ss = US"mail"; s++; } + else if (addr->address[0] == '|') ss = US"pipe"; + else ss = US"save"; + + fprintf(f, "%s to %s%sgenerated by ", ss, s, sc); + printed = addr->parent->address; + } + +fprintf(f, "%s", CS string_printing(printed)); + +if (ancestor != addr) + { + uschar *original = ancestor->onetime_parent; + if (!original) original= ancestor->address; + if (strcmpic(original, printed) != 0) + fprintf(f, "%s(%sgenerated from %s)", sc, + ancestor != addr->parent ? "ultimately " : "", + string_printing(original)); + } + +if (addr->host_used) + fprintf(f, "\n host %s [%s]", + addr->host_used->name, addr->host_used->address); + +fprintf(f, "%s", CS se); +return yield; +} + + + + + +/************************************************* +* Print error for an address * +*************************************************/ + +/* This function is called to print the error information out of an address for +a bounce or a warning message. It tries to format the message reasonably by +introducing newlines. All lines are indented by 4; the initial printing +position must be set before calling. + +This function used always to print the error. Nowadays we want to restrict it +to cases such as LMTP/SMTP errors from a remote host, and errors from :fail: +and filter "fail". We no longer pass other information willy-nilly in bounce +and warning messages. Text in user_message is always output; text in message +only if the af_pass_message flag is set. + +Arguments: + addr the address + f the FILE to print on + t some leading text + +Returns: nothing +*/ + +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; + +if (!s && !(s = addr->user_message)) + return; + +fprintf(f, "\n %s", t); + +while (*s) + if (*s == '\\' && s[1] == 'n') + { + fprintf(f, "\n "); + s += 2; + count = 0; + } + else + { + fputc(*s, f); + count++; + if (*s++ == ':' && isspace(*s) && count > 45) + { + fprintf(f, "\n "); /* sic (because space follows) */ + count = 0; + } + } +} + + +/*********************************************************** +* Print Diagnostic-Code for an address * +************************************************************/ + +/* This function is called to print the error information out of an address for +a bounce or a warning message. It tries to format the message reasonably as +required by RFC 3461 by adding a space after each newline + +it uses the same logic as print_address_error() above. if af_pass_message is true +and addr->message is set it uses the remote host answer. if not addr->user_message +is used instead if available. + +Arguments: + addr the address + f the FILE to print on + +Returns: nothing +*/ + +static void +print_dsn_diagnostic_code(const address_item *addr, FILE *f) +{ +uschar *s = testflag(addr, af_pass_message) ? addr->message : NULL; + +/* af_pass_message and addr->message set ? print remote host answer */ +if (s) + { + DEBUG(D_deliver) + debug_printf("DSN Diagnostic-Code: addr->message = %s\n", addr->message); + + /* search first ": ". we assume to find the remote-MTA answer there */ + if (!(s = Ustrstr(addr->message, ": "))) + return; /* not found, bail out */ + s += 2; /* skip ": " */ + fprintf(f, "Diagnostic-Code: smtp; "); + } +/* no message available. do nothing */ +else return; + +while (*s) + if (*s == '\\' && s[1] == 'n') + { + fputs("\n ", f); /* as defined in RFC 3461 */ + s += 2; + } + else + fputc(*s++, f); + +fputc('\n', f); +} + + +/************************************************* +* Check list of addresses for duplication * +*************************************************/ + +/* This function was introduced when the test for duplicate addresses that are +not pipes, files, or autoreplies was moved from the middle of routing to when +routing was complete. That was to fix obscure cases when the routing history +affects the subsequent routing of identical addresses. This function is called +after routing, to check that the final routed addresses are not duplicates. + +If we detect a duplicate, we remember what it is a duplicate of. Note that +pipe, file, and autoreply de-duplication is handled during routing, so we must +leave such "addresses" alone here, as otherwise they will incorrectly be +discarded. + +Argument: address of list anchor +Returns: nothing +*/ + +static void +do_duplicate_check(address_item **anchor) +{ +address_item *addr; +while ((addr = *anchor)) + { + tree_node *tnode; + if (testflag(addr, af_pfr)) + { + anchor = &(addr->next); + } + else if ((tnode = tree_search(tree_duplicates, addr->unique))) + { + DEBUG(D_deliver|D_route) + debug_printf("%s is a duplicate address: discarded\n", addr->unique); + *anchor = addr->next; + addr->dupof = tnode->data.ptr; + addr->next = addr_duplicate; + addr_duplicate = addr; + } + else + { + tree_add_duplicate(addr->unique, addr); + anchor = &(addr->next); + } + } +} + + + + +/************************************************* +* Deliver one message * +*************************************************/ + +/* This is the function which is called when a message is to be delivered. It +is passed the id of the message. It is possible that the message no longer +exists, if some other process has delivered it, and it is also possible that +the message is being worked on by another process, in which case the data file +will be locked. + +If no delivery is attempted for any of the above reasons, the function returns +DELIVER_NOT_ATTEMPTED. + +If the give_up flag is set true, do not attempt any deliveries, but instead +fail all outstanding addresses and return the message to the sender (or +whoever). + +A delivery operation has a process all to itself; we never deliver more than +one message in the same process. Therefore we needn't worry too much about +store leakage. + +Liable to be called as root. + +Arguments: + id the id of the message to be delivered + forced TRUE if delivery was forced by an administrator; this overrides + retry delays and causes a delivery to be tried regardless + give_up TRUE if an administrator has requested that delivery attempts + be abandoned + +Returns: When the global variable mua_wrapper is FALSE: + DELIVER_ATTEMPTED_NORMAL if a delivery attempt was made + DELIVER_NOT_ATTEMPTED otherwise (see comment above) + When the global variable mua_wrapper is TRUE: + DELIVER_MUA_SUCCEEDED if delivery succeeded + DELIVER_MUA_FAILED if delivery failed + DELIVER_NOT_ATTEMPTED if not attempted (should not occur) +*/ + +int +deliver_message(uschar *id, BOOL forced, BOOL give_up) +{ +int i, rc; +int final_yield = DELIVER_ATTEMPTED_NORMAL; +time_t now = time(NULL); +address_item *addr_last = NULL; +uschar *filter_message = NULL; +int process_recipients = RECIP_ACCEPT; +open_db dbblock; +open_db *dbm_file; +extern int acl_where; + +uschar *info = queue_run_pid == (pid_t)0 + ? string_sprintf("delivering %s", id) + : string_sprintf("delivering %s (queue run pid %d)", id, queue_run_pid); + +/* If the D_process_info bit is on, set_process_info() will output debugging +information. If not, we want to show this initial information if D_deliver or +D_queue_run is set or in verbose mode. */ + +set_process_info("%s", info); + +if ( !(debug_selector & D_process_info) + && (debug_selector & (D_deliver|D_queue_run|D_v)) + ) + debug_printf("%s\n", info); + +/* Ensure that we catch any subprocesses that are created. Although Exim +sets SIG_DFL as its initial default, some routes through the code end up +here with it set to SIG_IGN - cases where a non-synchronous delivery process +has been forked, but no re-exec has been done. We use sigaction rather than +plain signal() on those OS where SA_NOCLDWAIT exists, because we want to be +sure it is turned off. (There was a problem on AIX with this.) */ + +#ifdef SA_NOCLDWAIT + { + struct sigaction act; + act.sa_handler = SIG_DFL; + sigemptyset(&(act.sa_mask)); + act.sa_flags = 0; + sigaction(SIGCHLD, &act, NULL); + } +#else +signal(SIGCHLD, SIG_DFL); +#endif + +/* Make the forcing flag available for routers and transports, set up the +global message id field, and initialize the count for returned files and the +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. */ + +if (id != message_id) + Ustrcpy(message_id, id); +f.deliver_force = forced; +return_count = 0; +message_size = 0; + +/* Initialize some flags */ + +update_spool = FALSE; +remove_journal = TRUE; + +/* Set a known context for any ACLs we call via expansions */ +acl_where = ACL_WHERE_DELIVERY; + +/* Reset the random number generator, so that if several delivery processes are +started from a queue runner that has already used random numbers (for sorting), +they don't all get the same sequence. */ + +random_seed = 0; + +/* Open and lock the message's data file. Exim locks on this one because the +header file may get replaced as it is re-written during the delivery process. +Any failures cause messages to be written to the log, except for missing files +while queue running - another process probably completed delivery. As part of +opening the data file, message_subdir gets set. */ + +if ((deliver_datafile = spool_open_datafile(id)) < 0) + return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */ + +/* The value of message_size at this point has been set to the data length, +plus one for the blank line that notionally precedes the data. */ + +/* Now read the contents of the header file, which will set up the headers in +store, and also the list of recipients and the tree of non-recipients and +assorted flags. It updates message_size. If there is a reading or format error, +give up; if the message has been around for sufficiently long, remove it. */ + + { + uschar * spoolname = string_sprintf("%s-H", id); + if ((rc = spool_read_header(spoolname, TRUE, TRUE)) != spool_read_OK) + { + if (errno == ERRNO_SPOOLFORMAT) + { + struct stat statbuf; + if (Ustat(spool_fname(US"input", message_subdir, spoolname, US""), + &statbuf) == 0) + log_write(0, LOG_MAIN, "Format error in spool file %s: " + "size=" OFF_T_FMT, spoolname, statbuf.st_size); + else + log_write(0, LOG_MAIN, "Format error in spool file %s", spoolname); + } + else + log_write(0, LOG_MAIN, "Error reading spool file %s: %s", spoolname, + strerror(errno)); + + /* If we managed to read the envelope data, received_time contains the + time the message was received. Otherwise, we can calculate it from the + message id. */ + + if (rc != spool_read_hdrerror) + { + received_time.tv_sec = received_time.tv_usec = 0; + /*XXX subsec precision?*/ + for (i = 0; i < 6; i++) + received_time.tv_sec = received_time.tv_sec * BASE_62 + tab62[id[i] - '0']; + } + + /* If we've had this malformed message too long, sling it. */ + + if (now - received_time.tv_sec > keep_malformed) + { + Uunlink(spool_fname(US"msglog", message_subdir, id, US"")); + Uunlink(spool_fname(US"input", message_subdir, id, US"-D")); + Uunlink(spool_fname(US"input", message_subdir, id, US"-H")); + Uunlink(spool_fname(US"input", message_subdir, id, US"-J")); + log_write(0, LOG_MAIN, "Message removed because older than %s", + readconf_printtime(keep_malformed)); + } + + (void)close(deliver_datafile); + deliver_datafile = -1; + return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */ + } + } + +/* The spool header file has been read. Look to see if there is an existing +journal file for this message. If there is, it means that a previous delivery +attempt crashed (program or host) before it could update the spool header file. +Read the list of delivered addresses from the journal and add them to the +nonrecipients tree. Then update the spool file. We can leave the journal in +existence, as it will get further successful deliveries added to it in this +run, and it will be deleted if this function gets to its end successfully. +Otherwise it might be needed again. */ + + { + uschar * fname = spool_fname(US"input", message_subdir, id, US"-J"); + FILE * jread; + + if ( (journal_fd = Uopen(fname, O_RDWR|O_APPEND +#ifdef O_CLOEXEC + | O_CLOEXEC +#endif +#ifdef O_NOFOLLOW + | O_NOFOLLOW +#endif + , SPOOL_MODE)) >= 0 + && lseek(journal_fd, 0, SEEK_SET) == 0 + && (jread = fdopen(journal_fd, "rb")) + ) + { + while (Ufgets(big_buffer, big_buffer_size, jread)) + { + int n = Ustrlen(big_buffer); + big_buffer[n-1] = 0; + tree_add_nonrecipient(big_buffer); + DEBUG(D_deliver) debug_printf("Previously delivered address %s taken from " + "journal file\n", big_buffer); + } + rewind(jread); + if ((journal_fd = dup(fileno(jread))) < 0) + journal_fd = fileno(jread); + else + (void) fclose(jread); /* Try to not leak the FILE resource */ + + /* Panic-dies on error */ + (void)spool_write_header(message_id, SW_DELIVERING, NULL); + } + else if (errno != ENOENT) + { + log_write(0, LOG_MAIN|LOG_PANIC, "attempt to open journal for reading gave: " + "%s", strerror(errno)); + return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */ + } + + /* A null recipients list indicates some kind of disaster. */ + + if (!recipients_list) + { + (void)close(deliver_datafile); + deliver_datafile = -1; + log_write(0, LOG_MAIN, "Spool error: no recipients for %s", fname); + return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */ + } + } + + +/* Handle a message that is frozen. There are a number of different things that +can happen, but in the default situation, unless forced, no delivery is +attempted. */ + +if (f.deliver_freeze) + { +#ifdef SUPPORT_MOVE_FROZEN_MESSAGES + /* Moving to another directory removes the message from Exim's view. Other + tools must be used to deal with it. Logging of this action happens in + spool_move_message() and its subfunctions. */ + + if ( move_frozen_messages + && spool_move_message(id, message_subdir, US"", US"F") + ) + return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */ +#endif + + /* For all frozen messages (bounces or not), timeout_frozen_after sets the + maximum time to keep messages that are frozen. Thaw if we reach it, with a + flag causing all recipients to be failed. The time is the age of the + message, not the time since freezing. */ + + if (timeout_frozen_after > 0 && message_age >= timeout_frozen_after) + { + log_write(0, LOG_MAIN, "cancelled by timeout_frozen_after"); + process_recipients = RECIP_FAIL_TIMEOUT; + } + + /* For bounce messages (and others with no sender), thaw if the error message + ignore timer is exceeded. The message will be discarded if this delivery + fails. */ + + else if (!*sender_address && message_age >= ignore_bounce_errors_after) + log_write(0, LOG_MAIN, "Unfrozen by errmsg timer"); + + /* If this is a bounce message, or there's no auto thaw, or we haven't + reached the auto thaw time yet, and this delivery is not forced by an admin + user, do not attempt delivery of this message. Note that forced is set for + continuing messages down the same channel, in order to skip load checking and + ignore hold domains, but we don't want unfreezing in that case. */ + + else + { + if ( ( sender_address[0] == 0 + || auto_thaw <= 0 + || now <= deliver_frozen_at + auto_thaw + ) + && ( !forced || !f.deliver_force_thaw + || !f.admin_user || continue_hostname + ) ) + { + (void)close(deliver_datafile); + deliver_datafile = -1; + log_write(L_skip_delivery, LOG_MAIN, "Message is frozen"); + return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */ + } + + /* If delivery was forced (by an admin user), assume a manual thaw. + Otherwise it's an auto thaw. */ + + if (forced) + { + f.deliver_manual_thaw = TRUE; + log_write(0, LOG_MAIN, "Unfrozen by forced delivery"); + } + else log_write(0, LOG_MAIN, "Unfrozen by auto-thaw"); + } + + /* We get here if any of the rules for unfreezing have triggered. */ + + f.deliver_freeze = FALSE; + update_spool = TRUE; + } + + +/* Open the message log file if we are using them. This records details of +deliveries, deferments, and failures for the benefit of the mail administrator. +The log is not used by exim itself to track the progress of a message; that is +done by rewriting the header spool file. */ + +if (message_logs) + { + uschar * fname = spool_fname(US"msglog", message_subdir, id, US""); + uschar * error; + int fd; + + if ((fd = open_msglog_file(fname, SPOOL_MODE, &error)) < 0) + { + log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't %s message log %s: %s", error, + fname, strerror(errno)); + return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */ + } + + /* Make a C stream out of it. */ + + if (!(message_log = fdopen(fd, "a"))) + { + log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't fdopen message log %s: %s", + fname, strerror(errno)); + return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */ + } + } + + +/* If asked to give up on a message, log who did it, and set the action for all +the addresses. */ + +if (give_up) + { + struct passwd *pw = getpwuid(real_uid); + log_write(0, LOG_MAIN, "cancelled by %s", + pw ? US pw->pw_name : string_sprintf("uid %ld", (long int)real_uid)); + process_recipients = RECIP_FAIL; + } + +/* Otherwise, if there are too many Received: headers, fail all recipients. */ + +else if (received_count > received_headers_max) + process_recipients = RECIP_FAIL_LOOP; + +/* Otherwise, if a system-wide, address-independent message filter is +specified, run it now, except in the case when we are failing all recipients as +a result of timeout_frozen_after. If the system filter yields "delivered", then +ignore the true recipients of the message. Failure of the filter file is +logged, and the delivery attempt fails. */ + +else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT) + { + int rc; + int filtertype; + ugid_block ugid; + redirect_block redirect; + + if (system_filter_uid_set) + { + ugid.uid = system_filter_uid; + ugid.gid = system_filter_gid; + ugid.uid_set = ugid.gid_set = TRUE; + } + else + ugid.uid_set = ugid.gid_set = FALSE; + + return_path = sender_address; + 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. */ + + redirect.string = system_filter; + redirect.isfile = TRUE; + redirect.check_owner = redirect.check_group = FALSE; + redirect.owners = NULL; + redirect.owngroups = NULL; + redirect.pw = NULL; + redirect.modemask = 0; + + DEBUG(D_deliver|D_filter) debug_printf("running system filter\n"); + + rc = rda_interpret( + &redirect, /* Where the data is */ + RDO_DEFER | /* Turn on all the enabling options */ + RDO_FAIL | /* Leave off all the disabling options */ + RDO_FILTER | + RDO_FREEZE | + RDO_REALLOG | + RDO_REWRITE, + NULL, /* No :include: restriction (not used in filter) */ + NULL, /* No sieve vacation directory (not sieve!) */ + NULL, /* No sieve enotify mailto owner (not sieve!) */ + NULL, /* No sieve user address (not sieve!) */ + NULL, /* No sieve subaddress (not sieve!) */ + &ugid, /* uid/gid data */ + &addr_new, /* Where to hang generated addresses */ + &filter_message, /* Where to put error message */ + NULL, /* Don't skip syntax errors */ + &filtertype, /* Will always be set to FILTER_EXIM for this call */ + US"system filter"); /* For error messages */ + + DEBUG(D_deliver|D_filter) debug_printf("system filter returned %d\n", rc); + + if (rc == FF_ERROR || rc == FF_NONEXIST) + { + (void)close(deliver_datafile); + deliver_datafile = -1; + log_write(0, LOG_MAIN|LOG_PANIC, "Error in system filter: %s", + string_printing(filter_message)); + return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */ + } + + /* 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. */ + + 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 + can use them. */ + + memcpy(filter_sn, filter_n, sizeof(filter_sn)); + + /* The filter can request that delivery of the original addresses be + deferred. */ + + if (rc == FF_DEFER) + { + process_recipients = RECIP_DEFER; + deliver_msglog("Delivery deferred by system filter\n"); + log_write(0, LOG_MAIN, "Delivery deferred by system filter"); + } + + /* The filter can request that a message be frozen, but this does not + take place if the message has been manually thawed. In that case, we must + unset "delivered", which is forced by the "freeze" command to make -bF + work properly. */ + + else if (rc == FF_FREEZE && !f.deliver_manual_thaw) + { + f.deliver_freeze = TRUE; + deliver_frozen_at = time(NULL); + process_recipients = RECIP_DEFER; + frozen_info = string_sprintf(" by the system filter%s%s", + filter_message ? US": " : US"", + filter_message ? filter_message : US""); + } + + /* The filter can request that a message be failed. The error message may be + quite long - it is sent back to the sender in the bounce - but we don't want + to fill up the log with repetitions of it. If it starts with << then the text + between << and >> is written to the log, with the rest left for the bounce + message. */ + + else if (rc == FF_FAIL) + { + uschar *colon = US""; + uschar *logmsg = US""; + int loglen = 0; + + process_recipients = RECIP_FAIL_FILTER; + + if (filter_message) + { + uschar *logend; + colon = US": "; + if ( filter_message[0] == '<' + && filter_message[1] == '<' + && (logend = Ustrstr(filter_message, ">>")) + ) + { + logmsg = filter_message + 2; + loglen = logend - logmsg; + filter_message = logend + 2; + if (filter_message[0] == 0) filter_message = NULL; + } + else + { + logmsg = filter_message; + loglen = Ustrlen(filter_message); + } + } + + log_write(0, LOG_MAIN, "cancelled by system filter%s%.*s", colon, loglen, + logmsg); + } + + /* Delivery can be restricted only to those recipients (if any) that the + filter specified. */ + + else if (rc == FF_DELIVERED) + { + process_recipients = RECIP_IGNORE; + if (addr_new) + log_write(0, LOG_MAIN, "original recipients ignored (system filter)"); + else + log_write(0, LOG_MAIN, "=> discarded (system filter)"); + } + + /* If any new addresses were created by the filter, fake up a "parent" + for them. This is necessary for pipes, etc., which are expected to have + parents, and it also gives some sensible logging for others. Allow + pipes, files, and autoreplies, and run them as the filter uid if set, + otherwise as the current uid. */ + + if (addr_new) + { + 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 + $recipients, so don't change it here without also changing it there. */ + + address_item *p = addr_new; + address_item *parent = deliver_make_addr(US"system-filter", FALSE); + + parent->domain = string_copylc(qualify_domain_recipient); + parent->local_part = US"system-filter"; + + /* As part of this loop, we arrange for addr_last to end up pointing + at the final address. This is used if we go on to add addresses for the + original recipients. */ + + while (p) + { + if (parent->child_count == USHRT_MAX) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "system filter generated more " + "than %d delivery addresses", USHRT_MAX); + parent->child_count++; + p->parent = parent; + + if (testflag(p, af_pfr)) + { + uschar *tpname; + uschar *type; + p->uid = uid; + p->gid = gid; + setflag(p, af_uid_set); + setflag(p, af_gid_set); + setflag(p, af_allow_file); + setflag(p, af_allow_pipe); + setflag(p, af_allow_reply); + + /* Find the name of the system filter's appropriate pfr transport */ + + if (p->address[0] == '|') + { + type = US"pipe"; + tpname = system_filter_pipe_transport; + address_pipe = p->address; + } + else if (p->address[0] == '>') + { + type = US"reply"; + tpname = system_filter_reply_transport; + } + else + { + if (p->address[Ustrlen(p->address)-1] == '/') + { + type = US"directory"; + tpname = system_filter_directory_transport; + } + else + { + type = US"file"; + tpname = system_filter_file_transport; + } + address_file = p->address; + } + + /* Now find the actual transport, first expanding the name. We have + set address_file or address_pipe above. */ + + if (tpname) + { + uschar *tmp = expand_string(tpname); + address_file = address_pipe = NULL; + if (!tmp) + p->message = string_sprintf("failed to expand \"%s\" as a " + "system filter transport name", tpname); + tpname = tmp; + } + else + p->message = string_sprintf("system_filter_%s_transport is unset", + type); + + if (tpname) + { + transport_instance *tp; + for (tp = transports; tp; tp = tp->next) + if (Ustrcmp(tp->name, tpname) == 0) + { + p->transport = tp; + break; + } + if (!tp) + p->message = string_sprintf("failed to find \"%s\" transport " + "for system filter delivery", tpname); + } + + /* If we couldn't set up a transport, defer the delivery, putting the + error on the panic log as well as the main log. */ + + if (!p->transport) + { + address_item *badp = p; + 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, EXIM_DTYPE_ROUTER, 0); + continue; + } + } /* End of pfr handling */ + + /* Either a non-pfr delivery, or we found a transport */ + + DEBUG(D_deliver|D_filter) + debug_printf("system filter added %s\n", p->address); + + addr_last = p; + p = p->next; + } /* Loop through all addr_new addresses */ + } + } + + +/* Scan the recipients list, and for every one that is not in the non- +recipients tree, add an addr item to the chain of new addresses. If the pno +value is non-negative, we must set the onetime parent from it. This which +points to the relevant entry in the recipients list. + +This processing can be altered by the setting of the process_recipients +variable, which is changed if recipients are to be ignored, failed, or +deferred. This can happen as a result of system filter activity, or if the -Mg +option is used to fail all of them. + +Duplicate addresses are handled later by a different tree structure; we can't +just extend the non-recipients tree, because that will be re-written to the +spool if the message is deferred, and in any case there are casing +complications for local addresses. */ + +if (process_recipients != RECIP_IGNORE) + for (i = 0; i < recipients_count; i++) + if (!tree_search(tree_nonrecipients, recipients_list[i].address)) + { + recipient_item *r = recipients_list + i; + address_item *new = deliver_make_addr(r->address, FALSE); + new->prop.errors_address = r->errors_to; +#ifdef SUPPORT_I18N + if ((new->prop.utf8_msg = message_smtputf8)) + { + new->prop.utf8_downcvt = message_utf8_downconvert == 1; + new->prop.utf8_downcvt_maybe = message_utf8_downconvert == -1; + DEBUG(D_deliver) debug_printf("utf8, downconvert %s\n", + new->prop.utf8_downcvt ? "yes" + : new->prop.utf8_downcvt_maybe ? "ifneeded" + : "no"); + } +#endif + + if (r->pno >= 0) + 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 */ + 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_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; + + + /* 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 */ + + + /* RECIP_FAIL_TIMEOUT is set when a message is frozen, but is older + than the value in timeout_frozen_after. Treat non-bounce messages + similarly to -Mg; for bounce messages we just want to discard, so + don't put the address on the failed list. The timeout has already + been logged. */ + + case RECIP_FAIL_TIMEOUT: + 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 */ + + /* 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 + create a bounce. Otherwise do nothing - this just discards the address. + The incident has already been logged. */ + + RECIP_QUEUE_FAILED: + if (sender_address[0]) + { + new->next = addr_failed; + addr_failed = new; + } + break; + + + /* RECIP_FAIL_LOOP is set when there are too many Received: headers + in the message. Process each address as a routing failure; if this + is a bounce message, it will get frozen. */ + + case RECIP_FAIL_LOOP: + new->message = US"Too many \"Received\" headers - suspected mail loop"; + post_process_one(new, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); + break; + + + /* 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; + } + +#ifndef DISABLE_EVENT + if (process_recipients != RECIP_ACCEPT) + { + uschar * save_local = deliver_localpart; + const uschar * save_domain = deliver_domain; + uschar * addr = new->address, * errmsg = NULL; + int start, end, dom; + + 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""; + + event_raise(event_action, US"msg:fail:internal", new->message); + + 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) + debug_printf(" %s %s\n", p->address, + p->onetime_parent ? p->onetime_parent : US""); + } + +/* Set up the buffers used for copying over the file when delivering. */ + +deliver_in_buffer = store_malloc(DELIVER_IN_BUFFER_SIZE); +deliver_out_buffer = store_malloc(DELIVER_OUT_BUFFER_SIZE); + + + +/* Until there are no more new addresses, handle each one as follows: + + . If this is a generated address (indicated by the presence of a parent + pointer) then check to see whether it is a pipe, file, or autoreply, and + if so, handle it directly here. The router that produced the address will + have set the allow flags into the address, and also set the uid/gid required. + Having the routers generate new addresses and then checking them here at + the outer level is tidier than making each router do the checking, and + means that routers don't need access to the failed address queue. + + . Break up the address into local part and domain, and make lowercased + versions of these strings. We also make unquoted versions of the local part. + + . Handle the percent hack for those domains for which it is valid. + + . For child addresses, determine if any of the parents have the same address. + If so, generate a different string for previous delivery checking. Without + this code, if the address spqr generates spqr via a forward or alias file, + delivery of the generated spqr stops further attempts at the top level spqr, + which is not what is wanted - it may have generated other addresses. + + . Check on the retry database to see if routing was previously deferred, but + only if in a queue run. Addresses that are to be routed are put on the + addr_route chain. Addresses that are to be deferred are put on the + addr_defer chain. We do all the checking first, so as not to keep the + retry database open any longer than necessary. + + . Now we run the addresses through the routers. A router may put the address + on either the addr_local or the addr_remote chain for local or remote + delivery, respectively, or put it on the addr_failed chain if it is + undeliveable, or it may generate child addresses and put them on the + addr_new chain, or it may defer an address. All the chain anchors are + passed as arguments so that the routers can be called for verification + purposes as well. + + . If new addresses have been generated by the routers, da capo. +*/ + +f.header_rewritten = FALSE; /* No headers rewritten yet */ +while (addr_new) /* Loop until all addresses dealt with */ + { + address_item *addr, *parent; + + /* Failure to open the retry database is treated the same as if it does + not exist. In both cases, dbm_file is NULL. */ + + if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE))) + DEBUG(D_deliver|D_retry|D_route|D_hints_lookup) + debug_printf("no retry data available\n"); + + /* Scan the current batch of new addresses, to handle pipes, files and + autoreplies, and determine which others are ready for routing. */ + + while (addr_new) + { + int rc; + uschar *p; + tree_node *tnode; + dbdata_retry *domain_retry_record; + dbdata_retry *address_retry_record; + + addr = addr_new; + addr_new = addr->next; + + DEBUG(D_deliver|D_retry|D_route) + { + debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); + debug_printf("Considering: %s\n", addr->address); + } + + /* Handle generated address that is a pipe or a file or an autoreply. */ + + if (testflag(addr, af_pfr)) + { + /* If an autoreply in a filter could not generate a syntactically valid + address, give up forthwith. Set af_ignore_error so that we don't try to + generate a bounce. */ + + if (testflag(addr, af_bad_reply)) + { + addr->basic_errno = ERRNO_BADADDRESS2; + addr->local_part = addr->address; + addr->message = + US"filter autoreply generated syntactically invalid recipient"; + addr->prop.ignore_error = TRUE; + (void) post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); + continue; /* with the next new address */ + } + + /* If two different users specify delivery to the same pipe or file or + autoreply, there should be two different deliveries, so build a unique + string that incorporates the original address, and use this for + duplicate testing and recording delivery, and also for retrying. */ + + addr->unique = + string_sprintf("%s:%s", addr->address, addr->parent->unique + + (testflag(addr->parent, af_homonym)? 3:0)); + + addr->address_retry_key = addr->domain_retry_key = + string_sprintf("T:%s", addr->unique); + + /* If a filter file specifies two deliveries to the same pipe or file, + we want to de-duplicate, but this is probably not wanted for two mail + commands to the same address, where probably both should be delivered. + So, we have to invent a different unique string in that case. Just + 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))) + { + DEBUG(D_deliver|D_route) + debug_printf("%s is a duplicate address: discarded\n", addr->address); + addr->dupof = tnode->data.ptr; + addr->next = addr_duplicate; + addr_duplicate = addr; + continue; + } + + DEBUG(D_deliver|D_route) debug_printf("unique = %s\n", addr->unique); + + /* Check for previous delivery */ + + if (tree_search(tree_nonrecipients, addr->unique)) + { + DEBUG(D_deliver|D_route) + debug_printf("%s was previously delivered: discarded\n", addr->address); + child_done(addr, tod_stamp(tod_log)); + continue; + } + + /* Save for checking future duplicates */ + + tree_add_duplicate(addr->unique, addr); + + /* Set local part and domain */ + + addr->local_part = addr->address; + addr->domain = addr->parent->domain; + + /* Ensure that the delivery is permitted. */ + + if (testflag(addr, af_file)) + { + if (!testflag(addr, af_allow_file)) + { + addr->basic_errno = ERRNO_FORBIDFILE; + addr->message = US"delivery to file forbidden"; + (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); + continue; /* with the next new address */ + } + } + else if (addr->address[0] == '|') + { + if (!testflag(addr, af_allow_pipe)) + { + addr->basic_errno = ERRNO_FORBIDPIPE; + addr->message = US"delivery to pipe forbidden"; + (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); + continue; /* with the next new address */ + } + } + else if (!testflag(addr, af_allow_reply)) + { + addr->basic_errno = ERRNO_FORBIDREPLY; + addr->message = US"autoreply forbidden"; + (void)post_process_one(addr, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); + continue; /* with the next new address */ + } + + /* If the errno field is already set to BADTRANSPORT, it indicates + failure to expand a transport string, or find the associated transport, + or an unset transport when one is required. Leave this test till now so + that the forbid errors are given in preference. */ + + if (addr->basic_errno == ERRNO_BADTRANSPORT) + { + (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); + continue; + } + + /* Treat /dev/null as a special case and abandon the delivery. This + avoids having to specify a uid on the transport just for this case. + Arrange for the transport name to be logged as "**bypassed**". */ + + if (Ustrcmp(addr->address, "/dev/null") == 0) + { + uschar *save = addr->transport->name; + addr->transport->name = US"**bypassed**"; + (void)post_process_one(addr, OK, LOG_MAIN, EXIM_DTYPE_TRANSPORT, '='); + addr->transport->name = save; + continue; /* with the next new address */ + } + + /* Pipe, file, or autoreply delivery is to go ahead as a normal local + delivery. */ + + DEBUG(D_deliver|D_route) + debug_printf("queued for %s transport\n", addr->transport->name); + addr->next = addr_local; + addr_local = addr; + continue; /* with the next new address */ + } + + /* Handle normal addresses. First, split up into local part and domain, + handling the %-hack if necessary. There is the possibility of a defer from + a lookup in percent_hack_domains. */ + + if ((rc = deliver_split_address(addr)) == DEFER) + { + addr->message = US"cannot check percent_hack_domains"; + addr->basic_errno = ERRNO_LISTDEFER; + (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_NONE, 0); + continue; + } + + /* Check to see if the domain is held. If so, proceed only if the + delivery was forced by hand. */ + + deliver_domain = addr->domain; /* set $domain */ + if ( !forced && hold_domains + && (rc = match_isinlist(addr->domain, (const uschar **)&hold_domains, 0, + &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, + NULL)) != FAIL + ) + { + if (rc == DEFER) + { + addr->message = US"hold_domains lookup deferred"; + addr->basic_errno = ERRNO_LISTDEFER; + } + else + { + addr->message = US"domain is held"; + addr->basic_errno = ERRNO_HELD; + } + (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_NONE, 0); + continue; + } + + /* Now we can check for duplicates and previously delivered addresses. In + order to do this, we have to generate a "unique" value for each address, + because there may be identical actual addresses in a line of descendents. + The "unique" field is initialized to the same value as the "address" field, + but gets changed here to cope with identically-named descendents. */ + + for (parent = addr->parent; parent; parent = parent->parent) + if (strcmpic(addr->address, parent->address) == 0) break; + + /* If there's an ancestor with the same name, set the homonym flag. This + influences how deliveries are recorded. Then add a prefix on the front of + the unique address. We use \n\ where n starts at 0 and increases each time. + It is unlikely to pass 9, but if it does, it may look odd but will still + work. This means that siblings or cousins with the same names are treated + as duplicates, which is what we want. */ + + if (parent) + { + setflag(addr, af_homonym); + if (parent->unique[0] != '\\') + addr->unique = string_sprintf("\\0\\%s", addr->address); + else + addr->unique = string_sprintf("\\%c\\%s", parent->unique[1] + 1, + addr->address); + } + + /* Ensure that the domain in the unique field is lower cased, because + domains are always handled caselessly. */ + + p = Ustrrchr(addr->unique, '@'); + while (*p != 0) { *p = tolower(*p); p++; } + + DEBUG(D_deliver|D_route) debug_printf("unique = %s\n", addr->unique); + + if (tree_search(tree_nonrecipients, addr->unique)) + { + DEBUG(D_deliver|D_route) + debug_printf("%s was previously delivered: discarded\n", addr->unique); + child_done(addr, tod_stamp(tod_log)); + continue; + } + + /* Get the routing retry status, saving the two retry keys (with and + without the local part) for subsequent use. If there is no retry record for + the standard address routing retry key, we look for the same key with the + sender attached, because this form is used by the smtp transport after a + 4xx response to RCPT when address_retry_include_sender is true. */ + + addr->domain_retry_key = string_sprintf("R:%s", addr->domain); + addr->address_retry_key = string_sprintf("R:%s@%s", addr->local_part, + addr->domain); + + if (dbm_file) + { + domain_retry_record = dbfn_read(dbm_file, addr->domain_retry_key); + 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) + { + uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key, + sender_address); + address_retry_record = dbfn_read(dbm_file, altkey); + if ( address_retry_record + && now - address_retry_record->time_stamp > retry_data_expire) + { + DEBUG(D_deliver|D_retry) + debug_printf("address<sender> retry record present but expired\n"); + address_retry_record = NULL; /* Ignore if too old */ + } + } + } + else + domain_retry_record = address_retry_record = NULL; + + DEBUG(D_deliver|D_retry) + { + if (!domain_retry_record) + 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"); + 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 + assume that the message which created the connection managed to route + an address to that connection. We do not want to run the risk of taking + a long time over routing here, because if we do, the server at the other + end of the connection may time it out. This is especially true for messages + with lots of addresses. For this kind of delivery, queue_running is not + set, so we would normally route all addresses. We take a pragmatic approach + and defer routing any addresses that have any kind of domain retry record. + That is, we don't even look at their retry times. It doesn't matter if this + doesn't work occasionally. This is all just an optimization, after all. + + The reason for not doing the same for address retries is that they normally + arise from 4xx responses, not DNS timeouts. */ + + if (continue_hostname && domain_retry_record) + { + addr->message = US"reusing SMTP connection skips previous routing defer"; + addr->basic_errno = ERRNO_RRETRY; + (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); + + addr->message = domain_retry_record->text; + setflag(addr, af_pass_message); + } + + /* If we are in a queue run, defer routing unless there is no retry data or + we've passed the next retry time, or this message is forced. In other + words, ignore retry data when not in a queue run. + + However, if the domain retry time has expired, always allow the routing + attempt. If it fails again, the address will be failed. This ensures that + each address is routed at least once, even after long-term routing + failures. + + If there is an address retry, check that too; just wait for the next + retry time. This helps with the case when the temporary error on the + address was really message-specific rather than address specific, since + it allows other messages through. + + We also wait for the next retry time if this is a message sent down an + existing SMTP connection (even though that will be forced). Otherwise there + will be far too many attempts for an address that gets a 4xx error. In + fact, after such an error, we should not get here because, the host should + not be remembered as one this message needs. However, there was a bug that + used to cause this to happen, so it is best to be on the safe side. + + Even if we haven't reached the retry time in the hints, there is one more + check to do, which is for the ultimate address timeout. We only do this + check if there is an address retry record and there is not a domain retry + record; this implies that previous attempts to handle the address had the + retry_use_local_parts option turned on. We use this as an approximation + for the destination being like a local delivery, for example delivery over + LMTP to an IMAP message store. In this situation users are liable to bump + into their quota and thereby have intermittently successful deliveries, + which keep the retry record fresh, which can lead to us perpetually + deferring messages. */ + + else if ( ( f.queue_running && !f.deliver_force + || continue_hostname + ) + && ( ( domain_retry_record + && now < domain_retry_record->next_try + && !domain_retry_record->expired + ) + || ( address_retry_record + && now < address_retry_record->next_try + ) ) + && ( domain_retry_record + || !address_retry_record + || !retry_ultimate_address_timeout(addr->address_retry_key, + addr->domain, address_retry_record, now) + ) ) + { + addr->message = US"retry time not reached"; + addr->basic_errno = ERRNO_RRETRY; + (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); + + /* For remote-retry errors (here and just above) that we've not yet + hit the rery 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 + can be cleaned up after a successful delivery. */ + + else + { + if (domain_retry_record || address_retry_record) + setflag(addr, af_dr_retry_exists); + addr->next = addr_route; + addr_route = addr; + DEBUG(D_deliver|D_route) + debug_printf("%s: queued for routing\n", addr->address); + } + } + + /* The database is closed while routing is actually happening. Requests to + update it are put on a chain and all processed together at the end. */ + + if (dbm_file) dbfn_close(dbm_file); + + /* If queue_domains is set, we don't even want to try routing addresses in + 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 (!f.deliver_force && queue_domains) + { + address_item *okaddr = NULL; + while (addr_route) + { + address_item *addr = addr_route; + addr_route = addr->next; + + deliver_domain = addr->domain; /* set $domain */ + if ((rc = match_isinlist(addr->domain, (const uschar **)&queue_domains, 0, + &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL)) + != OK) + if (rc == DEFER) + { + addr->basic_errno = ERRNO_LISTDEFER; + addr->message = US"queue_domains lookup deferred"; + (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); + } + else + { + addr->next = okaddr; + okaddr = addr; + } + else + { + addr->basic_errno = ERRNO_QUEUE_DOMAIN; + addr->message = US"domain is in queue_domains"; + (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); + } + } + + addr_route = okaddr; + } + + /* Now route those addresses that are not deferred. */ + + while (addr_route) + { + int rc; + address_item *addr = addr_route; + const uschar *old_domain = addr->domain; + uschar *old_unique = addr->unique; + addr_route = addr->next; + addr->next = NULL; + + /* Just in case some router parameter refers to it. */ + + if (!(return_path = addr->prop.errors_address)) + return_path = sender_address; + + /* If a router defers an address, add a retry item. Whether or not to + use the local part in the key is a property of the router. */ + + if ((rc = route_address(addr, &addr_local, &addr_remote, &addr_new, + &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), + 0); + + /* Otherwise, if there is an existing retry record in the database, add + retry items to delete both forms. We must also allow for the possibility + of a routing retry that includes the sender address. Since the domain might + have been rewritten (expanded to fully qualified) as a result of routing, + ensure that the rewritten form is also deleted. */ + + else if (testflag(addr, af_dr_retry_exists)) + { + uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key, + sender_address); + retry_add_item(addr, altkey, rf_delete); + retry_add_item(addr, addr->address_retry_key, rf_delete); + retry_add_item(addr, addr->domain_retry_key, rf_delete); + if (Ustrcmp(addr->domain, old_domain) != 0) + retry_add_item(addr, string_sprintf("R:%s", old_domain), rf_delete); + } + + /* DISCARD is given for :blackhole: and "seen finish". The event has been + logged, but we need to ensure the address (and maybe parents) is marked + done. */ + + if (rc == DISCARD) + { + address_done(addr, tod_stamp(tod_log)); + continue; /* route next address */ + } + + /* The address is finished with (failed or deferred). */ + + if (rc != OK) + { + (void)post_process_one(addr, rc, LOG_MAIN, EXIM_DTYPE_ROUTER, 0); + continue; /* route next address */ + } + + /* The address has been routed. If the router changed the domain, it will + also have changed the unique address. We have to test whether this address + has already been delivered, because it's the unique address that finally + gets recorded. */ + + if ( addr->unique != old_unique + && tree_search(tree_nonrecipients, addr->unique) != 0 + ) + { + DEBUG(D_deliver|D_route) debug_printf("%s was previously delivered: " + "discarded\n", addr->address); + if (addr_remote == addr) addr_remote = addr->next; + else if (addr_local == addr) addr_local = addr->next; + } + + /* If the router has same_domain_copy_routing set, we are permitted to copy + the routing for any other addresses with the same domain. This is an + optimisation to save repeated DNS lookups for "standard" remote domain + routing. The option is settable only on routers that generate host lists. + We play it very safe, and do the optimization only if the address is routed + to a remote transport, there are no header changes, and the domain was not + modified by the router. */ + + if ( addr_remote == addr + && addr->router->same_domain_copy_routing + && !addr->prop.extra_headers + && !addr->prop.remove_headers + && old_domain == addr->domain + ) + { + address_item **chain = &addr_route; + while (*chain) + { + address_item *addr2 = *chain; + if (Ustrcmp(addr2->domain, addr->domain) != 0) + { + chain = &(addr2->next); + continue; + } + + /* Found a suitable address; take it off the routing list and add it to + the remote delivery list. */ + + *chain = addr2->next; + addr2->next = addr_remote; + addr_remote = addr2; + + /* Copy the routing data */ + + addr2->domain = addr->domain; + addr2->router = addr->router; + addr2->transport = addr->transport; + addr2->host_list = addr->host_list; + addr2->fallback_hosts = addr->fallback_hosts; + addr2->prop.errors_address = addr->prop.errors_address; + copyflag(addr2, addr, af_hide_child); + copyflag(addr2, addr, af_local_host_removed); + + DEBUG(D_deliver|D_route) + debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" + "routing %s\n" + "Routing for %s copied from %s\n", + addr2->address, addr2->address, addr->address); + } + } + } /* Continue with routing the next address. */ + } /* Loop to process any child addresses that the routers created, and + any rerouted addresses that got put back on the new chain. */ + + +/* Debugging: show the results of the routing */ + +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) + debug_printf(" %s\n", p->address); + + debug_printf(" Remote deliveries:\n"); + for (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) + debug_printf(" %s\n", p->address); + + debug_printf(" Deferred addresses:\n"); + for (p = addr_defer; p; p = p->next) + debug_printf(" %s\n", p->address); + } + +/* Free any resources that were cached during routing. */ + +search_tidyup(); +route_tidyup(); + +/* These two variables are set only during routing, after check_local_user. +Ensure they are not set in transports. */ + +local_user_gid = (gid_t)(-1); +local_user_uid = (uid_t)(-1); + +/* Check for any duplicate addresses. This check is delayed until after +routing, because the flexibility of the routing configuration means that +identical addresses with different parentage may end up being redirected to +different addresses. Checking for duplicates too early (as we previously used +to) makes this kind of thing not work. */ + +do_duplicate_check(&addr_local); +do_duplicate_check(&addr_remote); + +/* When acting as an MUA wrapper, we proceed only if all addresses route to a +remote transport. The check that they all end up in one transaction happens in +the do_remote_deliveries() function. */ + +if ( mua_wrapper + && (addr_local || addr_failed || addr_defer) + ) + { + address_item *addr; + uschar *which, *colon, *msg; + + if (addr_local) + { + addr = addr_local; + which = US"local"; + } + else if (addr_defer) + { + addr = addr_defer; + which = US"deferred"; + } + else + { + addr = addr_failed; + which = US"failed"; + } + + while (addr->parent) addr = addr->parent; + + if (addr->message) + { + colon = US": "; + msg = addr->message; + } + else colon = msg = US""; + + /* We don't need to log here for a forced failure as it will already + have been logged. Defer will also have been logged, but as a defer, so we do + need to do the failure logging. */ + + if (addr != addr_failed) + log_write(0, LOG_MAIN, "** %s routing yielded a %s delivery", + addr->address, which); + + /* Always write an error to the caller */ + + fprintf(stderr, "routing %s yielded a %s delivery%s%s\n", addr->address, + which, colon, msg); + + final_yield = DELIVER_MUA_FAILED; + addr_failed = addr_defer = NULL; /* So that we remove the message */ + goto DELIVERY_TIDYUP; + } + + +/* If this is a run to continue deliveries to an external channel that is +already set up, defer any local deliveries. */ + +if (continue_transport) + { + if (addr_defer) + { + address_item *addr = addr_defer; + while (addr->next) addr = addr->next; + addr->next = addr_local; + } + else + addr_defer = addr_local; + addr_local = NULL; + } + + +/* Because address rewriting can happen in the routers, we should not really do +ANY deliveries until all addresses have been routed, so that all recipients of +the message get the same headers. However, this is in practice not always +possible, since sometimes remote addresses give DNS timeouts for days on end. +The pragmatic approach is to deliver what we can now, saving any rewritten +headers so that at least the next lot of recipients benefit from the rewriting +that has already been done. + +If any headers have been rewritten during routing, update the spool file to +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 ( 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); + f.header_rewritten = FALSE; + } + + +/* 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. + +The journal is just insurance against crashes. When the spool file is +ultimately updated at the end of processing, the journal is deleted. If a +journal is found to exist at the start of delivery, the addresses listed +therein are added to the non-recipients. */ + +if (addr_local || addr_remote) + { + if (journal_fd < 0) + { + uschar * fname = spool_fname(US"input", message_subdir, id, US"-J"); + + if ((journal_fd = Uopen(fname, +#ifdef O_CLOEXEC + O_CLOEXEC | +#endif + O_WRONLY|O_APPEND|O_CREAT|O_EXCL, SPOOL_MODE)) < 0) + { + log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't open journal file %s: %s", + fname, strerror(errno)); + return DELIVER_NOT_ATTEMPTED; + } + + /* Set the close-on-exec flag, make the file owned by Exim, and ensure + that the mode is correct - the group setting doesn't always seem to get + set automatically. */ + + if( fchown(journal_fd, exim_uid, exim_gid) + || fchmod(journal_fd, SPOOL_MODE) +#ifndef O_CLOEXEC + || fcntl(journal_fd, F_SETFD, fcntl(journal_fd, F_GETFD) | FD_CLOEXEC) +#endif + ) + { + int ret = Uunlink(fname); + log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't set perms on journal file %s: %s", + fname, strerror(errno)); + if(ret && errno != ENOENT) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s", + fname, strerror(errno)); + return DELIVER_NOT_ATTEMPTED; + } + } + } +else if (journal_fd >= 0) + { + close(journal_fd); + journal_fd = -1; + } + + + +/* Now we can get down to the business of actually doing deliveries. Local +deliveries are done first, then remote ones. If ever the problems of how to +handle fallback transports are figured out, this section can be put into a loop +for handling fallbacks, though the uid switching will have to be revised. */ + +/* Precompile a regex that is used to recognize a parameter in response +to an LHLO command, if is isn't already compiled. This may be used on both +local and remote LMTP deliveries. */ + +if (!regex_IGNOREQUOTA) + regex_IGNOREQUOTA = + regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE); + +/* Handle local deliveries */ + +if (addr_local) + { + DEBUG(D_deliver|D_transport) + debug_printf(">>>>>>>>>>>>>>>> Local deliveries >>>>>>>>>>>>>>>>\n"); + do_local_deliveries(); + 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 (f.queue_run_local) + while (addr_remote) + { + address_item *addr = addr_remote; + addr_remote = addr->next; + addr->next = NULL; + addr->basic_errno = ERRNO_LOCAL_ONLY; + addr->message = US"remote deliveries suppressed"; + (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_TRANSPORT, 0); + } + +/* Handle remote deliveries */ + +if (addr_remote) + { + DEBUG(D_deliver|D_transport) + debug_printf(">>>>>>>>>>>>>>>> Remote deliveries >>>>>>>>>>>>>>>>\n"); + + /* Precompile some regex that are used to recognize parameters in response + to an EHLO command, if they aren't already compiled. */ + + 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 + cannot be delivered in one transaction. */ + + if (remote_sort_domains) sort_remote_deliveries(); + if (!do_remote_deliveries(FALSE)) + { + log_write(0, LOG_MAIN, "** mua_wrapper is set but recipients cannot all " + "be delivered in one transaction"); + fprintf(stderr, "delivery to smarthost failed (configuration problem)\n"); + + final_yield = DELIVER_MUA_FAILED; + addr_failed = addr_defer = NULL; /* So that we remove the message */ + goto DELIVERY_TIDYUP; + } + + /* See if any of the addresses that failed got put on the queue for delivery + to their fallback hosts. We do it this way because often the same fallback + host is used for many domains, so all can be sent in a single transaction + (if appropriately configured). */ + + if (addr_fallback && !mua_wrapper) + { + DEBUG(D_deliver) debug_printf("Delivering to fallback hosts\n"); + addr_remote = addr_fallback; + addr_fallback = NULL; + if (remote_sort_domains) sort_remote_deliveries(); + do_remote_deliveries(TRUE); + } + f.disable_logging = FALSE; + } + + +/* All deliveries are now complete. Ignore SIGTERM during this tidying up +phase, to minimize cases of half-done things. */ + +DEBUG(D_deliver) + debug_printf(">>>>>>>>>>>>>>>> deliveries are done >>>>>>>>>>>>>>>>\n"); +cancel_cutthrough_connection(TRUE, US"deliveries are done"); + +/* Root privilege is no longer needed */ + +exim_setugid(exim_uid, exim_gid, FALSE, US"post-delivery tidying"); + +set_process_info("tidying up after delivering %s", message_id); +signal(SIGTERM, SIG_IGN); + +/* When we are acting as an MUA wrapper, the smtp transport will either have +succeeded for all addresses, or failed them all in normal cases. However, there +are some setup situations (e.g. when a named port does not exist) that cause an +immediate exit with deferral of all addresses. Convert those into failures. We +do not ever want to retry, nor do we want to send a bounce message. */ + +if (mua_wrapper) + { + if (addr_defer) + { + address_item *addr, *nextaddr; + for (addr = addr_defer; addr; addr = nextaddr) + { + log_write(0, LOG_MAIN, "** %s mua_wrapper forced failure for deferred " + "delivery", addr->address); + nextaddr = addr->next; + addr->next = addr_failed; + addr_failed = addr; + } + addr_defer = NULL; + } + + /* Now all should either have succeeded or failed. */ + + if (!addr_failed) + final_yield = DELIVER_MUA_SUCCEEDED; + else + { + host_item * host; + uschar *s = addr_failed->user_message; + + if (!s) s = addr_failed->message; + + fprintf(stderr, "Delivery failed: "); + if (addr_failed->basic_errno > 0) + { + fprintf(stderr, "%s", strerror(addr_failed->basic_errno)); + if (s) fprintf(stderr, ": "); + } + if ((host = addr_failed->host_used)) + fprintf(stderr, "H=%s [%s]: ", host->name, host->address); + if (s) + fprintf(stderr, "%s", CS s); + else if (addr_failed->basic_errno <= 0) + fprintf(stderr, "unknown error"); + fprintf(stderr, "\n"); + + final_yield = DELIVER_MUA_FAILED; + addr_failed = NULL; + } + } + +/* In a normal configuration, we now update the retry database. This is done in +one fell swoop at the end in order not to keep opening and closing (and +locking) the database. The code for handling retries is hived off into a +separate module for convenience. We pass it the addresses of the various +chains, because deferred addresses can get moved onto the failed chain if the +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 (!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) + { + /* 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: envid: %s ret: %d\n" + "DSN: Final recipient: %s\n" + "DSN: Remote SMTP server supports DSN: %d\n", + addr_dsntmp->router ? addr_dsntmp->router->name : US"(unknown)", + addr_dsntmp->address, + sender_address, + addr_dsntmp->dsn_orcpt ? addr_dsntmp->dsn_orcpt : US"NULL", + addr_dsntmp->dsn_flags, + dsn_envid ? dsn_envid : US"NULL", dsn_ret, + addr_dsntmp->address, + addr_dsntmp->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_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->next = addr_next; + } + else + DEBUG(D_deliver) debug_printf("DSN: not sending DSN success message\n"); + } + +if (addr_senddsn) + { + pid_t pid; + int fd; + + /* create exim process to send message */ + pid = child_open_exim(&fd); + + DEBUG(D_deliver) debug_printf("DSN: child_open_exim returns: %d\n", pid); + + if (pid < 0) /* Creation of child failed */ + { + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Process %d (parent %d) failed to " + "create child process to send failure message: %s", getpid(), + getppid(), strerror(errno)); + + DEBUG(D_deliver) debug_printf("DSN: child_open_exim failed\n"); + } + else /* Creation of child succeeded */ + { + 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); + + /* build unique id for MIME boundary */ + bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand()); + DEBUG(D_deliver) debug_printf("DSN: MIME boundary: %s\n", bound); + + if (errors_reply_to) + fprintf(f, "Reply-To: %s\n", errors_reply_to); + + fprintf(f, "Auto-Submitted: auto-generated\n" + "From: Mail Delivery System <Mailer-Daemon@%s>\n" + "To: %s\n" + "Subject: Delivery Status Notification\n" + "Content-Type: multipart/report; report-type=delivery-status; boundary=%s\n" + "MIME-Version: 1.0\n\n" + + "--%s\n" + "Content-type: text/plain; charset=us-ascii\n\n" + + "This message was created automatically by mail delivery software.\n" + " ----- The following addresses had successful delivery notifications -----\n", + qualify_domain_sender, sender_address, bound, bound); + + for (addr_dsntmp = addr_senddsn; addr_dsntmp; + addr_dsntmp = addr_dsntmp->next) + fprintf(f, "<%s> (relayed %s)\n\n", + addr_dsntmp->address, + addr_dsntmp->dsn_flags & rf_dsnlasthop ? "via non DSN router" + : addr_dsntmp->dsn_aware == dsn_support_no ? "to non-DSN-aware mailer" + : "via non \"Remote SMTP\" router" + ); + + fprintf(f, "--%s\n" + "Content-type: message/delivery-status\n\n" + "Reporting-MTA: dns; %s\n", + bound, smtp_active_hostname); + + if (dsn_envid) + { /* 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); + else + fprintf(f, "X-Original-Envelope-ID: error decoding xtext formatted ENVID\n"); + } + fputc('\n', f); + + for (addr_dsntmp = addr_senddsn; + addr_dsntmp; + addr_dsntmp = addr_dsntmp->next) + { + if (addr_dsntmp->dsn_orcpt) + fprintf(f,"Original-Recipient: %s\n", addr_dsntmp->dsn_orcpt); + + fprintf(f, "Action: delivered\n" + "Final-Recipient: rfc822;%s\n" + "Status: 2.0.0\n", + addr_dsntmp->address); + + if (addr_dsntmp->host_used && addr_dsntmp->host_used->name) + fprintf(f, "Remote-MTA: dns; %s\nDiagnostic-Code: smtp; 250 Ok\n\n", + addr_dsntmp->host_used->name); + else + fprintf(f, "Diagnostic-Code: X-Exim; relayed via non %s router\n\n", + addr_dsntmp->dsn_flags & rf_dsnlasthop ? "DSN" : "SMTP"); + } + + fprintf(f, "--%s\nContent-type: text/rfc822-headers\n\n", bound); + + fflush(f); + transport_filter_argv = NULL; /* Just in case */ + return_path = sender_address; /* In case not previously set */ + + /* Write the original email out */ + + tctx.u.fd = fd; + tctx.options = topt_add_return_path | topt_no_body; + /*XXX hmm, retval ignored. + Could error for any number of reasons, and they are not handled. */ + transport_write_message(&tctx, 0); + fflush(f); + + fprintf(f,"\n--%s--\n", bound); + + fflush(f); + fclose(f); + rc = child_close(pid, 0); /* Waits for child to close, no timeout */ + } + } + +/* If any addresses failed, we must send a message to somebody, unless +af_ignore_error is set, in which case no action is taken. It is possible for +several messages to get sent if there are addresses with different +requirements. */ + +while (addr_failed) + { + pid_t pid; + int fd; + uschar *logtod = tod_stamp(tod_log); + address_item *addr; + address_item *handled_addr = NULL; + address_item **paddr; + address_item *msgchain = NULL; + address_item **pmsgchain = &msgchain; + + /* There are weird cases when logging is disabled in the transport. However, + there may not be a transport (address failed by a router). */ + + f.disable_logging = FALSE; + if (addr_failed->transport) + f.disable_logging = addr_failed->transport->disable_logging; + + DEBUG(D_deliver) + debug_printf("processing failed address %s\n", addr_failed->address); + + /* There are only two ways an address in a bounce message can get here: + + (1) When delivery was initially deferred, but has now timed out (in the call + to retry_update() above). We can detect this by testing for + af_retry_timedout. If the address does not have its own errors address, + we arrange to ignore the error. + + (2) If delivery failures for bounce messages are being ignored. We can detect + this by testing for af_ignore_error. This will also be set if a bounce + message has been autothawed and the ignore_bounce_errors_after time has + passed. It might also be set if a router was explicitly configured to + ignore errors (errors_to = ""). + + If neither of these cases obtains, something has gone wrong. Log the + incident, but then ignore the error. */ + + if (sender_address[0] == 0 && !addr_failed->prop.errors_address) + { + if ( !testflag(addr_failed, af_retry_timedout) + && !addr_failed->prop.ignore_error) + log_write(0, LOG_MAIN|LOG_PANIC, "internal error: bounce message " + "failure is neither frozen nor ignored (it's been ignored)"); + + addr_failed->prop.ignore_error = TRUE; + } + + /* If the first address on the list has af_ignore_error set, just remove + it from the list, throw away any saved message file, log it, and + mark the recipient done. */ + + if ( addr_failed->prop.ignore_error + || addr_failed->dsn_flags & (rf_dsnflags & ~rf_notify_failure) + ) + { + addr = addr_failed; + addr_failed = addr->next; + if (addr->return_filename) Uunlink(addr->return_filename); + +#ifndef DISABLE_EVENT + msg_event_raise(US"msg:fail:delivery", addr); +#endif + log_write(0, LOG_MAIN, "%s%s%s%s: error ignored", + addr->address, + !addr->parent ? US"" : US" <", + !addr->parent ? US"" : addr->parent->address, + !addr->parent ? US"" : US">"); + + address_done(addr, logtod); + child_done(addr, logtod); + /* Panic-dies on error */ + (void)spool_write_header(message_id, SW_DELIVERING, NULL); + } + + /* Otherwise, handle the sending of a message. Find the error address for + the first address, then send a message that includes all failed addresses + that have the same error address. Note the bounce_recipient is a global so + that it can be accessed by $bounce_recipient while creating a customized + error message. */ + + else + { + if (!(bounce_recipient = addr_failed->prop.errors_address)) + bounce_recipient = sender_address; + + /* Make a subprocess to send a message */ + + if ((pid = child_open_exim(&fd)) < 0) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Process %d (parent %d) failed to " + "create child process to send failure message: %s", getpid(), + getppid(), strerror(errno)); + + /* Creation of child succeeded */ + + else + { + int ch, rc; + int filecount = 0; + int rcount = 0; + uschar *bcc, *emf_text; + 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; + uschar * bound; + uschar *dsnlimitmsg; + uschar *dsnnotifyhdr; + int topt; + + DEBUG(D_deliver) + debug_printf("sending error message to: %s\n", bounce_recipient); + + /* Scan the addresses for all that have the same errors address, removing + them from the addr_failed chain, and putting them on msgchain. */ + + paddr = &addr_failed; + for (addr = addr_failed; addr; addr = *paddr) + if (Ustrcmp(bounce_recipient, addr->prop.errors_address + ? addr->prop.errors_address : sender_address) == 0) + { /* The same - dechain */ + *paddr = addr->next; + *pmsgchain = addr; + addr->next = NULL; + pmsgchain = &(addr->next); + } + else + paddr = &addr->next; /* Not the same; skip */ + + /* Include X-Failed-Recipients: for automatic interpretation, but do + not let any one header line get too long. We do this by starting a + new header every 50 recipients. Omit any addresses for which the + "hide_child" flag is set. */ + + for (addr = msgchain; addr; addr = addr->next) + { + if (testflag(addr, af_hide_child)) continue; + if (rcount >= 50) + { + fprintf(fp, "\n"); + rcount = 0; + } + fprintf(fp, "%s%s", + rcount++ == 0 + ? "X-Failed-Recipients: " + : ",\n ", + testflag(addr, af_pfr) && addr->parent + ? string_printing(addr->parent->address) + : string_printing(addr->address)); + } + if (rcount > 0) fprintf(fp, "\n"); + + /* Output the standard headers */ + + if (errors_reply_to) + 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); + + /* generate boundary string and output MIME-Headers */ + bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand()); + + fprintf(fp, "Content-Type: multipart/report;" + " report-type=delivery-status; boundary=%s\n" + "MIME-Version: 1.0\n", + bound); + + /* Open a template file if one is provided. Log failure to open, but + carry on - default texts will be used. */ + + if (bounce_message_file) + if (!(emf = Ufopen(bounce_message_file, "rb"))) + log_write(0, LOG_MAIN|LOG_PANIC, "Failed to open %s for error " + "message texts: %s", bounce_message_file, strerror(errno)); + + /* Quietly copy to configured additional addresses if required. */ + + if ((bcc = moan_check_errorcopy(bounce_recipient))) + 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(fp, "%s\n", emf_text); + else + fprintf(fp, "Subject: Mail delivery failed%s\n\n", + to_sender? ": returning message to sender" : ""); + + /* output human readable part as text/plain section */ + fprintf(fp, "--%s\n" + "Content-type: text/plain; charset=us-ascii\n\n", + bound); + + if ((emf_text = next_emf(emf, US"intro"))) + fprintf(fp, "%s", CS emf_text); + else + { + 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(fp, "%s", CS bounce_message_text); + if (to_sender) + 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(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', 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 + post_process_one() for the existence of data in the message file.) A TRUE + return from print_address_information() means that the address is not + hidden. */ + + paddr = &msgchain; + for (addr = msgchain; addr; addr = *paddr) + { + 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', fp); + + /* Leave on msgchain if there's a return file. */ + + if (addr->return_file >= 0) + { + paddr = &(addr->next); + filecount++; + } + + /* Else save so that we can tick off the recipient when the + message is sent. */ + + else + { + *paddr = addr->next; + addr->next = handled_addr; + handled_addr = addr; + } + } + + fputc('\n', fp); + + /* Get the next text, whether we need it or not, so as to be + positioned for the one after. */ + + emf_text = next_emf(emf, US"generated text"); + + /* If there were any file messages passed by the local transports, + include them in the message. Then put the address on the handled chain. + In the case of a batch of addresses that were all sent to the same + transport, the return_file field in all of them will contain the same + fd, and the return_filename field in the *last* one will be set (to the + name of the file). */ + + if (msgchain) + { + address_item *nextaddr; + + if (emf_text) + fprintf(fp, "%s", CS emf_text); + else + fprintf(fp, + "The following text was generated during the delivery " + "attempt%s:\n", (filecount > 1)? "s" : ""); + + for (addr = msgchain; addr; addr = nextaddr) + { + FILE *fm; + address_item *topaddr = addr; + + /* List all the addresses that relate to this file */ + + fputc('\n', fp); + while(addr) /* Insurance */ + { + print_address_information(addr, fp, US"------ ", US"\n ", + US" ------\n"); + if (addr->return_filename) break; + addr = addr->next; + } + fputc('\n', fp); + + /* Now copy the file */ + + if (!(fm = Ufopen(addr->return_filename, "rb"))) + fprintf(fp, " +++ Exim error... failed to open text file: %s\n", + strerror(errno)); + else + { + while ((ch = fgetc(fm)) != EOF) fputc(ch, fp); + (void)fclose(fm); + } + Uunlink(addr->return_filename); + + /* Can now add to handled chain, first fishing off the next + address on the msgchain. */ + + nextaddr = addr->next; + addr->next = handled_addr; + handled_addr = topaddr; + } + fputc('\n', fp); + } + + /* output machine readable part */ +#ifdef SUPPORT_I18N + if (message_smtputf8) + fprintf(fp, "--%s\n" + "Content-type: message/global-delivery-status\n\n" + "Reporting-MTA: dns; %s\n", + bound, smtp_active_hostname); + else +#endif + fprintf(fp, "--%s\n" + "Content-type: message/delivery-status\n\n" + "Reporting-MTA: dns; %s\n", + bound, smtp_active_hostname); + + if (dsn_envid) + { + /* must be decoded from xtext: see RFC 3461:6.3a */ + uschar *xdec_envid; + if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0) + fprintf(fp, "Original-Envelope-ID: %s\n", dsn_envid); + else + fprintf(fp, "X-Original-Envelope-ID: error decoding xtext formatted ENVID\n"); + } + fputc('\n', fp); + + for (addr = handled_addr; addr; addr = addr->next) + { + host_item * hu; + fprintf(fp, "Action: failed\n" + "Final-Recipient: rfc822;%s\n" + "Status: 5.0.0\n", + addr->address); + if ((hu = addr->host_used) && 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(fp, "Remote-MTA: X-ip; [%s]%s\n", hu->address, p); + } + if ((s = addr->smtp_greeting) && *s) + fprintf(fp, "X-Remote-MTA-smtp-greeting: X-str; %s\n", s); + if ((s = addr->helo_response) && *s) + fprintf(fp, "X-Remote-MTA-helo-response: X-str; %s\n", s); + if ((s = addr->message) && *s) + fprintf(fp, "X-Exim-Diagnostic: X-str; %s\n", s); + } +#endif + print_dsn_diagnostic_code(addr, fp); + } + fputc('\n', fp); + } + + /* Now copy the message, trying to give an intelligible comment if + it is too long for it all to be copied. The limit isn't strictly + applied because of the buffering. There is, however, an option + to suppress copying altogether. */ + + emf_text = next_emf(emf, US"copy"); + + /* add message body + we ignore the intro text from template and add + the text for bounce_return_size_limit at the end. + + bounce_return_message is ignored + in case RET= is defined we honor these values + otherwise bounce_return_body is honored. + + bounce_return_size_limit is always honored. + */ + + fprintf(fp, "--%s\n", bound); + + dsnlimitmsg = US"X-Exim-DSN-Information: Due to administrative limits only headers are returned"; + dsnnotifyhdr = NULL; + topt = topt_add_return_path; + + /* RET=HDRS? top priority */ + if (dsn_ret == dsn_ret_hdrs) + topt |= topt_no_body; + else + { + struct stat statbuf; + + /* no full body return at all? */ + if (!bounce_return_body) + { + topt |= topt_no_body; + /* add header if we overrule RET=FULL */ + if (dsn_ret == dsn_ret_full) + dsnnotifyhdr = dsnlimitmsg; + } + /* line length limited... return headers only if oversize */ + /* size limited ... return headers only if limit reached */ + else if ( max_received_linelength > bounce_return_linesize_limit + || ( bounce_return_size_limit > 0 + && fstat(deliver_datafile, &statbuf) == 0 + && statbuf.st_size > max + ) ) + { + topt |= topt_no_body; + dsnnotifyhdr = dsnlimitmsg; + } + } + +#ifdef SUPPORT_I18N + if (message_smtputf8) + fputs(topt & topt_no_body ? "Content-type: message/global-headers\n\n" + : "Content-type: message/global\n\n", + fp); + else +#endif + fputs(topt & topt_no_body ? "Content-type: text/rfc822-headers\n\n" + : "Content-type: message/rfc822\n\n", + fp); + + 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(fp); + tctx.tblock = &tb; + tctx.options = topt; + tb.add_headers = dsnnotifyhdr; + + /*XXX no checking for failure! buggy! */ + transport_write_message(&tctx, 0); + } + fflush(fp); + + /* we never add the final text. close the file */ + if (emf) + (void)fclose(emf); + + 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(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 (f.running_in_test_harness) millisleep(500); + + /* If the process failed, there was some disaster in setting up the + error message. Unless the message is very old, ensure that addr_defer + is non-null, which will have the effect of leaving the message on the + spool. The failed addresses will get tried again next time. However, we + don't really want this to happen too often, so freeze the message unless + there are some genuine deferred addresses to try. To do this we have + to call spool_write_header() here, because with no genuine deferred + addresses the normal code below doesn't get run. */ + + if (rc != 0) + { + uschar *s = US""; + if (now - received_time.tv_sec < retry_maximum_timeout && !addr_defer) + { + addr_defer = (address_item *)(+1); + f.deliver_freeze = TRUE; + deliver_frozen_at = time(NULL); + /* Panic-dies on error */ + (void)spool_write_header(message_id, SW_DELIVERING, NULL); + s = US" (frozen)"; + } + deliver_msglog("Process failed (%d) when writing error message " + "to %s%s", rc, bounce_recipient, s); + log_write(0, LOG_MAIN, "Process failed (%d) when writing error message " + "to %s%s", rc, bounce_recipient, s); + } + + /* The message succeeded. Ensure that the recipients that failed are + now marked finished with on the spool and their parents updated. */ + + else + { + for (addr = handled_addr; addr; addr = addr->next) + { + address_done(addr, logtod); + child_done(addr, logtod); + } + /* Panic-dies on error */ + (void)spool_write_header(message_id, SW_DELIVERING, NULL); + } + } + } + } + +f.disable_logging = FALSE; /* In case left set */ + +/* Come here from the mua_wrapper case if routing goes wrong */ + +DELIVERY_TIDYUP: + +/* If there are now no deferred addresses, we are done. Preserve the +message log if so configured, and we are using them. Otherwise, sling it. +Then delete the message itself. */ + +if (!addr_defer) + { + uschar * fname; + + if (message_logs) + { + fname = spool_fname(US"msglog", message_subdir, id, US""); + if (preserve_message_logs) + { + int rc; + uschar * moname = spool_fname(US"msglog.OLD", US"", id, US""); + + if ((rc = Urename(fname, moname)) < 0) + { + (void)directory_make(spool_directory, + spool_sname(US"msglog.OLD", US""), + MSGLOG_DIRECTORY_MODE, TRUE); + rc = Urename(fname, moname); + } + if (rc < 0) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to move %s to the " + "msglog.OLD directory", fname); + } + else + if (Uunlink(fname) < 0) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s", + fname, strerror(errno)); + } + + /* Remove the two message files. */ + + fname = spool_fname(US"input", message_subdir, id, US"-D"); + if (Uunlink(fname) < 0) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s", + fname, strerror(errno)); + fname = spool_fname(US"input", message_subdir, id, US"-H"); + if (Uunlink(fname) < 0) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s", + fname, strerror(errno)); + + /* 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)); + else + log_write(0, LOG_MAIN, "Completed"); + + /* Unset deliver_freeze so that we won't try to move the spool files further down */ + f.deliver_freeze = FALSE; + +#ifndef DISABLE_EVENT + (void) event_raise(event_action, US"msg:complete", NULL); +#endif + } + +/* If there are deferred addresses, we are keeping this message because it is +not yet completed. Lose any temporary files that were catching output from +pipes for any of the deferred addresses, handle one-time aliases, and see if +the message has been on the queue for so long that it is time to send a warning +message to the sender, unless it is a mailer-daemon. If all deferred addresses +have the same domain, we can set deliver_domain for the expansion of +delay_warning_ condition - if any of them are pipes, files, or autoreplies, use +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. + +If we can't make a process to send the message, don't worry. + +For mailing list expansions we want to send the warning message to the +mailing list manager. We can't do a perfect job here, as some addresses may +have different errors addresses, but if we take the errors address from +each deferred address it will probably be right in most cases. + +If addr_defer == +1, it means there was a problem sending an error message +for failed addresses, and there were no "real" deferred addresses. The value +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 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) + { + address_item *otaddr; + + if (addr->basic_errno > ERRNO_WARN_BASE) want_warning_msg = TRUE; + + if (deliver_domain) + { + const uschar *d = testflag(addr, af_pfr) + ? addr->parent->domain : addr->domain; + + /* The domain may be unset for an address that has never been routed + because the system filter froze the message. */ + + if (!d || Ustrcmp(d, deliver_domain) != 0) + deliver_domain = NULL; + } + + if (addr->return_filename) Uunlink(addr->return_filename); + + /* Handle the case of one-time aliases. If any address in the ancestry + of this one is flagged, ensure it is in the recipients list, suitably + flagged, and that its parent is marked delivered. */ + + for (otaddr = addr; otaddr; otaddr = otaddr->parent) + if (otaddr->onetime_parent) break; + + if (otaddr) + { + int i; + int t = recipients_count; + + for (i = 0; i < recipients_count; i++) + { + uschar *r = recipients_list[i].address; + if (Ustrcmp(otaddr->onetime_parent, r) == 0) t = i; + if (Ustrcmp(otaddr->address, r) == 0) break; + } + + /* Didn't find the address already in the list, and did find the + ultimate parent's address in the list, and they really are different + (i.e. not from an identity-redirect). After adding the recipient, + update the errors address in the recipients list. */ + + if ( i >= recipients_count && t < recipients_count + && Ustrcmp(otaddr->address, otaddr->parent->address) != 0) + { + DEBUG(D_deliver) debug_printf("one_time: adding %s in place of %s\n", + otaddr->address, otaddr->parent->address); + receive_add_recipient(otaddr->address, t); + recipients_list[recipients_count-1].errors_to = otaddr->prop.errors_address; + tree_add_nonrecipient(otaddr->parent->address); + update_spool = TRUE; + } + } + + /* Except for error messages, ensure that either the errors address for + this deferred address or, if there is none, the sender address, is on the + list of recipients for a warning message. */ + + if (sender_address[0]) + { + uschar * s = addr->prop.errors_address; + if (!s) s = sender_address; + if (Ustrstr(recipients, s) == NULL) + recipients = string_sprintf("%s%s%s", recipients, + recipients[0] ? "," : "", s); + } + } + + /* Send a warning message if the conditions are right. If the condition check + fails because of a lookup defer, there is nothing we can do. The warning + is not sent. Another attempt will be made at the next delivery attempt (if + it also defers). */ + + 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 + && ( !delay_warning_condition + || expand_check_condition(delay_warning_condition, + US"delay_warning", US"option") + ) + ) + { + int count; + 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 (f.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; + } + } + + /* See how many warnings we should have sent by now */ + + for (count = 0; count < delay_warning[1]; count++) + if (queue_time < delay_warning[count+2]) break; + + show_time = delay_warning[count+1]; + + if (count >= delay_warning[1]) + { + int extra; + int last_gap = show_time; + if (count > 1) last_gap -= delay_warning[count]; + extra = (queue_time - delay_warning[count+1])/last_gap; + show_time += last_gap * extra; + count += extra; + } + + DEBUG(D_deliver) + { + 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); + } + + /* We have computed the number of warnings there should have been by now. + If there haven't been enough, send one, and up the count to what it should + have been. */ + + if (warning_count < count) + { + header_line *h; + int fd; + pid_t pid = child_open_exim(&fd); + + if (pid > 0) + { + uschar *wmf_text; + FILE *wmf = NULL; + FILE *f = fdopen(fd, "wb"); + uschar * bound; + transport_ctx tctx = {{0}}; + + if (warn_message_file) + if (!(wmf = Ufopen(warn_message_file, "rb"))) + log_write(0, LOG_MAIN|LOG_PANIC, "Failed to open %s for warning " + "message texts: %s", warn_message_file, strerror(errno)); + + warnmsg_recipients = recipients; + warnmsg_delay = queue_time < 120*60 + ? string_sprintf("%d minutes", show_time/60) + : string_sprintf("%d hours", show_time/3600); + + 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", recipients); + + /* generated boundary string and output MIME-Headers */ + bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand()); + + fprintf(f, "Content-Type: multipart/report;" + " report-type=delivery-status; boundary=%s\n" + "MIME-Version: 1.0\n", + bound); + + if ((wmf_text = next_emf(wmf, US"header"))) + fprintf(f, "%s\n", wmf_text); + else + fprintf(f, "Subject: Warning: message %s delayed %s\n\n", + message_id, warnmsg_delay); + + /* output human readable part as text/plain section */ + fprintf(f, "--%s\n" + "Content-type: text/plain; charset=us-ascii\n\n", + bound); + + if ((wmf_text = next_emf(wmf, US"intro"))) + fprintf(f, "%s", CS wmf_text); + else + { + fprintf(f, +"This message was created automatically by mail delivery software.\n"); + + if (Ustrcmp(recipients, sender_address) == 0) + fprintf(f, +"A message that you sent has not yet been delivered to one or more of its\n" +"recipients after more than "); + + else + fprintf(f, +"A message sent by\n\n <%s>\n\n" +"has not yet been delivered to one or more of its recipients after more than \n", + sender_address); + + fprintf(f, "%s on the queue on %s.\n\n" + "The message identifier is: %s\n", + warnmsg_delay, primary_hostname, message_id); + + for (h = header_list; h; h = h->next) + if (strncmpic(h->text, US"Subject:", 8) == 0) + fprintf(f, "The subject of the message is: %s", h->text + 9); + else if (strncmpic(h->text, US"Date:", 5) == 0) + fprintf(f, "The date of the message is: %s", h->text + 6); + fputc('\n', f); + + fprintf(f, "The address%s to which the message has not yet been " + "delivered %s:\n", + !addr_defer->next ? "" : "es", + !addr_defer->next ? "is": "are"); + } + + /* 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) + { + address_item *addr = addr_defer; + addr_defer = addr->next; + if (print_address_information(addr, f, US" ", US"\n ", US"")) + print_address_error(addr, f, US"Delay reason: "); + fputc('\n', f); + } + fputc('\n', f); + + /* Final text */ + + if (wmf) + { + if ((wmf_text = next_emf(wmf, US"final"))) + fprintf(f, "%s", CS wmf_text); + (void)fclose(wmf); + } + else + { + fprintf(f, +"No action is required on your part. Delivery attempts will continue for\n" +"some time, and this warning may be repeated at intervals if the message\n" +"remains undelivered. Eventually the mail delivery software will give up,\n" +"and when that happens, the message will be returned to you.\n"); + } + + /* output machine readable part */ + fprintf(f, "\n--%s\n" + "Content-type: message/delivery-status\n\n" + "Reporting-MTA: dns; %s\n", + bound, + smtp_active_hostname); + + + if (dsn_envid) + { + /* 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); + else + fprintf(f,"X-Original-Envelope-ID: error decoding xtext formatted ENVID\n"); + } + fputc('\n', f); + + for ( ; addr_dsndefer; addr_dsndefer = addr_dsndefer->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) + { + fprintf(f, "Remote-MTA: dns; %s\n", + addr_dsndefer->host_used->name); + print_dsn_diagnostic_code(addr_dsndefer, f); + } + fputc('\n', f); + } + + fprintf(f, "--%s\n" + "Content-type: text/rfc822-headers\n\n", + bound); + + fflush(f); + /* header only as required by RFC. only failure DSN needs to honor RET=FULL */ + tctx.u.fd = fileno(f); + tctx.options = topt_add_return_path | topt_no_body; + transport_filter_argv = NULL; /* Just in case */ + 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); + + fprintf(f,"\n--%s--\n", bound); + + fflush(f); + + /* Close and wait for child process to complete, without a timeout. + If there's an error, don't update the count. */ + + (void)fclose(f); + if (child_close(pid, 0) == 0) + { + warning_count = count; + update_spool = TRUE; /* Ensure spool rewritten */ + } + } + } + } + + /* Clear deliver_domain */ + + deliver_domain = NULL; + + /* If this was a first delivery attempt, unset the first time flag, and + ensure that the spool gets updated. */ + + if (f.deliver_firsttime) + { + f.deliver_firsttime = FALSE; + update_spool = TRUE; + } + + /* If delivery was frozen and freeze_tell is set, generate an appropriate + message, unless the message is a local error message (to avoid loops). Then + log the freezing. If the text in "frozen_info" came from a system filter, + it has been escaped into printing characters so as not to mess up log lines. + For the "tell" message, we turn \n back into newline. Also, insert a newline + near the start instead of the ": " string. */ + + if (f.deliver_freeze) + { + 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: "); + + if (ss != NULL) + { + ss[21] = '.'; + ss[22] = '\n'; + } + + ss = s; + while (*ss != 0) + { + if (*ss == '\\' && ss[1] == 'n') + { + *ss++ = ' '; + *ss++ = '\n'; + } + else ss++; + } + moan_tell_someone(freeze_tell, addr_defer, US"Message frozen", + "Message %s has been frozen%s.\nThe sender is <%s>.\n", message_id, + s, sender_address); + } + + /* Log freezing just before we update the -H file, to minimize the chance + of a race problem. */ + + deliver_msglog("*** Frozen%s\n", frozen_info); + log_write(0, LOG_MAIN, "Frozen%s", frozen_info); + } + + /* If there have been any updates to the non-recipients list, or other things + that get written to the spool, we must now update the spool header file so + that it has the right information for the next delivery attempt. If there + was more than one address being delivered, the header_change update is done + earlier, in case one succeeds and then something crashes. */ + + DEBUG(D_deliver) + debug_printf("delivery deferred: update_spool=%d header_rewritten=%d\n", + update_spool, f.header_rewritten); + + if (update_spool || f.header_rewritten) + /* Panic-dies on error */ + (void)spool_write_header(message_id, SW_DELIVERING, NULL); + } + +/* Finished with the message log. If the message is complete, it will have +been unlinked or renamed above. */ + +if (message_logs) (void)fclose(message_log); + +/* Now we can close and remove the journal file. Its only purpose is to record +successfully completed deliveries asap so that this information doesn't get +lost if Exim (or the machine) crashes. Forgetting about a failed delivery is +not serious, as trying it again is not harmful. The journal might not be open +if all addresses were deferred at routing or directing. Nevertheless, we must +remove it if it exists (may have been lying around from a crash during the +previous delivery attempt). We don't remove the journal if a delivery +subprocess failed to pass back delivery information; this is controlled by +the remove_journal flag. When the journal is left, we also don't move the +message off the main spool if frozen and the option is set. It should get moved +at the next attempt, after the journal has been inspected. */ + +if (journal_fd >= 0) (void)close(journal_fd); + +if (remove_journal) + { + uschar * fname = spool_fname(US"input", message_subdir, id, US"-J"); + + if (Uunlink(fname) < 0 && errno != ENOENT) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s: %s", fname, + strerror(errno)); + + /* Move the message off the spool if requested */ + +#ifdef SUPPORT_MOVE_FROZEN_MESSAGES + if (f.deliver_freeze && move_frozen_messages) + (void)spool_move_message(id, message_subdir, US"", US"F"); +#endif + } + +/* Closing the data file frees the lock; if the file has been unlinked it +will go away. Otherwise the message becomes available for another process +to try delivery. */ + +(void)close(deliver_datafile); +deliver_datafile = -1; +DEBUG(D_deliver) debug_printf("end delivery of %s\n", id); + +/* It is unlikely that there will be any cached resources, since they are +released after routing, and in the delivery subprocesses. However, it's +possible for an expansion for something afterwards (for example, +expand_check_condition) to do a lookup. We must therefore be sure everything is +released. */ + +search_tidyup(); +acl_where = ACL_WHERE_UNKNOWN; +return final_yield; +} + + + +void +deliver_init(void) +{ +#ifdef EXIM_TFO_PROBE +tfo_probe(); +#else +f.tcp_fastopen_ok = TRUE; +#endif + + +if (!regex_PIPELINING) regex_PIPELINING = + regex_must_compile(US"\\n250[\\s\\-]PIPELINING(\\s|\\n|$)", FALSE, TRUE); + +if (!regex_SIZE) regex_SIZE = + regex_must_compile(US"\\n250[\\s\\-]SIZE(\\s|\\n|$)", FALSE, TRUE); + +if (!regex_AUTH) regex_AUTH = + regex_must_compile(AUTHS_REGEX, FALSE, TRUE); + +#ifdef SUPPORT_TLS +if (!regex_STARTTLS) regex_STARTTLS = + regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE); + +# ifdef EXPERIMENTAL_REQUIRETLS +if (!regex_REQUIRETLS) regex_REQUIRETLS = + regex_must_compile(US"\\n250[\\s\\-]REQUIRETLS(\\s|\\n|$)", FALSE, TRUE); +# endif +#endif + +if (!regex_CHUNKING) regex_CHUNKING = + regex_must_compile(US"\\n250[\\s\\-]CHUNKING(\\s|\\n|$)", FALSE, TRUE); + +#ifndef DISABLE_PRDR +if (!regex_PRDR) regex_PRDR = + regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE); +#endif + +#ifdef SUPPORT_I18N +if (!regex_UTF8) regex_UTF8 = + regex_must_compile(US"\\n250[\\s\\-]SMTPUTF8(\\s|\\n|$)", FALSE, TRUE); +#endif + +if (!regex_DSN) regex_DSN = + regex_must_compile(US"\\n250[\\s\\-]DSN(\\s|\\n|$)", FALSE, TRUE); + +if (!regex_IGNOREQUOTA) regex_IGNOREQUOTA = + regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE); + +#ifdef EXPERIMENTAL_PIPE_CONNECT +if (!regex_EARLY_PIPE) regex_EARLY_PIPE = + regex_must_compile(US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)", FALSE, TRUE); +#endif +} + + +uschar * +deliver_get_sender_address (uschar * id) +{ +int rc; +uschar * new_sender_address, + * save_sender_address; +BOOL save_qr = f.queue_running; +uschar * spoolname; + +/* make spool_open_datafile non-noisy on fail */ + +f.queue_running = TRUE; + +/* Side effect: message_subdir is set for the (possibly split) spool directory */ + +deliver_datafile = spool_open_datafile(id); +f.queue_running = save_qr; +if (deliver_datafile < 0) + return NULL; + +/* Save and restore the global sender_address. I'm not sure if we should +not save/restore all the other global variables too, because +spool_read_header() may change all of them. But OTOH, when this +deliver_get_sender_address() gets called, the current message is done +already and nobody needs the globals anymore. (HS12, 2015-08-21) */ + +spoolname = string_sprintf("%s-H", id); +save_sender_address = sender_address; + +rc = spool_read_header(spoolname, TRUE, TRUE); + +new_sender_address = sender_address; +sender_address = save_sender_address; + +if (rc != spool_read_OK) + return NULL; + +assert(new_sender_address); + +(void)close(deliver_datafile); +deliver_datafile = -1; + +return new_sender_address; +} + + + +void +delivery_re_exec(int exec_type) +{ +uschar * where; + +if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only) + { + int channel_fd = cutthrough.cctx.sock; + + smtp_peer_options = cutthrough.peer_options; + continue_sequence = 0; + +#ifdef SUPPORT_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; + + where = US"socketpair"; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) != 0) + goto fail; + + where = US"fork"; + if ((pid = fork()) < 0) + goto fail; + + else if (pid == 0) /* child: fork again to totally disconnect */ + { + if (f.running_in_test_harness) millisleep(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 */ + channel_fd = pfd[1]; + } +#endif + + transport_do_pass_socket(cutthrough.transport, cutthrough.host.name, + cutthrough.host.address, message_id, channel_fd); + } +else + { + cancel_cutthrough_connection(TRUE, US"non-continued delivery"); + (void) child_exec_exim(exec_type, FALSE, NULL, FALSE, 2, US"-Mc", message_id); + } +return; /* compiler quietening; control does not reach here. */ + +#ifdef SUPPORT_TLS +fail: + log_write(0, + LOG_MAIN | (exec_type == CEE_EXEC_EXIT ? LOG_PANIC : LOG_PANIC_DIE), + "delivery re-exec %s failed: %s", where, strerror(errno)); + + /* Get here if exec_type == CEE_EXEC_EXIT. + Note: this must be _exit(), not exit(). */ + + _exit(EX_EXECFAILED); +#endif +} + +/* vi: aw ai sw=2 +*/ +/* End of deliver.c */ |