X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/e6841985b1abe753ea39f12665444234344cdc15..90bd3832bc0ff090ac5e37dfc66b30cabb9cfc1a:/src/src/deliver.c diff --git a/src/src/deliver.c b/src/src/deliver.c index c86c4bb4b..70f44535f 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) The Exim Maintainers 2020 - 2022 */ +/* Copyright (c) The Exim Maintainers 2020 - 2023 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ /* SPDX-License-Identifier: GPL-2.0-or-later */ @@ -27,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 */ @@ -68,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; @@ -78,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; @@ -145,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; @@ -576,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; @@ -724,7 +723,7 @@ 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 */ @@ -899,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; @@ -951,13 +950,13 @@ router_name = transport_name = NULL; * 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 @@ -976,7 +975,7 @@ Returns: the new value of the buffer pointer 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)); @@ -1246,6 +1245,14 @@ else 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) @@ -1279,7 +1286,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); @@ -1338,25 +1345,21 @@ if (LOGGING(deliver_time)) if (addr->message) g = string_append(g, 2, US": ", addr->message); - { - const uschar * s = 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. */ - /* 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->ptr, g->s); - if (f.deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE) - deliver_msglog("%s %s\n", now, s); +/* Write the main log and reset the store. +For errors of the type "retry time not reached" (also remotes skipped +on queue run), logging is controlled by L_retry_defer. Note that this kind +of error number is negative, and all the retry ones are less than any +others. */ - /* Write the main log and reset the store. - For errors of the type "retry time not reached" (also remotes skipped - on queue run), logging is controlled by L_retry_defer. Note that this kind - of error number is negative, and all the retry ones are less than any - others. */ - - log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags, - "== %s", s); - } +log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags, + "== %Y", g); store_reset(reset_point); return; @@ -1422,16 +1425,12 @@ if (LOGGING(deliver_time)) /* Do the logging. For the message log, "routing failed" for those cases, just to make it clearer. */ - { - const uschar * s = string_from_gstring(g); - - if (driver_kind) - deliver_msglog("%s %s failed for %s\n", now, driver_kind, s); - else - deliver_msglog("%s %s\n", now, s); +if (driver_kind) + deliver_msglog("%s %s failed for %.*s\n", now, driver_kind, g->ptr, g->s); +else + deliver_msglog("%s %.*s\n", now, g->ptr, g->s); - log_write(0, LOG_MAIN, "** %s", s); - } +log_write(0, LOG_MAIN, "** %Y", g); store_reset(reset_point); return; @@ -1859,8 +1858,9 @@ if (tp->gid_set) } 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; @@ -1886,6 +1886,7 @@ it does not provide a passwd value from which a gid can be taken. */ 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))) { @@ -1989,6 +1990,7 @@ check_message_size(transport_instance *tp, address_item *addr) 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); @@ -2151,6 +2153,7 @@ if(addr->prop.errors_address) else return_path = sender_address; +GET_OPTION("return_path"); if (tp->return_path) { uschar * new_return_path = expand_string(tp->return_path); @@ -2180,6 +2183,7 @@ if (!findugid(addr, tp, &uid, &gid, &use_initgroups)) return; 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 */ @@ -2209,6 +2213,7 @@ all users have access. It is necessary to be in a visible directory for some operating systems when running pipes, as some commands (e.g. "rm" under Solaris 2.5) require this. */ +GET_OPTION("current_directory"); working_directory = tp->current_dir ? tp->current_dir : addr->current_dir; if (working_directory) { @@ -2372,7 +2377,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; @@ -2383,7 +2390,7 @@ if ((pid = exim_fork(US"delivery-local")) == 0) { 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; @@ -2604,36 +2611,40 @@ if ((status & 0xffff) != 0) /* 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; + } } } @@ -2649,6 +2660,7 @@ tpt_parallel_check(transport_instance * tp, address_item * addr, uschar ** key) { unsigned max_parallel; +GET_OPTION("max_parallel"); if (!tp->max_parallel) return FALSE; max_parallel = (unsigned) expand_string_integer(tp->max_parallel, TRUE); @@ -2772,6 +2784,7 @@ while (addr_local) /* 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); @@ -2826,11 +2839,12 @@ while (addr_local) 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) @@ -3352,8 +3366,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 @@ -3468,7 +3482,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; @@ -3558,7 +3572,7 @@ 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); @@ -3578,7 +3592,14 @@ while (!done) 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); @@ -4446,9 +4467,10 @@ nonmatch domains 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) @@ -4664,7 +4686,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; @@ -4891,6 +4915,15 @@ all pipes, so I do not see a reason to use non-blocking IO here 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", @@ -5091,7 +5124,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; @@ -5108,7 +5141,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++; @@ -5120,7 +5153,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. */ @@ -5128,8 +5161,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 */ @@ -5266,12 +5299,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); @@ -5286,8 +5319,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"; @@ -5301,7 +5334,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, @@ -5371,6 +5404,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')); + } } } @@ -5401,19 +5439,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) { @@ -5534,24 +5571,45 @@ return actual_time; 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; } + +/* 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. */ + +static void +dsn_put_wrapped(FILE * fp, const uschar * header, const uschar * s) +{ +gstring * g = string_cat(NULL, header); + +g = string_cat(g, s); +gstring_release_unused(g); +fprintf(fp, "%s\n", wrap_header(string_from_gstring(g), 79, 1023, US" ", 1)); +} + + + + /************************************************* * Send a bounce message * *************************************************/ @@ -5655,6 +5713,7 @@ else /* Open a template file if one is provided. Log failure to open, but carry on - default texts will be used. */ + GET_OPTION("bounce_message_file"); if (bounce_message_file) emf = expand_open(bounce_message_file, US"bounce_message_file", US"error"); @@ -5723,7 +5782,7 @@ wording. */ if (addr->return_file >= 0) { - paddr = &(addr->next); + paddr = &addr->next; filecount++; } @@ -5819,7 +5878,7 @@ wording. */ if (dsn_envid) { /* must be decoded from xtext: see RFC 3461:6.3a */ - uschar *xdec_envid; + uschar * xdec_envid; if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0) fprintf(fp, "Original-Envelope-ID: %s\n", dsn_envid); else @@ -5830,6 +5889,9 @@ wording. */ 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"); @@ -5837,8 +5899,6 @@ wording. */ { fprintf(fp, "Remote-MTA: dns; %s\n", hu->name); #ifdef EXPERIMENTAL_DSN_INFO - { - const uschar * s; if (hu->address) { uschar * p = hu->port == 25 @@ -5846,15 +5906,18 @@ wording. */ 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); + dsn_put_wrapped(fp, US"X-Remote-MTA-smtp-greeting: X-str; ", 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); - } + 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); } @@ -5930,7 +5993,7 @@ wording. */ tctx.u.fd = fileno(fp); tctx.tblock = &tb; - tctx.options = topt; + tctx.options = topt | topt_truncate_headers; tb.add_headers = dsnnotifyhdr; /*XXX no checking for failure! buggy! */ @@ -5961,7 +6024,7 @@ wording. */ if (rc != 0) { - uschar *s = US""; + uschar * s = US""; if (now - received_time.tv_sec < retry_maximum_timeout && !addr_defer) { addr_defer = (address_item *)(+1); @@ -6010,6 +6073,7 @@ 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"); @@ -6148,7 +6212,7 @@ fprintf(f, "--%s\n" 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; +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 */ @@ -6168,6 +6232,162 @@ If there's an error, don't update the count. */ 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 * *************************************************/ @@ -6208,7 +6428,7 @@ Returns: When the global variable mua_wrapper is FALSE: */ int -deliver_message(uschar *id, BOOL forced, BOOL give_up) +deliver_message(const uschar * id, BOOL forced, BOOL give_up) { int i, rc; int final_yield = DELIVER_ATTEMPTED_NORMAL; @@ -6294,7 +6514,7 @@ 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, +/* 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 @@ -6327,8 +6547,8 @@ give up; if the message has been around for sufficiently long, remove it. */ if (rc != spool_read_hdrerror) { received_time.tv_sec = received_time.tv_usec = 0; - /*XXX subsec precision?*/ - for (i = 0; i < 6; i++) + /*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']; } @@ -6553,6 +6773,7 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT) /* 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; @@ -6732,12 +6953,14 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT) 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 @@ -6745,11 +6968,13 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT) 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; @@ -6793,7 +7018,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 */ @@ -6833,8 +7058,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)) @@ -6894,6 +7120,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 @@ -6902,7 +7130,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; @@ -6931,9 +7159,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)) @@ -7959,156 +8188,8 @@ 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 */ - - /* Write the original email out */ - - 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); - - fprintf(f,"\n--%s--\n", bound); - - 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 @@ -8330,7 +8411,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; } @@ -8358,7 +8439,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, @@ -8377,54 +8458,57 @@ else if (addr_defer != (address_item *)(+1)) || 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 */ + GET_OPTION("delay_warning_condition"); + if ( ( !delay_warning_condition + || expand_check_condition(delay_warning_condition, + US"delay_warning", US"option") + ) + ) + { + int count; + int show_time; + int queue_time = time(NULL) - received_time.tv_sec; - for (count = 0; count < delay_warning[1]; count++) - if (queue_time < delay_warning[count+2]) break; + queue_time = test_harness_fudged_queue_time(queue_time); - show_time = delay_warning[count+1]; + /* See how many warnings we should have sent by now */ - 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; - } + for (count = 0; count < delay_warning[1]; count++) + if (queue_time < delay_warning[count+2]) break; - 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); - } + show_time = delay_warning[count+1]; - /* 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 (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; + } - if (warning_count < count) - if (send_warning_message(recipients, queue_time, show_time)) + DEBUG(D_deliver) { - warning_count = count; - update_spool = TRUE; /* Ensure spool rewritten */ + 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) + if (send_warning_message(recipients, queue_time, show_time)) + { + warning_count = count; + update_spool = TRUE; /* Ensure spool rewritten */ + } + } } /* Clear deliver_domain */ @@ -8449,27 +8533,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);