X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/4191cb150300d310ab5fa22ce2cfb02b6f6051b0..1d19531abc45f656eff8f3dd5ba6986d919fee11:/src/src/deliver.c diff --git a/src/src/deliver.c b/src/src/deliver.c index 8f85626a0..e96733f73 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -2,9 +2,10 @@ * Exim - an Internet mail transport agent * *************************************************/ +/* Copyright (c) The Exim Maintainers 2020 - 2023 */ /* 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. */ @@ -26,7 +27,7 @@ typedef struct pardata { 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 */ @@ -37,8 +38,8 @@ enum { RECIP_ACCEPT, RECIP_IGNORE, RECIP_DEFER, /* 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 */ @@ -67,7 +68,6 @@ static address_item *addr_new = NULL; static address_item *addr_remote = NULL; static address_item *addr_route = NULL; static address_item *addr_succeed = NULL; -static address_item *addr_senddsn = NULL; static FILE *message_log = NULL; static BOOL update_spool; @@ -77,7 +77,7 @@ static pardata *parlist = NULL; 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; @@ -144,7 +144,7 @@ Returns: a pointer to an initialized address_item */ 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; @@ -341,13 +341,7 @@ if (Ustrstr(filename, US"/../")) 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 @@ -581,7 +575,7 @@ Returns: TRUE or FALSE */ 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; @@ -668,7 +662,7 @@ Returns: nothing */ static void -address_done(address_item *addr, uschar *now) +address_done(address_item * addr, const uschar * now) { update_spool = TRUE; /* Ensure spool gets updated */ @@ -725,11 +719,11 @@ Returns: nothing */ 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 */ @@ -904,7 +898,7 @@ void msg_event_raise(const uschar * event, const address_item * addr) { const uschar * save_domain = deliver_domain; -uschar * save_local = deliver_localpart; +const uschar * save_local = deliver_localpart; const uschar * save_host = deliver_host; const uschar * save_address = deliver_host_address; const int save_port = deliver_host_port; @@ -953,9 +947,22 @@ router_name = transport_name = NULL; /************************************************* -* Generate local prt for logging * +* Generate local part for logging * *************************************************/ +static const uschar * +string_get_lpart_sub(const address_item * addr, const uschar * s) +{ +#ifdef SUPPORT_I18N +if (testflag(addr, af_utf8_downcvt)) + { + const uschar * t = string_localpart_utf8_to_alabel(s, NULL); + return t ? t : s; /* t is NULL on a failed conversion */ + } +#endif +return s; +} + /* This function is a subroutine for use in string_log_address() below. Arguments: @@ -968,34 +975,15 @@ Returns: the new value of the buffer pointer static gstring * string_get_localpart(address_item * addr, gstring * yield) { -uschar * s; +const uschar * s; -s = addr->prefix; -if (testflag(addr, af_include_affixes) && s) - { -#ifdef SUPPORT_I18N - if (testflag(addr, af_utf8_downcvt)) - s = string_localpart_utf8_to_alabel(s, NULL); -#endif - yield = string_cat(yield, s); - } +if (testflag(addr, af_include_affixes) && (s = addr->prefix)) + yield = string_cat(yield, string_get_lpart_sub(addr, s)); -s = addr->local_part; -#ifdef SUPPORT_I18N -if (testflag(addr, af_utf8_downcvt)) - s = string_localpart_utf8_to_alabel(s, NULL); -#endif -yield = string_cat(yield, s); +yield = string_cat(yield, string_get_lpart_sub(addr, addr->local_part)); -s = addr->suffix; -if (testflag(addr, af_include_affixes) && s) - { -#ifdef SUPPORT_I18N - if (testflag(addr, af_utf8_downcvt)) - s = string_localpart_utf8_to_alabel(s, NULL); -#endif - yield = string_cat(yield, s); - } +if (testflag(addr, af_include_affixes) && (s = addr->suffix)) + yield = string_cat(yield, string_get_lpart_sub(addr, s)); return yield; } @@ -1056,7 +1044,7 @@ splitting is done; in those cases use the original field. */ 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) { @@ -1290,7 +1278,7 @@ if (LOGGING(deliver_time)) /* 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); @@ -1349,13 +1337,11 @@ if (LOGGING(deliver_time)) 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 @@ -1365,7 +1351,7 @@ others. */ log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags, - "== %s", g->s); + "== %Y", g); store_reset(reset_point); return; @@ -1428,17 +1414,15 @@ if (addr->message) if (LOGGING(deliver_time)) g = string_append(g, 2, US" DT=", string_timediff(&addr->delivery_time)); -(void) string_from_gstring(g); - /* Do the logging. For the message log, "routing failed" for those cases, 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; @@ -1465,12 +1449,12 @@ Returns: nothing */ 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); @@ -2323,7 +2307,7 @@ if ((pid = exim_fork(US"delivery-local")) == 0) if (addr->transport->setup) switch((addr->transport->setup)(addr->transport, addr, NULL, uid, gid, - &(addr->message))) + &addr->message)) { case DEFER: addr->transport_return = DEFER; @@ -2379,7 +2363,9 @@ if ((pid = exim_fork(US"delivery-local")) == 0) 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; @@ -2390,7 +2376,7 @@ if ((pid = exim_fork(US"delivery-local")) == 0) { ok = transport_set_up_command(&transport_filter_argv, tp->filter_command, - TRUE, PANIC, addr, US"transport filter", NULL); + TSUC_EXPAND_ARGS, PANIC, addr, US"transport filter", NULL); transport_filter_timeout = tp->filter_timeout; } else transport_filter_argv = NULL; @@ -3359,8 +3345,8 @@ while (!done) 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 @@ -3475,7 +3461,7 @@ while (!done) /* 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; @@ -3488,7 +3474,7 @@ while (!done) 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) { @@ -3565,14 +3551,14 @@ while (!done) 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: @@ -3616,7 +3602,7 @@ while (!done) 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++; @@ -3765,7 +3751,7 @@ Returns: nothing */ 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 @@ -3780,7 +3766,7 @@ into the special_action field for each successful delivery. */ 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 @@ -4105,7 +4091,7 @@ par_reduce(int max, BOOL fallback) { while (parcount > max) { - address_item *doneaddr = par_wait(); + address_item * doneaddr = par_wait(); if (!doneaddr) { log_write(0, LOG_MAIN|LOG_PANIC, @@ -4301,10 +4287,14 @@ So look out for the place it gets used. } /* 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); /************************************************************************/ @@ -4667,7 +4657,9 @@ all pipes, so I do not see a reason to use non-blocking IO here 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; @@ -4710,17 +4702,13 @@ all pipes, so I do not see a reason to use non-blocking IO here { 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 @@ -5098,7 +5086,7 @@ Returns: OK int deliver_split_address(address_item * addr) { -uschar * address = addr->address; +const uschar * address = addr->address; uschar * domain; uschar * t; int len; @@ -5115,7 +5103,7 @@ where they are locally interpreted. [The new draft "821" is more explicit on this, Jan 1999.] We know the syntax is valid, so this can be done by simply removing quoting backslashes and any unquoted doublequotes. */ -t = addr->cc_local_part = store_get(len+1, address); +addr->cc_local_part = t = store_get(len+1, address); while(len-- > 0) { int c = *address++; @@ -5127,7 +5115,7 @@ while(len-- > 0) } 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. */ @@ -5135,8 +5123,8 @@ 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 */ @@ -5273,12 +5261,12 @@ Returns: TRUE if the address is not hidden */ 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); @@ -5293,8 +5281,8 @@ else if (!testflag(addr, af_pfr) || !addr->parent) 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"; @@ -5308,7 +5296,7 @@ fprintf(f, "%s", CS string_printing(printed)); 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, @@ -5378,6 +5366,11 @@ while (*s) 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')); + } } } @@ -5408,19 +5401,18 @@ uschar * s = testflag(addr, af_pass_message) ? addr->message : NULL; unsigned cnt; /* af_pass_message and addr->message set ? print remote host answer */ -if (s) - { - 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) { @@ -5559,240 +5551,1031 @@ else if (!(fp = Ufopen(s, "rb"))) 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; + 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)) - { - 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); + address_item * nextaddr; + + if (emf_text) + fprintf(fp, "%s", CS emf_text); else - (void) fclose(jread); /* Try to not leak the FILE resource */ + fprintf(fp, + "The following text was generated during the delivery " + "attempt%s:\n", (filecount > 1)? "s" : ""); - /* Panic-dies on error */ - (void)spool_write_header(message_id, SW_DELIVERING, NULL); - } - else if (errno != ENOENT) - { - log_write(0, LOG_MAIN|LOG_PANIC, "attempt to open journal for reading gave: " - "%s", strerror(errno)); - return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */ - } + for (address_item * addr = msgchain; addr; addr = nextaddr) + { + FILE *fm; + address_item *topaddr = addr; - /* A null recipients list indicates some kind of disaster. */ + /* List all the addresses that relate to this file */ - if (!recipients_list) + 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; + +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; +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; + +#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); + else + (void) fclose(jread); /* Try to not leak the FILE resource */ + + /* Panic-dies on error */ + (void)spool_write_header(message_id, SW_DELIVERING, NULL); + } + else if (errno != ENOENT) + { + log_write(0, LOG_MAIN|LOG_PANIC, "attempt to open journal for reading gave: " + "%s", strerror(errno)); + return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */ + } + + /* A null recipients list indicates some kind of disaster. */ + + if (!recipients_list) { (void)close(deliver_datafile); deliver_datafile = -1; @@ -6190,7 +6973,7 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT) 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 */ @@ -6230,8 +7013,9 @@ if (process_recipients != RECIP_IGNORE) for (i = 0; i < recipients_count; i++) if (!tree_search(tree_nonrecipients, recipients_list[i].address)) { - recipient_item *r = recipients_list + i; - address_item *new = deliver_make_addr(r->address, FALSE); + 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)) @@ -6291,6 +7075,8 @@ if (process_recipients != RECIP_IGNORE) 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 @@ -6299,7 +7085,7 @@ if (process_recipients != RECIP_IGNORE) The incident has already been logged. */ RECIP_QUEUE_FAILED: - if (sender_address[0]) + if (*sender_address) { new->next = addr_failed; addr_failed = new; @@ -6328,9 +7114,10 @@ if (process_recipients != RECIP_IGNORE) #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)) @@ -6407,7 +7194,7 @@ deliver_out_buffer = store_malloc(DELIVER_OUT_BUFFER_SIZE); 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. */ @@ -7159,10 +7946,7 @@ if (addr_local || addr_remote) 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)); @@ -7209,7 +7993,7 @@ local and remote LMTP deliveries. */ 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 */ @@ -7327,188 +8111,40 @@ if (mua_wrapper) if (!s) s = addr_failed->message; - fprintf(stderr, "Delivery failed: "); - if (addr_failed->basic_errno > 0) - { - fprintf(stderr, "%s", strerror(addr_failed->basic_errno)); - if (s) fprintf(stderr, ": "); - } - if ((host = addr_failed->host_used)) - fprintf(stderr, "H=%s [%s]: ", host->name, host->address); - if (s) - fprintf(stderr, "%s", CS s); - else if (addr_failed->basic_errno <= 0) - fprintf(stderr, "unknown error"); - fprintf(stderr, "\n"); - - final_yield = DELIVER_MUA_FAILED; - addr_failed = NULL; - } - } - -/* In a normal configuration, we now update the retry database. This is done in -one fell swoop at the end in order not to keep opening and closing (and -locking) the database. The code for handling retries is hived off into a -separate module for convenience. We pass it the addresses of the various -chains, because deferred addresses can get moved onto the failed chain if the -retry cutoff time has expired for all alternative destinations. Bypass the -updating of the database if the -N flag is set, which is a debugging thing that -prevents actual delivery. */ - -else if (!f.dont_deliver) - retry_update(&addr_defer, &addr_failed, &addr_succeed); - -/* Send DSN for successful messages if requested */ -addr_senddsn = NULL; - -for (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); + fprintf(stderr, "Delivery failed: "); + if (addr_failed->basic_errno > 0) + { + fprintf(stderr, "%s", strerror(addr_failed->basic_errno)); + if (s) fprintf(stderr, ": "); + } + if ((host = addr_failed->host_used)) + fprintf(stderr, "H=%s [%s]: ", host->name, host->address); + if (s) + fprintf(stderr, "%s", CS s); + else if (addr_failed->basic_errno <= 0) + fprintf(stderr, "unknown error"); + fprintf(stderr, "\n"); - 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 @@ -7517,14 +8153,8 @@ requirements. */ while (addr_failed) { - pid_t pid; - int fd; - uschar *logtod = tod_stamp(tod_log); - address_item *addr; - address_item *handled_addr = NULL; - address_item **paddr; - address_item *msgchain = NULL; - address_item **pmsgchain = &msgchain; + 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). */ @@ -7594,439 +8224,10 @@ while (addr_failed) /* 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 */ @@ -8125,7 +8326,7 @@ was set just to keep the message on the spool, so there is nothing to do here. 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) @@ -8133,7 +8334,7 @@ else if (addr_defer != (address_item *)(+1)) 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; @@ -8165,7 +8366,7 @@ else if (addr_defer != (address_item *)(+1)) 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; } @@ -8193,7 +8394,7 @@ else if (addr_defer != (address_item *)(+1)) 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, @@ -8255,181 +8456,11 @@ else if (addr_defer != (address_item *)(+1)) have been. */ if (warning_count < count) - { - 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 "); - - else - fprintf(f, -"A message sent by\n\n <%s>\n\n" -"has not yet been delivered to one or more of its recipients after more than \n", - sender_address); - - fprintf(f, "%s on the queue on %s.\n\n" - "The message identifier is: %s\n", - warnmsg_delay, primary_hostname, message_id); - - for (h = header_list; h; h = h->next) - if (strncmpic(h->text, US"Subject:", 8) == 0) - fprintf(f, "The subject of the message is: %s", h->text + 9); - else if (strncmpic(h->text, US"Date:", 5) == 0) - fprintf(f, "The date of the message is: %s", h->text + 6); - fputc('\n', f); - - fprintf(f, "The address%s to which the message has not yet been " - "delivered %s:\n", - !addr_defer->next ? "" : "es", - !addr_defer->next ? "is": "are"); - } - - /* List the addresses, with error information if allowed */ - - 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_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 */ - } - } - } + if (send_warning_message(recipients, queue_time, show_time)) + { + warning_count = count; + update_spool = TRUE; /* Ensure spool rewritten */ + } } /* Clear deliver_domain */ @@ -8454,27 +8485,23 @@ else if (addr_defer != (address_item *)(+1)) 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); @@ -8609,7 +8636,7 @@ if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only) if (pid == 0) /* child: will fork again to totally disconnect */ { smtp_proxy_tls(cutthrough.cctx.tls_ctx, big_buffer, big_buffer_size, - pfd, 5*60); + pfd, 5*60, cutthrough.host.name); /* does not return */ }