* Exim - an Internet mail transport agent *
*************************************************/
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
/* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 - 2021 */
/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
/* The main code for delivering a message. */
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 */
*/
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);
}
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);
/* 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);
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++;
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. */
+ most normal messages it will remain NULL all the time.
+
+ 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':
+ if (*ptr == '0')
+ {
+ continue_transport = NULL;
+ continue_hostname = NULL;
+ }
+ done = TRUE;
+ DEBUG(D_deliver) debug_printf("Z0%c item read\n", *ptr);
+ break;
+ case '1':
+ if (continue_hostname)
+ {
+ Ustrncpy(continue_next_id, ptr, MESSAGE_ID_LENGTH);
+ continue_sequence++;
+ }
+ DEBUG(D_deliver) debug_printf("continue_next_id: %s%s\n",
+ continue_next_id, continue_hostname ? "" : " (ignored)");
+ break;
}
- 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;
+ address_count_max = mua_wrapper || Ustrchr(tp->max_addresses, '$')
+ ? UNLIMITED_ADDRS : expand_max_rcpt(tp->max_addresses);
/************************************************************************/
else
return_path = 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)
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
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",
rmt_dlv_checked_write(fd, 'I', '0', big_buffer, ptr - big_buffer);
}
+ /* Continuation message-id */
+ if (*continue_next_id)
+ rmt_dlv_checked_write(fd, 'Z', '1', continue_next_id, MESSAGE_ID_LENGTH);
+
/* 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. */
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 */
*/
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.
+/* 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 no delivery is attempted for any of the above reasons, the function returns
-DELIVER_NOT_ATTEMPTED.
+static void
+dsn_put_wrapped(FILE * fp, const uschar * header, const uschar * s)
+{
+gstring * g = string_cat(NULL, header);
-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).
+g = string_cat(g, s);
+gstring_release_unused(g);
+fprintf(fp, "%s\n", wrap_header(string_from_gstring(g), 79, 1023, US" ", 1));
+}
-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)
-*/
+/*************************************************
+* Send a bounce message *
+*************************************************/
-int
-deliver_message(uschar *id, BOOL forced, BOOL give_up)
+/* 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. */
+
+static void
+send_bounce_message(time_t now, const uschar * logtod)
{
-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;
+pid_t pid;
+int fd;
-#ifdef MEASURE_TIMING
-report_time_since(×tamp_startup, US"delivery start"); /* testcase 0022, 2100 */
-#endif
+if (!(bounce_recipient = addr_failed->prop.errors_address))
+ bounce_recipient = sender_address;
-info = queue_run_pid == (pid_t)0
- ? string_sprintf("delivering %s", id)
- : string_sprintf("delivering %s (queue run pid %d)", id, queue_run_pid);
+/* Make a subprocess to send a message, using its stdin */
-/* 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 ((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));
-set_process_info("%s", info);
+/* Creation of child succeeded */
-if ( !(debug_selector & D_process_info)
- && (debug_selector & (D_deliver|D_queue_run|D_v))
- )
- debug_printf("%s\n", info);
+else
+ {
+ 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;
-/* 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.) */
+ 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 */
-#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
+ /* 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. */
-/* 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. */
+ 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");
-if (id != message_id)
- Ustrcpy(message_id, id);
-f.deliver_force = forced;
-return_count = 0;
-message_size = 0;
+ /* Output the standard headers */
-/* Initialize some flags */
+ 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);
-update_spool = FALSE;
-remove_journal = TRUE;
+ /* generate boundary string and output MIME-Headers */
+ bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
-/* Set a known context for any ACLs we call via expansions */
-acl_where = ACL_WHERE_DELIVERY;
+ fprintf(fp, "Content-Type: multipart/report;"
+ " report-type=delivery-status; boundary=%s\n"
+ "MIME-Version: 1.0\n",
+ bound);
-/* 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. */
+ /* Open a template file if one is provided. Log failure to open, but
+ carry on - default texts will be used. */
-random_seed = 0;
+ GET_OPTION("bounce_message_file");
+ if (bounce_message_file)
+ emf = expand_open(bounce_message_file,
+ US"bounce_message_file", US"error");
-/* 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. */
+ /* Quietly copy to configured additional addresses if required. */
-if ((deliver_datafile = spool_open_datafile(id)) < 0)
- return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */
+ if ((bcc = moan_check_errorcopy(bounce_recipient)))
+ fprintf(fp, "Bcc: %s\n", bcc);
-/* 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. */
+ /* 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. */
-/* 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 ((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" : "");
- {
- uschar * spoolname = string_sprintf("%s-H", id);
- if ((rc = spool_read_header(spoolname, TRUE, TRUE)) != spool_read_OK)
+ /* 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
{
- 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);
- }
+ 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
- log_write(0, LOG_MAIN, "Error reading spool file %s: %s", spoolname,
- strerror(errno));
+ 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);
- /* 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. */
+ /* 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. */
- if (rc != spool_read_hdrerror)
+ 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)
{
- 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'];
+ paddr = &addr->next;
+ filecount++;
}
- /* If we've had this malformed message too long, sling it. */
+ /* Else save so that we can tick off the recipient when the
+ message is sent. */
- if (now - received_time.tv_sec > keep_malformed)
+ else
{
- Uunlink(spool_fname(US"msglog", message_subdir, id, US""));
- Uunlink(spool_fname(US"input", message_subdir, id, US"-D"));
- Uunlink(spool_fname(US"input", message_subdir, id, US"-H"));
- Uunlink(spool_fname(US"input", message_subdir, id, US"-J"));
- log_write(0, LOG_MAIN, "Message removed because older than %s",
- readconf_printtime(keep_malformed));
+ *paddr = addr->next;
+ addr->next = handled_addr;
+ handled_addr = addr;
}
-
- (void)close(deliver_datafile);
- deliver_datafile = -1;
- return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */
}
- }
-/* The spool header file has been read. Look to see if there is an existing
-journal file for this message. If there is, it means that a previous delivery
-attempt crashed (program or host) before it could update the spool header file.
-Read the list of delivered addresses from the journal and add them to the
-nonrecipients tree. Then update the spool file. We can leave the journal in
-existence, as it will get further successful deliveries added to it in this
-run, and it will be deleted if this function gets to its end successfully.
-Otherwise it might be needed again. */
+ fputc('\n', fp);
- {
- uschar * fname = spool_fname(US"input", message_subdir, id, US"-J");
- FILE * jread;
+ /* Get the next text, whether we need it or not, so as to be
+ positioned for the one after. */
- if ( (journal_fd = Uopen(fname, O_RDWR|O_APPEND
-#ifdef O_CLOEXEC
- | O_CLOEXEC
-#endif
-#ifdef O_NOFOLLOW
- | O_NOFOLLOW
-#endif
- , SPOOL_MODE)) >= 0
- && lseek(journal_fd, 0, SEEK_SET) == 0
- && (jread = fdopen(journal_fd, "rb"))
- )
+ 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)
{
- while (Ufgets(big_buffer, big_buffer_size, jread))
+ 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)
{
- int n = Ustrlen(big_buffer);
- big_buffer[n-1] = 0;
- tree_add_nonrecipient(big_buffer);
- DEBUG(D_deliver) debug_printf("Previously delivered address %s taken from "
- "journal file\n", big_buffer);
- }
+ 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. */
+
+ if (now - received_time.tv_sec > keep_malformed)
+ {
+ Uunlink(spool_fname(US"msglog", message_subdir, id, US""));
+ Uunlink(spool_fname(US"input", message_subdir, id, US"-D"));
+ Uunlink(spool_fname(US"input", message_subdir, id, US"-H"));
+ Uunlink(spool_fname(US"input", message_subdir, id, US"-J"));
+ log_write(0, LOG_MAIN, "Message removed because older than %s",
+ readconf_printtime(keep_malformed));
+ }
+
+ (void)close(deliver_datafile);
+ deliver_datafile = -1;
+ return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */
+ }
+ }
+
+/* The spool header file has been read. Look to see if there is an existing
+journal file for this message. If there is, it means that a previous delivery
+attempt crashed (program or host) before it could update the spool header file.
+Read the list of delivered addresses from the journal and add them to the
+nonrecipients tree. Then update the spool file. We can leave the journal in
+existence, as it will get further successful deliveries added to it in this
+run, and it will be deleted if this function gets to its end successfully.
+Otherwise it might be needed again. */
+
+ {
+ uschar * fname = spool_fname(US"input", message_subdir, id, US"-J");
+ FILE * jread;
+
+ if ( (journal_fd = Uopen(fname,
+ O_RDWR|O_APPEND | EXIM_CLOEXEC | EXIM_NOFOLLOW, SPOOL_MODE)) >= 0
+ && lseek(journal_fd, 0, SEEK_SET) == 0
+ && (jread = fdopen(journal_fd, "rb"))
+ )
+ {
+ while (Ufgets(big_buffer, big_buffer_size, jread))
+ {
+ int n = Ustrlen(big_buffer);
+ big_buffer[n-1] = 0;
+ tree_add_nonrecipient(big_buffer);
+ DEBUG(D_deliver) debug_printf("Previously delivered address %s taken from "
+ "journal file\n", big_buffer);
+ }
rewind(jread);
if ((journal_fd = dup(fileno(jread))) < 0)
journal_fd = fileno(jread);
/* 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;
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)))
+ if (f.queue_2stage)
+ dbm_file = NULL;
+ else 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");
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);
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);
uschar * fname = spool_fname(US"input", message_subdir, id, US"-J");
if ((journal_fd = Uopen(fname,
-#ifdef O_CLOEXEC
- O_CLOEXEC |
-#endif
- O_WRONLY|O_APPEND|O_CREAT|O_EXCL, SPOOL_MODE)) < 0)
+ EXIM_CLOEXEC | O_WRONLY|O_APPEND|O_CREAT|O_EXCL, SPOOL_MODE)) < 0)
{
log_write(0, LOG_MAIN|LOG_PANIC, "Couldn't open journal file %s: %s",
fname, strerror(errno));
if (!regex_IGNOREQUOTA)
regex_IGNOREQUOTA =
- regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE);
+ regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", MCS_NOFLAGS, TRUE);
/* Handle local deliveries */
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,