* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* SPDX-License-Identifier: GPL-2.0-or-later */
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 */
+ const uschar *return_path; /* return_path for these addresses */
} pardata;
/* Values for the process_recipients variable */
/* Mutually recursive functions for marking addresses done. */
-static void child_done(address_item *, uschar *);
-static void address_done(address_item *, uschar *);
+static void child_done(address_item *, const uschar *);
+static void address_done(address_item *, const uschar *);
/* Table for turning base-62 numbers into binary */
static address_item *addr_remote = NULL;
static address_item *addr_route = NULL;
static address_item *addr_succeed = NULL;
-static address_item *addr_senddsn = NULL;
static FILE *message_log = NULL;
static BOOL update_spool;
static struct pollfd *parpoll;
static int return_count;
static uschar *frozen_info = US"";
-static uschar *used_return_path = NULL;
+static const uschar * used_return_path = NULL;
*/
address_item *
-deliver_make_addr(uschar *address, BOOL copy)
+deliver_make_addr(const uschar * address, BOOL copy)
{
address_item * addr = store_get(sizeof(address_item), GET_UNTAINTED);
*addr = address_defaults;
for (int i = 2; i > 0; i--)
{
int fd = Uopen(filename,
-#ifdef O_CLOEXEC
- O_CLOEXEC |
-#endif
-#ifdef O_NOFOLLOW
- O_NOFOLLOW |
-#endif
- O_WRONLY|O_APPEND|O_CREAT, mode);
+ EXIM_CLOEXEC | EXIM_NOFOLLOW | O_WRONLY|O_APPEND|O_CREAT, mode);
if (fd >= 0)
{
/* Set the close-on-exec flag and change the owner to the exim uid/gid (this
*/
static BOOL
-same_strings(uschar *one, uschar *two)
+same_strings(const uschar * one, const uschar * two)
{
if (one == two) return TRUE; /* Includes the case where both NULL */
if (!one || !two) return FALSE;
*/
static void
-address_done(address_item *addr, uschar *now)
+address_done(address_item * addr, const uschar * now)
{
update_spool = TRUE; /* Ensure spool gets updated */
*/
static void
-child_done(address_item *addr, uschar *now)
+child_done(address_item * addr, const uschar * now)
{
while (addr->parent)
{
- address_item *aa;
+ address_item * aa;
addr = addr->parent;
if (--addr->child_count > 0) return; /* Incomplete parent */
if (LOGGING(outgoing_port))
g = string_fmt_append(g, ":%d", h->port);
-if (continue_sequence > 1) /*XXX this is wrong for a dropped proxyconn. Would have to pass back from transport */
+if (testflag(addr, af_cont_conn))
g = string_catn(g, US"*", 1);
#ifdef SUPPORT_SOCKS
*/
uschar *
-event_raise(uschar * action, const uschar * event, uschar * ev_data, int * errnop)
+event_raise(const uschar * action, const uschar * event, const uschar * ev_data,
+ int * errnop)
{
-uschar * s;
+const uschar * s;
if (action)
{
DEBUG(D_deliver)
event_name = event;
event_data = ev_data;
- if (!(s = expand_string(action)) && *expand_string_message)
+ if (!(s = expand_cstring(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
+ the caller to modify his normal processing. Copy the string to
+ de-const it.
*/
if (s && *s)
{
debug_printf("Event(%s): event_action returned \"%s\"\n", event, s);
if (errnop)
*errnop = ERRNO_EVENT;
- return s;
+ return string_copy(s);
}
}
return NULL;
msg_event_raise(const uschar * event, const address_item * addr)
{
const uschar * save_domain = deliver_domain;
-uschar * save_local = deliver_localpart;
+const uschar * save_local = deliver_localpart;
const uschar * save_host = deliver_host;
const uschar * save_address = deliver_host_address;
+uschar * save_rn = router_name, * save_tn = transport_name;
const int save_port = deliver_host_port;
router_name = addr->router ? addr->router->name : NULL;
deliver_host = save_host;
deliver_localpart = save_local;
deliver_domain = save_domain;
-router_name = transport_name = NULL;
+router_name = save_rn;
+transport_name = save_tn;
}
#endif /*DISABLE_EVENT*/
* Generate local part for logging *
*************************************************/
-static uschar *
-string_get_lpart_sub(const address_item * addr, uschar * s)
+static const uschar *
+string_get_lpart_sub(const address_item * addr, const uschar * s)
{
#ifdef SUPPORT_I18N
if (testflag(addr, af_utf8_downcvt))
{
- uschar * t = string_localpart_utf8_to_alabel(s, NULL);
+ const uschar * t = string_localpart_utf8_to_alabel(s, NULL);
return t ? t : s; /* t is NULL on a failed conversion */
}
#endif
static gstring *
string_get_localpart(address_item * addr, gstring * yield)
{
-uschar * s;
+const uschar * s;
if (testflag(addr, af_include_affixes) && (s = addr->prefix))
yield = string_cat(yield, string_get_lpart_sub(addr, s));
else
{
uschar * cmp;
- int off = g->ptr; /* start of the "full address" */
+ int off = gstring_length(g); /* start of the "full address" */
if (addr->local_part)
{
g = string_catn(g, US" K", 2);
}
+#ifndef DISABLE_DKIM
+ if (addr->dkim_used && LOGGING(dkim_verbose))
+ {
+ g = string_catn(g, US" DKIM=", 6);
+ g = string_cat(g, addr->dkim_used);
+ }
+#endif
+
/* confirmation message (SMTP (host_used) and LMTP (driver_name)) */
if ( LOGGING(smtp_confirmation)
/* 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));
+log_write(0, flags, "%Y", g);
#ifndef DISABLE_EVENT
if (!msg) msg_event_raise(US"msg:delivery", addr);
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);
+ deliver_msglog("%s %.*s\n", now, g->ptr, g->s);
/* Write the main log and reset the store.
For errors of the type "retry time not reached" (also remotes skipped
log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags,
- "== %s", g->s);
+ "== %Y", g);
store_reset(reset_point);
return;
if (LOGGING(deliver_time))
g = string_append(g, 2, US" DT=", string_timediff(&addr->delivery_time));
-(void) string_from_gstring(g);
-
/* Do the logging. For the message log, "routing failed" for those cases,
just to make it clearer. */
if (driver_kind)
- deliver_msglog("%s %s failed for %s\n", now, driver_kind, g->s);
+ deliver_msglog("%s %s failed for %.*s\n", now, driver_kind, g->ptr, g->s);
else
- deliver_msglog("%s %s\n", now, g->s);
+ deliver_msglog("%s %.*s\n", now, g->ptr, g->s);
-log_write(0, LOG_MAIN, "** %s", g->s);
+log_write(0, LOG_MAIN, "** %Y", g);
store_reset(reset_point);
return;
*/
static void
-post_process_one(address_item *addr, int result, int logflags, int driver_type,
+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;
+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);
(void)close(addr->return_file);
}
-/* Check if the transport notifed continue-conn status explicitly, and
-update our knowlege. */
-
-if (testflag(addr, af_new_conn)) continue_sequence = 1;
-else if (testflag(addr, af_cont_conn)) continue_sequence++;
-
/* The success case happens only after delivery by a transport. */
if (result == OK)
}
else if (tp->expand_gid)
{
+ GET_OPTION("group");
if (!route_find_expanded_group(tp->expand_gid, tp->name, US"transport", gidp,
- &(addr->message)))
+ &addr->message))
{
common_error(FALSE, addr, ERRNO_GIDFAIL, NULL);
return FALSE;
else if (tp->expand_uid)
{
struct passwd *pw;
+ GET_OPTION("user");
if (!route_find_expanded_user(tp->expand_uid, tp->name, US"transport", &pw,
uidp, &(addr->message)))
{
int rc = OK;
int size_limit;
+GET_OPTION("message_size_limit");
deliver_set_expansions(addr);
size_limit = expand_string_integer(tp->message_size_limit, TRUE);
deliver_set_expansions(NULL);
previously_transported(address_item *addr, BOOL testing)
{
uschar * s = string_sprintf("%s/%s",
- addr->unique + (testflag(addr, af_homonym)? 3:0), addr->transport->name);
+ addr->unique + (testflag(addr, af_homonym) ? 3:0), addr->transport->name);
if (tree_search(tree_nonrecipients, s) != 0)
{
else
return_path = sender_address;
+GET_OPTION("return_path");
if (tp->return_path)
{
uschar * new_return_path = expand_string(tp->return_path);
home directory set in the address may already be expanded; a flag is set to
indicate that. In other cases we must expand it. */
+GET_OPTION("home_directory");
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 */
operating systems when running pipes, as some commands (e.g. "rm" under Solaris
2.5) require this. */
+GET_OPTION("current_directory");
working_directory = tp->current_dir ? tp->current_dir : addr->current_dir;
if (working_directory)
{
if (addr->transport->setup)
switch((addr->transport->setup)(addr->transport, addr, NULL, uid, gid,
- &(addr->message)))
+ &addr->message))
{
case DEFER:
addr->transport_return = DEFER;
addr->local_part, tp->name);
/* Setting these globals in the subprocess means we need never clear them */
- transport_name = addr->transport->name;
+
+ transport_name = tp->name;
+ if (addr->router) router_name = addr->router->name;
driver_srcfile = tp->srcfile;
driver_srcline = tp->srcline;
{
ok = transport_set_up_command(&transport_filter_argv,
tp->filter_command,
- TRUE, PANIC, addr, FALSE, US"transport filter", NULL);
+ TSUC_EXPAND_ARGS, PANIC, addr, US"transport filter", NULL);
transport_filter_timeout = tp->filter_timeout;
}
else transport_filter_argv = NULL;
and close the pipe we were writing down before exiting. */
(void)close(pfd[pipe_write]);
- search_tidyup();
- exit(EXIT_SUCCESS);
+ exim_exit(EXIT_SUCCESS);
}
/* Back in the main process: panic if the fork did not succeed. This seems
/* If SPECIAL_WARN is set in the top address, send a warning message. */
-if (addr->special_action == SPECIAL_WARN && addr->transport->warn_message)
+if (addr->special_action == SPECIAL_WARN)
{
- int fd;
- uschar *warn_message;
- pid_t pid;
+ uschar * warn_message = addr->transport->warn_message;
+ GET_OPTION("quota_warn_message");
+ if (warn_message)
+ {
+ int fd;
+ pid_t pid;
- DEBUG(D_deliver) debug_printf("Warning message requested by transport\n");
+ 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);
+ if (!(warn_message = expand_string(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, US"tpt-warning-message")) > 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);
+ else if ((pid = child_open_exim(&fd, US"tpt-warning-message")) > 0)
+ {
+ FILE * f = fdopen(fd, "wb");
+ if (errors_reply_to && !contains_header(US"Reply-To", warn_message))
+ 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. */
+ /* Close and wait for child process to complete, without a timeout. */
- (void)fclose(f);
- (void)child_close(pid, 0);
- }
+ (void)fclose(f);
+ (void)child_close(pid, 0);
+ }
- addr->special_action = SPECIAL_NONE;
+ addr->special_action = SPECIAL_NONE;
+ }
}
}
{
unsigned max_parallel;
+GET_OPTION("max_parallel");
if (!tp->max_parallel) return FALSE;
max_parallel = (unsigned) expand_string_integer(tp->max_parallel, TRUE);
static void
do_local_deliveries(void)
{
-open_db dbblock;
-open_db *dbm_file = NULL;
+open_db dbblock, * dbm_file = NULL;
time_t now = time(NULL);
/* Loop until we have exhausted the supply of local deliveries */
/* Expand the batch_id string for comparison with other addresses.
Expansion failure suppresses batching. */
+ GET_OPTION("batch_id");
if (tp->batch_id)
{
deliver_set_expansions(addr);
if (ok && batch_id)
{
- uschar *bid;
- address_item *save_nextnext = next->next;
+ uschar * bid;
+ address_item * save_nextnext = next->next;
next->next = NULL; /* Expansion for a single address */
deliver_set_expansions(next);
next->next = save_nextnext;
+ GET_OPTION("batch_id");
bid = expand_string(tp->batch_id);
deliver_set_expansions(NULL);
if (!bid)
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
+ 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);
uschar *msg = p->msg;
BOOL done = p->done;
+continue_hostname = NULL;
+continue_transport = NULL;
+
/* 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 poll() tells us, if data is available for reading.
pipeheader[PIPE_HEADER_SIZE] = '\0';
DEBUG(D_deliver)
- debug_printf("got %ld bytes (pipeheader) from transport process %d\n",
- (long) got, pid);
+ debug_printf("got %ld bytes (pipeheader) '%c' from transport process %d\n",
+ (long) got, *id, pid);
{
/* If we can't decode the pipeheader, the subprocess seems to have a
/* Put the amount of data written into the parlist block */
- case 'S':
+ case 'S': /* Size */
memcpy(&(p->transport_count), ptr, sizeof(transport_count));
ptr += sizeof(transport_count);
break;
guarantee it won't be split in the pipe. */
#ifndef DISABLE_TLS
- case 'X':
+ case 'X': /* TLS details */
if (!addr) goto ADDR_MISMATCH; /* Below, in 'A' handler */
switch (*subid)
{
if (*subid > '1') setflag(addr, af_tcp_fastopen_data);
break;
- case 'D':
+ case 'D': /* DSN */
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':
+ case 'A': /* Per-address info */
if (!addr)
{
ADDR_MISMATCH:
switch (*subid)
{
- case 3: /* explicit notification of continued-connection (non)use;
+#ifndef DISABLE_DKIM
+ case '4': /* DKIM information */
+ addr->dkim_used = string_copy(ptr);
+ while(*ptr++);
+ break;
+#endif
+
+ case '3': /* explicit notification of continued-connection (non)use;
overrides caller's knowlege. */
if (*ptr & BIT(1)) setflag(addr, af_new_conn);
else if (*ptr & BIT(2)) setflag(addr, af_cont_conn);
break;
#endif
- case '0':
+ case '0': /* results of trying to send to this address */
DEBUG(D_deliver) debug_printf("A0 %s tret %d\n", addr->address, *ptr);
addr->transport_return = *ptr++;
addr->special_action = *ptr++;
h->dnssec = *ptr == '2' ? DS_YES
: *ptr == '1' ? DS_NO
: DS_UNK;
- ptr++;
addr->host_used = h;
}
- else ptr++;
+ ptr++;
+ continue_flags = 0;
+#ifndef DISABLE_TLS
+ if (testflag(addr, af_cert_verified)) continue_flags |= CTF_CV;
+# ifdef SUPPORT_DANE
+ if (testflag(addr, af_dane_verified)) continue_flags |= CTF_DV;
+# endif
+# ifndef DISABLE_TLS_RESUME
+ if (testflag(addr, af_tls_resume)) continue_flags |= CTF_TR;
+# endif
+#endif
/* Finished with this address */
addr = addr->next;
while (*ptr++) ;
break;
- /* Z marks the logical end of the data. It is followed by '0' if
+ /* Z0 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. */
+ Those are now for historical reasons only; we always clear the continued
+ channel info, and then set it explicitly if the transport indicates it
+ is still open, because it could differ for each transport we are running in
+ parallel.
+
+ Z1 is a suggested message_id to handle next, used during a
+ continued-transport sequence. */
case 'Z':
- if (*ptr == '0')
+ switch (*subid)
{
- continue_transport = NULL;
- continue_hostname = NULL;
+ case '0': /* End marker */
+ done = TRUE;
+ DEBUG(D_deliver) debug_printf("Z0%c item read\n", *ptr);
+ break;
+ case '1': /* Suggested continuation message */
+ Ustrncpy(continue_next_id, ptr, MESSAGE_ID_LENGTH);
+ continue_sequence = atoi(CS ptr + MESSAGE_ID_LENGTH + 1);
+ DEBUG(D_deliver) debug_printf("continue_next_id: %s seq %d\n",
+ continue_next_id, continue_sequence);
+ break;
+ case '2': /* Continued transport, host & addr */
+ {
+ int recvd_fd;
+
+ DEBUG(D_any) if (Ustrcmp(process_purpose, "continued-delivery") != 0)
+ debug_printf("%s becomes continued-delivery\n", process_purpose);
+ process_purpose = US"continued-delivery";
+ continue_transport = string_copy(ptr); while (*ptr++) ;
+ continue_hostname = string_copy(ptr); while (*ptr++) ;
+ continue_host_address = string_copy(ptr); while (*ptr++) ;
+ continue_sequence = atoi(CS ptr);
+
+ dup2((recvd_fd = recv_fd_from_sock(fd)), 0);
+ close(recvd_fd);
+
+ DEBUG(D_deliver)
+ debug_printf("continue: tpt '%s' host '%s' addr '%s' seq %d\n",
+ continue_transport, continue_hostname,
+ continue_host_address, continue_sequence);
+ break;
+ }
+ case '3': /* Continued conn info */
+ smtp_peer_options = ptr[0];
+ f.smtp_authenticated = ptr[1] & 1;
+ break;
+#ifndef DISABLE_TLS
+ case '4': /* Continued TLS info */
+ continue_proxy_cipher = string_copy(ptr);
+ break;
+ case '5': /* Continued DANE info */
+ case '6': /* Continued TLS info */
+# ifdef SUPPORT_DANE
+ continue_proxy_dane = *subid == '5';
+# endif
+ continue_proxy_sni = *ptr ? string_copy(ptr) : NULL;
+ break;
+#endif
+#ifndef DISABLE_ESMTP_LIMITS
+ case '7': /* Continued peer limits */
+ sscanf(CS ptr, "%u %u %u",
+ &continue_limit_mail, &continue_limit_rcpt,
+ &continue_limit_rcptdom);
+ break;
+#endif
+#ifdef SUPPORT_SOCKS
+ case '8': /* Continued proxy info */
+ proxy_local_address = string_copy(ptr); while (*ptr++) ;
+ proxy_local_port = atoi(CS ptr); while (*ptr++) ;
+ proxy_external_address = string_copy(ptr); while (*ptr++) ;
+ proxy_external_port = atoi(CS ptr);
+ break;
+#endif
}
- done = TRUE;
- DEBUG(D_deliver) debug_printf("Z0%c item read\n", *ptr);
break;
/* Anything else is a disaster. */
*/
static void
-remote_post_process(address_item *addr, int logflags, uschar *msg,
+remote_post_process(address_item * addr, int logflags, uschar * msg,
BOOL fallback)
{
/* If any host addresses were found to be unusable, add them to the unusable
while (addr)
{
- address_item *next = addr->next;
+ 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
{
while (parcount > max)
{
- address_item *doneaddr = par_wait();
+ address_item * doneaddr = par_wait();
if (!doneaddr)
{
log_write(0, LOG_MAIN|LOG_PANIC,
}
/* Get the maximum it can handle in one envelope, with zero meaning
- unlimited, which is forced for the MUA wrapper case. */
+ unlimited, which is forced for the MUA wrapper case and if the
+ value could vary depending on the messages.
+ For those, we only split (below) by (tpt,dest,erraddr,hdrs) and rely on the
+ transport splitting further by max_rcp. So we potentially lose some
+ parallellism. */
- address_count_max = tp->max_addresses;
- if (address_count_max == 0 || mua_wrapper) address_count_max = 999999;
+ GET_OPTION("max_rcpt");
+ address_count_max = mua_wrapper || Ustrchr(tp->max_addresses, '$')
+ ? UNLIMITED_ADDRS : expand_max_rcpt(tp->max_addresses);
/************************************************************************/
&& 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;
+ int new_max = remote_delivery_count/remote_max_parallel, message_max;
+ GET_OPTION("connection_max_messages");
+ 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)
/* 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;
- else
- return_path = sender_address;
+ return_path = addr->prop.errors_address
+ ? addr->prop.errors_address : sender_address;
+ GET_OPTION("return_path");
if (tp->return_path)
{
- uschar *new_return_path = expand_string(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)
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. */
+ /* Once we hit the max number of parallel transports set a flag indicating
+ whether there are further addresses that list the same host. This tells the
+ transport to leave the channel open for us. */
+/*XXX maybe we should *count* possible further's, and set continue_more if
+parmax * tpt-max is exceeded? */
- for (next = addr_remote; next && !f.continue_more; next = next->next)
- for (host_item * h = next->host_list; h; h = h->next)
- if (Ustrcmp(h->name, continue_hostname) == 0)
- { f.continue_more = TRUE; break; }
+ if (parcount+1 >= remote_max_parallel)
+ {
+ host_item * h1 = addr->host_list;
+ if (h1)
+ {
+ const uschar * name = continue_hostname ? continue_hostname : h1->name;
+ for (next = addr_remote; next && !f.continue_more; next = next->next)
+ for (host_item * h = next->host_list; h; h = h->next)
+ if (Ustrcmp(h->name, name) == 0)
+ { f.continue_more = TRUE; break; }
+ }
}
+ else DEBUG(D_deliver)
+ debug_printf(
+ "not reached parallelism limit (%d/%d) so not setting continue_more\n",
+ parcount+1, remote_max_parallel);
/* 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
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. */
+ create a pipe when all processes have finished, break the retry loop.
+ Use socketpair() rather than pipe() so we can pass an fd back from the
+ transport process.
+ */
while (!pipe_done)
{
- if (pipe(pfd) == 0) pipe_done = TRUE;
- else if (parcount > 0) parmax = parcount;
- else break;
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, 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
search_tidyup();
+/*
+A continued-tpt will, in the tpt parent here, call par_reduce for
+the one child. But we are hoping to never do continued-transport...
+SO.... we may have called par_reduce for a single child, above when we'd
+hit the limit on child-count. Possibly multiple times with different
+transports and target hosts. Does it matter if several return a suggested
+next-id, and we lose all but the last? Hmm. Less parallel working would
+happen. Perhaps still do continued-tpt once one has been set? No, that won't
+work for all cases.
+BAH.
+Could take the initial continued-tpt hit, and then do the next-id thing?
+
+do_remote_deliveries par_reduce par_wait par_read_pipe
+*/
+
+ /*XXX what about firsttime? */
+ if (continue_transport && !exim_lockfile_needed())
+ if (!continue_wait_db)
+ {
+ continue_wait_db = dbfn_open_multi(
+ string_sprintf("wait-%.200s", continue_transport),
+ O_RDWR,
+ (open_db *) store_get(sizeof(open_db), GET_UNTAINTED));
+ continue_next_id[0] = '\0';
+ }
+
if ((pid = exim_fork(US"transport")) == 0)
{
int fd = pfd[pipe_write];
host_item *h;
/* Setting these globals in the subprocess means we need never clear them */
- transport_name = addr->transport->name;
+
+ transport_name = tp->name;
+ if (addr->router) router_name = addr->router->name;
driver_srcfile = tp->srcfile;
driver_srcline = tp->srcline;
{
uschar * fname = spool_fname(US"input", message_subdir, message_id, US"-D");
- if ((deliver_datafile = Uopen(fname,
-#ifdef O_CLOEXEC
- O_CLOEXEC |
-#endif
- O_RDWR | O_APPEND, 0)) < 0)
+ if ( (deliver_datafile = Uopen(fname, EXIM_CLOEXEC | O_RDWR | O_APPEND, 0))
+ < 0)
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Failed to reopen %s for remote "
"parallel delivery: %s", fname, strerror(errno));
}
- /* Set the close-on-exec flag */
-#ifndef O_CLOEXEC
+#ifndef O_CLOEXEC /* Set the close-on-exec flag */
(void)fcntl(deliver_datafile, F_SETFD, fcntl(deliver_datafile, F_GETFD) |
FD_CLOEXEC);
#endif
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. */
+ the first address, so that gets sent at the start.
+
+ Result item tags:
+ A C D H I K L P R S T X Z
+ */
/* Host unusability information: for most success cases this will
be null. */
{
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);
+ 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
/* 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. */
+ and finally an 'A' item for the remaining data. The actual recipient address
+ is not sent but is implicit in the address-chain being handled. */
for(; addr; addr = addr->next)
{
- uschar *ptr;
+ uschar * ptr;
- /* The certificate verification status goes into the flags */
+#ifndef DISABLE_TLS
+ /* The certificate verification status goes into the flags, in A0 */
if (tls_out.certificate_verified) setflag(addr, af_cert_verified);
-#ifdef SUPPORT_DANE
+# ifdef SUPPORT_DANE
if (tls_out.dane_verified) setflag(addr, af_dane_verified);
-#endif
+# endif
# ifndef DISABLE_TLS_RESUME
if (tls_out.resumption & RESUME_USED) setflag(addr, af_tls_resume);
# endif
/* Use an X item only if there's something to send */
-#ifndef DISABLE_TLS
if (addr->cipher)
{
ptr = big_buffer + sprintf(CS big_buffer, "%.128s", addr->cipher) + 1;
rmt_dlv_checked_write(fd, 'R', '0', big_buffer, ptr - big_buffer);
}
+#ifndef DISABLE_DKIM
+ if (addr->dkim_used && LOGGING(dkim_verbose))
+ {
+ DEBUG(D_deliver) debug_printf("dkim used: %s\n", addr->dkim_used);
+ ptr = big_buffer + sprintf(CS big_buffer, "%.128s", addr->dkim_used) + 1;
+ rmt_dlv_checked_write(fd, 'A', '4', big_buffer, ptr - big_buffer);
+ }
+#endif
+
if (testflag(addr, af_new_conn) || testflag(addr, af_cont_conn))
{
DEBUG(D_deliver) debug_printf("%scontinued-connection\n",
#endif
/* The rest of the information goes in an 'A0' item. */
-
+#ifdef notdef
+ DEBUG(D_deliver)
+ debug_printf("%s %s for MAIL\n",
+ addr->special_action == '=' ? "initial RCPT"
+ : addr->special_action == '-' ? "additional RCPT" : "?",
+ addr->address);
+#endif
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));
if (LOGGING(incoming_interface) && sending_ip_address)
#endif
{
- uschar * ptr;
- ptr = big_buffer + sprintf(CS big_buffer, "%.128s", sending_ip_address) + 1;
+ uschar * 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);
}
+ /* Continuation message-id, if a continuation is for that reason,
+ and the next sequence number (MAIL FROM count) for the connection. */
+
+ if (*continue_next_id)
+ rmt_dlv_checked_write(fd, 'Z', '1', big_buffer,
+ sprintf(CS big_buffer, "%.*s %u",
+ MESSAGE_ID_LENGTH, continue_next_id, continue_sequence+1) + 1);
+
+ /* Connection details, only on the first suggested continuation for
+ wait-db ones, but for all continue-more ones (though any after the
+ delivery proc has the info are pointless). */
+
+ if (continue_hostname)
+ {
+ {
+ uschar * ptr = big_buffer;
+ ptr += sprintf(CS ptr, "%.128s", continue_transport) + 1;
+ ptr += sprintf(CS ptr, "%.128s", continue_hostname) + 1;
+ ptr += sprintf(CS ptr, "%.128s", continue_host_address) + 1;
+ ptr += sprintf(CS ptr, "%u", continue_sequence+1) + 1;
+ rmt_dlv_checked_write(fd, 'Z', '2', big_buffer, ptr - big_buffer);
+ send_fd_over_socket(fd, continue_fd);
+ }
+
+ big_buffer[0] = smtp_peer_options;
+ big_buffer[1] = f.smtp_authenticated ? 1 : 0;
+ rmt_dlv_checked_write(fd, 'Z', '3', big_buffer, 2);
+
+ if (tls_out.active.sock >= 0 || continue_proxy_cipher)
+ rmt_dlv_checked_write(fd, 'Z', '4', big_buffer,
+ sprintf(CS big_buffer, "%.128s", continue_proxy_cipher) + 1);
+
+ if (tls_out.sni)
+ rmt_dlv_checked_write(fd, 'Z',
+#ifdef SUPPORT_DANE
+ tls_out.dane_verified ? '5' : '6',
+#else
+ '6',
+#endif
+ tls_out.sni, Ustrlen(tls_out.sni)+1);
+
+#ifndef DISABLE_ESMTP_LIMITS
+ if (continue_limit_mail || continue_limit_rcpt || continue_limit_rcptdom)
+ rmt_dlv_checked_write(fd, 'Z', '7', big_buffer,
+ sprintf(CS big_buffer, "%u %u %u",
+ continue_limit_mail, continue_limit_rcpt,
+ continue_limit_rcptdom) + 1);
+#endif
+
+#ifdef SUPPORT_SOCKS
+ if (proxy_session)
+ {
+ uschar * ptr = big_buffer;
+ ptr += sprintf(CS ptr, "%.128s", proxy_local_address) + 1;
+ ptr += sprintf(CS ptr, "%u", proxy_local_port) + 1;
+ ptr += sprintf(CS ptr, "%.128s", proxy_external_address) + 1;
+ ptr += sprintf(CS ptr, "%u", proxy_external_port) + 1;
+ rmt_dlv_checked_write(fd, 'Z', '8', big_buffer, ptr - big_buffer);
+ }
+#endif
+ }
+
/* Add termination flag, close the pipe, and that's it. The character
- after 'Z' indicates whether continue_transport is now NULL or not.
+ after "Z0" 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);
+ exim_exit(EXIT_SUCCESS);
}
/* Back in the mainline: close the unwanted half of the pipe. */
(continue_transport gets set to NULL) before we consider any other addresses
in this message. */
- if (continue_transport) par_reduce(0, fallback);
+ if (continue_transport)
+ {
+ par_reduce(0, fallback);
+ if (!continue_next_id && continue_wait_db)
+ { dbfn_close_multi(continue_wait_db); continue_wait_db = NULL; }
+ }
/* 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. Wait long enough for most cases to complete
the transport. */
- else testharness_pause_ms(600);
+ else
+ testharness_pause_ms(600);
continue;
int
deliver_split_address(address_item * addr)
{
-uschar * address = addr->address;
+const uschar * address = addr->address;
uschar * domain;
uschar * t;
int len;
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, address);
+addr->cc_local_part = t = store_get(len+1, address);
while(len-- > 0)
{
int c = *address++;
}
else *t++ = c;
}
-*t = 0;
+*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;
+ uschar * new_address = NULL;
+ const uschar * local_part = addr->cc_local_part;
deliver_domain = addr->domain; /* set $domain */
if (Ustrcmp(t->name, continue_transport) == 0)
{
if (t->info->closedown) (t->info->closedown)(t);
+ continue_transport = NULL;
break;
}
return DELIVER_NOT_ATTEMPTED;
*/
static BOOL
-print_address_information(address_item *addr, FILE *f, uschar *si, uschar *sc,
- uschar *se)
+print_address_information(address_item * addr, FILE * f, uschar * si,
+ uschar * sc, uschar * se)
{
BOOL yield = TRUE;
-uschar *printed = US"";
-address_item *ancestor = addr;
+const uschar * printed = US"";
+address_item * ancestor = addr;
while (ancestor->parent) ancestor = ancestor->parent;
fprintf(f, "%s", CS si);
else
{
- uschar *s = addr->address;
- uschar *ss;
+ const uschar * s = addr->address;
+ const uschar * ss;
if (addr->address[0] == '>') { ss = US"mail"; s++; }
else if (addr->address[0] == '|') ss = US"pipe";
if (ancestor != addr)
{
- uschar *original = ancestor->onetime_parent;
+ const uschar * original = ancestor->onetime_parent;
if (!original) original= ancestor->address;
if (strcmpic(original, printed) != 0)
fprintf(f, "%s(%sgenerated from %s)", sc,
fprintf(f, "\n "); /* sic (because space follows) */
count = 0;
}
+ else if (count > 254) /* arbitrary limit */
+ {
+ fprintf(f, "[truncated]");
+ do s++; while (*s && !(*s == '\\' && s[1] == '\n'));
+ }
}
}
unsigned cnt;
/* 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);
+if (!s)
+ return;
- /* search first ": ". we assume to find the remote-MTA answer there */
- if (!(s = Ustrstr(addr->message, ": ")))
- return; /* not found, bail out */
- s += 2; /* skip ": " */
- cnt = fprintf(f, "Diagnostic-Code: smtp; ");
- }
-/* no message available. do nothing */
-else return;
+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 ": " */
+cnt = fprintf(f, "Diagnostic-Code: smtp; ");
while (*s)
{
*/
static void
-do_duplicate_check(address_item **anchor)
+do_duplicate_check(address_item ** anchor)
{
-address_item *addr;
+address_item * addr;
while ((addr = *anchor))
{
- tree_node *tnode;
+ tree_node * tnode;
if (testflag(addr, af_pfr))
- {
- anchor = &(addr->next);
- }
+ anchor = &addr->next;
else if ((tnode = tree_search(tree_duplicates, addr->unique)))
{
DEBUG(D_deliver|D_route)
else
{
tree_add_duplicate(addr->unique, addr);
- anchor = &(addr->next);
+ anchor = &addr->next;
}
}
}
static FILE *
expand_open(const uschar * filename,
- const uschar * varname, const uschar * reason)
+ const uschar * optname, const uschar * reason)
{
const uschar * s = expand_cstring(filename);
FILE * fp = NULL;
if (!s || !*s)
log_write(0, LOG_MAIN|LOG_PANIC,
- "Failed to expand %s: '%s'\n", varname, filename);
+ "Failed to expand %s: '%s'\n", optname, filename);
else if (*s != '/' || is_tainted(s))
log_write(0, LOG_MAIN|LOG_PANIC,
"%s is not %s after expansion: '%s'\n",
- varname, *s == '/' ? "untainted" : "absolute", s);
+ optname, *s == '/' ? "untainted" : "absolute", s);
else if (!(fp = Ufopen(s, "rb")))
log_write(0, LOG_MAIN|LOG_PANIC, "Failed to open %s for %s "
"message texts: %s", s, reason, strerror(errno));
return fp;
}
-/*************************************************
-* Deliver one message *
-*************************************************/
-
-/* 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.
+/* Output the given header and string, converting either
+the sequence "\n" or a real newline into newline plus space.
+If that still takes us past column 78, look for the last space
+and split there too.
+Append a newline if string did not have one.
+Limit to about 1024 chars total. */
-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).
+static void
+dsn_put_wrapped(FILE * fp, const uschar * header, const uschar * s)
+{
+gstring * g = string_cat(NULL, header);
-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.
+g = string_cat(g, s);
+gstring_release_unused(g);
+fprintf(fp, "%s\n", wrap_header(string_from_gstring(g), 79, 1023, US" ", 1));
+}
-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;
+/*************************************************
+* Send a bounce message *
+*************************************************/
-#ifdef MEASURE_TIMING
-report_time_since(×tamp_startup, US"delivery start"); /* testcase 0022, 2100 */
-#endif
+/* 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. */
-info = queue_run_pid == (pid_t)0
- ? string_sprintf("delivering %s", id)
- : string_sprintf("delivering %s (queue run pid %d)", id, queue_run_pid);
+static void
+send_bounce_message(time_t now, const uschar * logtod)
+{
+pid_t pid;
+int fd;
-/* 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. */
+if (!(bounce_recipient = addr_failed->prop.errors_address))
+ bounce_recipient = sender_address;
-set_process_info("%s", info);
+/* Make a subprocess to send a message, using its stdin */
-if ( !(debug_selector & D_process_info)
- && (debug_selector & (D_deliver|D_queue_run|D_v))
- )
- debug_printf("%s\n", info);
+if ((pid = child_open_exim(&fd, US"bounce-message")) < 0)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Process %d (parent %d) failed to "
+ "create child process to send failure message: %s", getpid(),
+ getppid(), strerror(errno));
-/* 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.) */
+/* Creation of child succeeded */
-#ifdef SA_NOCLDWAIT
+else
{
- 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
+ int ch, rc, filecount = 0, rcount = 0;
+ uschar * bcc, * emf_text;
+ FILE * fp = fdopen(fd, "wb"), * 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, * dsnlimitmsg, * dsnnotifyhdr;
+ int topt;
+ address_item ** paddr;
+ address_item * msgchain = NULL, ** pmsgchain = &msgchain;
+ address_item * handled_addr = NULL;
-/* 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. */
+ 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 (address_item * 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 */
-if (id != message_id)
- Ustrcpy(message_id, id);
-f.deliver_force = forced;
-return_count = 0;
-message_size = 0;
+ /* 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. */
-/* Initialize some flags */
+ for (address_item * 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");
-update_spool = FALSE;
-remove_journal = TRUE;
+ /* Output the standard headers */
-/* Set a known context for any ACLs we call via expansions */
-acl_where = ACL_WHERE_DELIVERY;
+ 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);
+ moan_write_references(fp, NULL);
-/* 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. */
+ /* generate boundary string and output MIME-Headers */
+ bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
-random_seed = 0;
+ fprintf(fp, "Content-Type: multipart/report;"
+ " report-type=delivery-status; boundary=%s\n"
+ "MIME-Version: 1.0\n",
+ bound);
-/* 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. */
+ /* Open a template file if one is provided. Log failure to open, but
+ carry on - default texts will be used. */
-if ((deliver_datafile = spool_open_datafile(id)) < 0)
- return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */
+ GET_OPTION("bounce_message_file");
+ if (bounce_message_file)
+ emf = expand_open(bounce_message_file,
+ US"bounce_message_file", US"error");
-/* 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. */
+ /* Quietly copy to configured additional addresses if required. */
-/* 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. */
+ if ((bcc = moan_check_errorcopy(bounce_recipient)))
+ fprintf(fp, "Bcc: %s\n", bcc);
- {
- 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));
+ /* 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 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 ((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" : "");
- 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'];
- }
+ /* 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 (address_item * 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 (address_item * 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 (address_item * addr = handled_addr; addr; addr = addr->next)
+ {
+ host_item * hu;
+#ifdef EXPERIMENTAL_DSN_INFO
+ const uschar * s;
+#endif
+
+ print_dsn_addr_action(fp, addr, US"failed", US"5.0.0");
+
+ if ((hu = addr->host_used) && hu->name)
+ {
+ fprintf(fp, "Remote-MTA: dns; %s\n", hu->name);
+#ifdef EXPERIMENTAL_DSN_INFO
+ 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)
+ dsn_put_wrapped(fp, US"X-Remote-MTA-smtp-greeting: X-str; ", s);
+ if ((s = addr->helo_response) && *s)
+ dsn_put_wrapped(fp, US"X-Remote-MTA-helo-response: X-str; ", s);
+ if (testflag(addr, af_pass_message) && (s = addr->message) && *s)
+ dsn_put_wrapped(fp, US"X-Exim-Diagnostic: X-str; ", s);
+#endif
+ print_dsn_diagnostic_code(addr, fp);
+ }
+#ifdef EXPERIMENTAL_DSN_INFO
+ else if (testflag(addr, af_pass_message) && (s = addr->message) && *s)
+ dsn_put_wrapped(fp, US"X-Exim-Diagnostic: X-str; ", s);
+#endif
+ 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 | topt_truncate_headers;
+ 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 */
+
+ /* 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 (address_item * 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);
+ }
+ }
+}
+
+/*************************************************
+* Send a warning message *
+*************************************************/
+/* Return: boolean success */
+
+static BOOL
+send_warning_message(const uschar * recipients, int queue_time, int show_time)
+{
+int fd;
+pid_t pid = child_open_exim(&fd, US"delay-warning-message");
+FILE * wmf = NULL, * f = fdopen(fd, "wb");
+uschar * wmf_text, * bound;
+transport_ctx tctx = {{0}};
+
+
+if (pid <= 0) return FALSE;
+
+GET_OPTION("warn_message_file");
+if (warn_message_file)
+ wmf = expand_open(warn_message_file,
+ US"warn_message_file", US"warning");
+
+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);
+moan_write_references(f, NULL);
+
+/* 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 (header_line * 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 */
+
+fputc('\n', f);
+for (address_item * addr = addr_defer; addr; addr = 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 (address_item * addr = addr_defer; addr; addr = addr->next)
+ {
+ host_item * hu;
+
+ print_dsn_addr_action(f, addr, US"delayed", US"4.0.0");
+
+ if ((hu = addr->host_used) && hu->name)
+ {
+ fprintf(f, "Remote-MTA: dns; %s\n", hu->name);
+ print_dsn_diagnostic_code(addr, 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_truncate_headers | 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);
+return child_close(pid, 0) == 0;
+}
+
+/*************************************************
+* Send a success-DSN *
+*************************************************/
+
+static void
+maybe_send_dsn(const address_item * const addr_succeed)
+{
+address_item * addr_senddsn = NULL;
+
+for (const address_item * a = addr_succeed; a; a = a->next)
+ {
+ /* af_ignore_error not honored here. it's not an error */
+ DEBUG(D_deliver) debug_printf("DSN: processing router : %s\n"
+ "DSN: processing successful delivery address: %s\n"
+ "DSN: Sender_address: %s\n"
+ "DSN: orcpt: %s flags: 0x%x\n"
+ "DSN: envid: %s ret: %d\n"
+ "DSN: Final recipient: %s\n"
+ "DSN: Remote SMTP server supports DSN: %d\n",
+ a->router ? a->router->name : US"(unknown)",
+ a->address,
+ sender_address,
+ a->dsn_orcpt ? a->dsn_orcpt : US"NULL",
+ a->dsn_flags,
+ dsn_envid ? dsn_envid : US"NULL", dsn_ret,
+ a->address,
+ a->dsn_aware
+ );
+
+ /* send report if next hop not DSN aware or a router flagged "last DSN hop"
+ and a report was requested */
+
+ if ( (a->dsn_aware != dsn_support_yes || a->dsn_flags & rf_dsnlasthop)
+ && a->dsn_flags & rf_notify_success
+ )
+ {
+ /* copy and relink address_item and send report with all of them at once later */
+ address_item * addr_next = addr_senddsn;
+ addr_senddsn = store_get(sizeof(address_item), GET_UNTAINTED);
+ *addr_senddsn = *a;
+ addr_senddsn->next = addr_next;
+ }
+ else
+ DEBUG(D_deliver) debug_printf("DSN: not sending DSN success message\n");
+ }
+
+if (addr_senddsn)
+ { /* create exim process to send message */
+ int fd;
+ pid_t pid = child_open_exim(&fd, US"DSN");
+
+ DEBUG(D_deliver) debug_printf("DSN: child_open_exim returns: %d\n", pid);
+
+ if (pid < 0) /* Creation of child failed */
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Process %d (parent %d) failed to "
+ "create child process to send success-dsn message: %s", getpid(),
+ getppid(), strerror(errno));
+
+ DEBUG(D_deliver) debug_printf("DSN: child_open_exim failed\n");
+ }
+ else /* Creation of child succeeded */
+ {
+ FILE * f = fdopen(fd, "wb");
+ /* 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 success-dsn 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);
+
+ moan_write_from(f);
+ fprintf(f, "Auto-Submitted: auto-generated\n"
+ "To: %s\n"
+ "Subject: Delivery Status Notification\n",
+ sender_address);
+ moan_write_references(f, NULL);
+ fprintf(f, "Content-Type: multipart/report;"
+ " report-type=delivery-status; boundary=%s\n"
+ "MIME-Version: 1.0\n\n"
+
+ "--%s\n"
+ "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",
+ bound, bound);
+
+ for (address_item * a = addr_senddsn; a; a = a->next)
+ fprintf(f, "<%s> (relayed %s)\n\n",
+ a->address,
+ a->dsn_flags & rf_dsnlasthop ? "via non DSN router"
+ : a->dsn_aware == dsn_support_no ? "to non-DSN-aware mailer"
+ : "via non \"Remote SMTP\" router"
+ );
+
+ fprintf(f, "--%s\n"
+ "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 (address_item * a = addr_senddsn; a; a = a->next)
+ {
+ host_item * hu;
+
+ print_dsn_addr_action(f, a, US"delivered", US"2.0.0");
+
+ if ((hu = a->host_used) && hu->name)
+ fprintf(f, "Remote-MTA: dns; %s\nDiagnostic-Code: smtp; 250 Ok\n\n",
+ hu->name);
+ else
+ fprintf(f, "Diagnostic-Code: X-Exim; relayed via non %s router\n\n",
+ a->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_truncate_headers | topt_no_body;
+ /*XXX hmm, FALSE(fail) retval ignored.
+ Could error for any number of reasons, and they are not handled. */
+ transport_write_message(&tctx, 0);
+ fflush(f);
+
+ fprintf(f,"\n--%s--\n", bound);
+
+ fflush(f);
+ fclose(f);
+ (void) child_close(pid, 0); /* Waits for child to close, no timeout */
+ }
+ }
+}
+
+/*************************************************
+* 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(const uschar * id, BOOL forced, BOOL give_up)
+{
+int i, rc, final_yield, process_recipients;
+time_t now;
+address_item * addr_last;
+uschar * filter_message, * info;
+open_db dbblock, * dbm_file;
+extern int acl_where;
+
+CONTINUED_ID:
+final_yield = DELIVER_ATTEMPTED_NORMAL;
+now = time(NULL);
+addr_last = NULL;
+filter_message = NULL;
+process_recipients = RECIP_ACCEPT;
+
+#ifdef MEASURE_TIMING
+report_time_since(×tamp_startup, US"delivery start"); /* testcase 0022, 2100 */
+#endif
+
+info = queue_run_pid == (pid_t)0
+ ? string_sprintf("delivering %s", id)
+ : string_sprintf("delivering %s (queue run pid %d)", id, queue_run_pid);
+
+/* 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;
+ /*III subsec precision?*/
+ for (i = 0; i < MESSAGE_ID_TIME_LEN; 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. */
uschar * fname = spool_fname(US"input", message_subdir, id, US"-J");
FILE * jread;
- if ( (journal_fd = Uopen(fname, O_RDWR|O_APPEND
-#ifdef O_CLOEXEC
- | O_CLOEXEC
-#endif
-#ifdef O_NOFOLLOW
- | O_NOFOLLOW
-#endif
- , SPOOL_MODE)) >= 0
+ if ( (journal_fd = Uopen(fname,
+ O_RDWR|O_APPEND | EXIM_CLOEXEC | EXIM_NOFOLLOW, SPOOL_MODE)) >= 0
&& lseek(journal_fd, 0, SEEK_SET) == 0
&& (jread = fdopen(journal_fd, "rb"))
)
/* Any error in the filter file causes a delivery to be abandoned. */
+ GET_OPTION("system_filter");
redirect.string = system_filter;
redirect.isfile = TRUE;
redirect.check_owner = redirect.check_group = FALSE;
if (p->address[0] == '|')
{
type = US"pipe";
+ GET_OPTION("system_filter_pipe_transport");
tpname = system_filter_pipe_transport;
address_pipe = p->address;
}
else if (p->address[0] == '>')
{
type = US"reply";
+ GET_OPTION("system_filter_reply_transport");
tpname = system_filter_reply_transport;
}
else
if (p->address[Ustrlen(p->address)-1] == '/')
{
type = US"directory";
+ GET_OPTION("system_filter_directory_transport");
tpname = system_filter_directory_transport;
}
else
{
type = US"file";
+ GET_OPTION("system_filter_file_transport");
tpname = system_filter_file_transport;
}
address_file = p->address;
transport_instance *tp;
for (tp = transports; tp; tp = tp->next)
if (Ustrcmp(tp->name, tpname) == 0)
- {
- p->transport = tp;
- break;
- }
+ { p->transport = tp; break; }
if (!tp)
p->message = string_sprintf("failed to find \"%s\" transport "
"for system filter delivery", tpname);
if (!p->transport)
{
- address_item *badp = p;
+ 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 */
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);
+ 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))
case RECIP_FAIL:
new->message = US"delivery cancelled by administrator";
+ /* not setting af_pass_message here means that will not
+ appear in the bounce message */
/* Fall through */
/* Common code for the failure cases above. If this is not a bounce
The incident has already been logged. */
RECIP_QUEUE_FAILED:
- if (sender_address[0])
+ if (*sender_address)
{
new->next = addr_failed;
addr_failed = new;
#ifndef DISABLE_EVENT
if (process_recipients != RECIP_ACCEPT && event_action)
{
- uschar * save_local = deliver_localpart;
+ const uschar * save_local = deliver_localpart;
const uschar * save_domain = deliver_domain;
- uschar * addr = new->address, * errmsg = NULL;
+ const uschar * addr = new->address;
+ uschar * errmsg = NULL;
int start, end, dom;
if (!parse_extract_address(addr, &errmsg, &start, &end, &dom, TRUE))
f.header_rewritten = FALSE; /* No headers rewritten yet */
while (addr_new) /* Loop until all addresses dealt with */
{
- address_item *addr, *parent;
+ 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. */
+ not exist. In both cases, dbm_file is NULL. For the first stage of a 2-phase
+ queue run don't bother checking domain- or address-retry info; they will take
+ effect on the second stage. */
- if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE)))
- DEBUG(D_deliver|D_retry|D_route|D_hints_lookup)
- debug_printf("no retry data available\n");
+ if (f.queue_2stage)
+ dbm_file = NULL;
+ else
+ {
+ /* If we have transaction-capable hintsdbs, open the retry db without
+ locking, and leave open for the transport process and for subsequent
+ deliveries. If the open fails, tag that explicitly for the transport but
+ retry the open next time around, in case it was created in the interim. */
+
+ if (continue_retry_db == (open_db *)-1)
+ continue_retry_db = NULL;
+
+ if (continue_retry_db)
+ dbm_file = continue_retry_db;
+ else if (!exim_lockfile_needed() && continue_transport)
+ {
+ dbm_file = dbfn_open_multi(US"retry", O_RDONLY, &dbblock);
+ continue_retry_db = dbm_file ? dbm_file : (open_db *)-1;
+ }
+ else
+ dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE);
+
+ if (!dbm_file)
+ 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. */
addr->unique =
string_sprintf("%s:%s", addr->address, addr->parent->unique +
- (testflag(addr->parent, af_homonym)? 3:0));
+ (testflag(addr->parent, af_homonym) ? 3:0));
addr->address_retry_key = addr->domain_retry_key =
string_sprintf("T:%s", addr->unique);
/* 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 (dbm_file && !continue_retry_db)
+ { dbfn_close(dbm_file); dbm_file = NULL; }
/* 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.
else if (testflag(addr, af_dr_retry_exists))
{
- uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
+ 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);
}
} /* 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. */
+ any rerouted addresses that got put back on the new chain. */
+if (dbm_file) /* Can only be continue_retry_db */
+ { dbfn_close_multi(continue_retry_db); continue_retry_db = dbm_file = NULL; }
/* Debugging: show the results of the routing */
uschar * fname = spool_fname(US"input", message_subdir, id, US"-J");
if ((journal_fd = Uopen(fname,
-#ifdef O_CLOEXEC
- O_CLOEXEC |
-#endif
- O_WRONLY|O_APPEND|O_CREAT|O_EXCL, SPOOL_MODE)) < 0)
+ EXIM_CLOEXEC | O_WRONLY|O_APPEND|O_CREAT|O_EXCL, SPOOL_MODE)) < 0)
{
log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't open journal file %s: %s",
fname, strerror(errno));
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 (address_item * a = addr_succeed; a; a = a->next)
- {
- /* af_ignore_error not honored here. it's not an error */
- DEBUG(D_deliver) debug_printf("DSN: processing router : %s\n"
- "DSN: processing successful delivery address: %s\n"
- "DSN: Sender_address: %s\n"
- "DSN: orcpt: %s flags: 0x%x\n"
- "DSN: envid: %s ret: %d\n"
- "DSN: Final recipient: %s\n"
- "DSN: Remote SMTP server supports DSN: %d\n",
- a->router ? a->router->name : US"(unknown)",
- a->address,
- sender_address,
- a->dsn_orcpt ? a->dsn_orcpt : US"NULL",
- a->dsn_flags,
- dsn_envid ? dsn_envid : US"NULL", dsn_ret,
- a->address,
- a->dsn_aware
- );
-
- /* send report if next hop not DSN aware or a router flagged "last DSN hop"
- and a report was requested */
-
- if ( (a->dsn_aware != dsn_support_yes || a->dsn_flags & rf_dsnlasthop)
- && a->dsn_flags & rf_notify_success
- )
- {
- /* copy and relink address_item and send report with all of them at once later */
- address_item * addr_next = addr_senddsn;
- addr_senddsn = store_get(sizeof(address_item), GET_UNTAINTED);
- *addr_senddsn = *a;
- 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, US"DSN");
-
- DEBUG(D_deliver) debug_printf("DSN: child_open_exim returns: %d\n", pid);
-
- if (pid < 0) /* Creation of child failed */
- {
- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Process %d (parent %d) failed to "
- "create child process to send success-dsn message: %s", getpid(),
- getppid(), strerror(errno));
-
- DEBUG(D_deliver) debug_printf("DSN: child_open_exim failed\n");
- }
- else /* Creation of child succeeded */
- {
- FILE * f = fdopen(fd, "wb");
- /* 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 success-dsn 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);
-
- moan_write_from(f);
- fprintf(f, "Auto-Submitted: auto-generated\n"
- "To: %s\n"
- "Subject: Delivery Status Notification\n",
- sender_address);
- moan_write_references(f, NULL);
- fprintf(f, "Content-Type: multipart/report;"
- " report-type=delivery-status; boundary=%s\n"
- "MIME-Version: 1.0\n\n"
-
- "--%s\n"
- "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",
- bound, bound);
-
- for (address_item * a = addr_senddsn; a; a = a->next)
- fprintf(f, "<%s> (relayed %s)\n\n",
- a->address,
- a->dsn_flags & rf_dsnlasthop ? "via non DSN router"
- : a->dsn_aware == dsn_support_no ? "to non-DSN-aware mailer"
- : "via non \"Remote SMTP\" router"
- );
-
- fprintf(f, "--%s\n"
- "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 (address_item * a = addr_senddsn; a; a = a->next)
- {
- host_item * hu;
-
- print_dsn_addr_action(f, a, US"delivered", US"2.0.0");
-
- if ((hu = a->host_used) && hu->name)
- fprintf(f, "Remote-MTA: dns; %s\nDiagnostic-Code: smtp; 250 Ok\n\n",
- hu->name);
- else
- fprintf(f, "Diagnostic-Code: X-Exim; relayed via non %s router\n\n",
- a->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 */
+ final_yield = DELIVER_MUA_FAILED;
+ addr_failed = NULL;
+ }
+ }
- /* Write the original email out */
+/* 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. */
- tctx.u.fd = fd;
- tctx.options = topt_add_return_path | topt_no_body;
- /*XXX hmm, FALSE(fail) retval ignored.
- Could error for any number of reasons, and they are not handled. */
- transport_write_message(&tctx, 0);
- fflush(f);
+else if (!f.dont_deliver)
+ retry_update(&addr_defer, &addr_failed, &addr_succeed);
- fprintf(f,"\n--%s--\n", bound);
+/* Send DSN for successful messages if requested */
- fflush(f);
- fclose(f);
- rc = child_close(pid, 0); /* Waits for child to close, no timeout */
- }
- }
+maybe_send_dsn(addr_succeed);
/* 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
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;
+ const uschar * logtod = tod_stamp(tod_log);
+ address_item * addr;
/* There are weird cases when logging is disabled in the transport. However,
there may not be a transport (address failed by a router). */
/* 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. */
+ that have the same error address. */
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, US"bounce-message")) < 0)
- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Process %d (parent %d) failed to "
- "create child process to send failure message: %s", getpid(),
- getppid(), strerror(errno));
-
- /* 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);
- moan_write_references(fp, NULL);
-
- /* 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)
- emf = expand_open(bounce_message_file,
- US"bounce_message_file", US"error");
-
- /* Quietly copy to configured additional addresses if required. */
-
- if ((bcc = moan_check_errorcopy(bounce_recipient)))
- fprintf(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;
-
- print_dsn_addr_action(fp, addr, US"failed", US"5.0.0");
-
- 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; %.900s\n", s);
- if ((s = addr->helo_response) && *s)
- fprintf(fp, "X-Remote-MTA-helo-response: X-str; %.900s\n", s);
- if ((s = addr->message) && *s)
- fprintf(fp, "X-Exim-Diagnostic: X-str; %.900s\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 */
-
- /* 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);
- }
- }
- }
+ send_bounce_message(now, logtod);
}
f.disable_logging = FALSE; /* In case left set */
else if (addr_defer != (address_item *)(+1))
{
- uschar *recipients = US"";
+ uschar * recipients = US"";
BOOL want_warning_msg = FALSE;
deliver_domain = testflag(addr_defer, af_pfr)
for (address_item * addr = addr_defer; addr; addr = addr->next)
{
- address_item *otaddr;
+ address_item * otaddr;
if (addr->basic_errno > ERRNO_WARN_BASE) want_warning_msg = TRUE;
for (i = 0; i < recipients_count; i++)
{
- uschar *r = recipients_list[i].address;
+ const uschar * r = recipients_list[i].address;
if (Ustrcmp(otaddr->onetime_parent, r) == 0) t = i;
if (Ustrcmp(otaddr->address, r) == 0) break;
}
if (sender_address[0])
{
- uschar * s = addr->prop.errors_address;
+ const uschar * s = addr->prop.errors_address;
if (!s) s = sender_address;
if (Ustrstr(recipients, s) == NULL)
recipients = string_sprintf("%s%s%s", recipients,
|| 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")
- )
- )
+ && sender_address[0] != 0)
{
- int count;
- int show_time;
- int queue_time = time(NULL) - received_time.tv_sec;
-
- queue_time = test_harness_fudged_queue_time(queue_time);
-
- /* 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)
+ GET_OPTION("delay_warning_condition");
+ if ( ( !delay_warning_condition
+ || expand_check_condition(delay_warning_condition,
+ US"delay_warning", US"option")
+ )
+ )
{
- header_line *h;
- int fd;
- pid_t pid = child_open_exim(&fd, US"delay-warning-message");
-
- if (pid > 0)
- {
- uschar * wmf_text;
- FILE * wmf = NULL;
- FILE * f = fdopen(fd, "wb");
- uschar * bound;
- transport_ctx tctx = {{0}};
-
- if (warn_message_file)
- wmf = expand_open(warn_message_file,
- US"warn_message_file", US"warning");
-
- 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);
- moan_write_references(f, NULL);
-
- /* 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 ");
+ int count;
+ int show_time;
+ int queue_time = time(NULL) - received_time.tv_sec;
- 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");
- }
+ queue_time = test_harness_fudged_queue_time(queue_time);
- /* List the addresses, with error information if allowed */
+ /* See how many warnings we should have sent by now */
- fputc('\n', f);
- for (address_item * addr = addr_defer; addr; addr = addr->next)
- {
- if (print_address_information(addr, f, US" ", US"\n ", US""))
- print_address_error(addr, f, US"Delay reason: ");
- fputc('\n', f);
- }
- fputc('\n', f);
+ for (count = 0; count < delay_warning[1]; count++)
+ if (queue_time < delay_warning[count+2]) break;
- /* Final text */
+ show_time = delay_warning[count+1];
- 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");
- }
+ 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;
+ }
- /* 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);
+ 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 (dsn_envid)
+ if (warning_count < count)
+ if (send_warning_message(recipients, queue_time, show_time))
{
- /* 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 (address_item * addr = addr_defer; addr; addr = addr->next)
- {
- host_item * hu;
-
- print_dsn_addr_action(f, addr, US"delayed", US"4.0.0");
-
- if ((hu = addr->host_used) && hu->name)
- {
- fprintf(f, "Remote-MTA: dns; %s\n", hu->name);
- print_dsn_diagnostic_code(addr, 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 */
- }
- }
+ warning_count = count;
+ update_spool = TRUE; /* Ensure spool rewritten */
+ }
}
}
if (f.deliver_freeze)
{
- if (freeze_tell && freeze_tell[0] != 0 && !f.local_error_message)
+ if (freeze_tell && *freeze_tell && !f.local_error_message)
{
- uschar *s = string_copy(frozen_info);
- uschar *ss = Ustrstr(s, " by the system filter: ");
+ uschar * s = string_copy(frozen_info);
+ uschar * ss = Ustrstr(s, " by the system filter: ");
- if (ss != NULL)
+ if (ss)
{
ss[21] = '.';
ss[22] = '\n';
}
- ss = s;
- while (*ss != 0)
- {
+ for (ss = s; *ss; )
if (*ss == '\\' && ss[1] == 'n')
- {
- *ss++ = ' ';
- *ss++ = '\n';
- }
- else ss++;
- }
+ { *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);
report_time_since(×tamp_startup, US"delivery end"); /* testcase 0005 */
#endif
+/* If the transport suggested another message to deliver, go round again. */
+
+if (final_yield == DELIVER_ATTEMPTED_NORMAL && *continue_next_id)
+ {
+ addr_defer = addr_failed = addr_succeed = NULL;
+ tree_duplicates = NULL; /* discard dups info from old message */
+ id = string_copyn(continue_next_id, MESSAGE_ID_LENGTH);
+ continue_next_id[0] = '\0';
+ goto CONTINUED_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,