diff -urN ../exim-4.96.orig/src/acl.c ./src/acl.c --- ../exim-4.96.orig/src/acl.c 2022-06-23 16:41:10.000000000 +0300 +++ ./src/acl.c 2023-08-31 21:15:11.915256000 +0300 @@ -3398,7 +3398,7 @@ case CONTROL_FAKEREJECT: cancel_cutthrough_connection(TRUE, US"fakereject"); case CONTROL_FAKEDEFER: - fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL; + fake_response = control_type == CONTROL_FAKEDEFER ? DEFER : FAIL; if (*p == '/') { const uschar *pp = p + 1; diff -urN ../exim-4.96.orig/src/deliver.c ./src/deliver.c --- ../exim-4.96.orig/src/deliver.c 2022-06-23 16:41:10.000000000 +0300 +++ ./src/deliver.c 2023-08-31 22:00:41.099658000 +0300 @@ -37,8 +37,8 @@ /* 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 +67,6 @@ 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; @@ -668,7 +667,7 @@ */ 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,7 +724,7 @@ */ static void -child_done(address_item *addr, uschar *now) +child_done(address_item * addr, const uschar * now) { while (addr->parent) { @@ -1459,12 +1458,12 @@ */ 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); @@ -2317,7 +2316,7 @@ if (addr->transport->setup) switch((addr->transport->setup)(addr->transport, addr, NULL, uid, gid, - &(addr->message))) + &addr->message)) { case DEFER: addr->transport_return = DEFER; @@ -3482,7 +3481,7 @@ 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) { @@ -3566,7 +3565,7 @@ 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: @@ -3610,7 +3609,7 @@ 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++; @@ -3759,7 +3758,7 @@ */ 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 @@ -3774,7 +3773,7 @@ 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 @@ -4099,7 +4098,7 @@ { while (parcount > max) { - address_item *doneaddr = par_wait(); + address_item * doneaddr = par_wait(); if (!doneaddr) { log_write(0, LOG_MAIN|LOG_PANIC, @@ -5554,6 +5553,603 @@ } /************************************************* +* Send a bounce 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. */ + +static void +send_bounce_message(time_t now, const uschar * logtod) +{ +pid_t pid; +int fd; + +if (!(bounce_recipient = addr_failed->prop.errors_address)) + bounce_recipient = sender_address; + +/* Make a subprocess to send a message, using its stdin */ + +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, 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; + + 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 */ + + /* 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 (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"); + + /* 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 (address_item * addr = msgchain; addr; addr = *paddr) + { + if (print_address_information(addr, fp, US" ", US"\n ", US"")) + print_address_error(addr, fp, US""); + + /* End the final line for the address */ + + fputc('\n', fp); + + /* Leave on msgchain if there's a return file. */ + + if (addr->return_file >= 0) + { + paddr = &(addr->next); + filecount++; + } + + /* Else save so that we can tick off the recipient when the + message is sent. */ + + else + { + *paddr = addr->next; + addr->next = handled_addr; + handled_addr = addr; + } + } + + fputc('\n', fp); + + /* Get the next text, whether we need it or not, so as to be + positioned for the one after. */ + + emf_text = next_emf(emf, US"generated text"); + + /* If there were any file messages passed by the local transports, + include them in the message. Then put the address on the handled chain. + In the case of a batch of addresses that were all sent to the same + transport, the return_file field in all of them will contain the same + fd, and the return_filename field in the *last* one will be set (to the + name of the file). */ + + if (msgchain) + { + address_item * nextaddr; + + if (emf_text) + fprintf(fp, "%s", CS emf_text); + else + fprintf(fp, + "The following text was generated during the delivery " + "attempt%s:\n", (filecount > 1)? "s" : ""); + + for (address_item * addr = msgchain; addr; addr = nextaddr) + { + FILE *fm; + address_item *topaddr = addr; + + /* List all the addresses that relate to this file */ + + fputc('\n', fp); + while(addr) /* Insurance */ + { + print_address_information(addr, fp, US"------ ", US"\n ", + US" ------\n"); + if (addr->return_filename) break; + addr = addr->next; + } + fputc('\n', fp); + + /* Now copy the file */ + + if (!(fm = Ufopen(addr->return_filename, "rb"))) + fprintf(fp, " +++ Exim error... failed to open text file: %s\n", + strerror(errno)); + else + { + while ((ch = fgetc(fm)) != EOF) fputc(ch, fp); + (void)fclose(fm); + } + Uunlink(addr->return_filename); + + /* Can now add to handled chain, first fishing off the next + address on the msgchain. */ + + nextaddr = addr->next; + addr->next = handled_addr; + handled_addr = topaddr; + } + fputc('\n', fp); + } + + /* output machine readable part */ +#ifdef SUPPORT_I18N + if (message_smtputf8) + fprintf(fp, "--%s\n" + "Content-type: message/global-delivery-status\n\n" + "Reporting-MTA: dns; %s\n", + bound, smtp_active_hostname); + else +#endif + fprintf(fp, "--%s\n" + "Content-type: message/delivery-status\n\n" + "Reporting-MTA: dns; %s\n", + bound, smtp_active_hostname); + + if (dsn_envid) + { + /* must be decoded from xtext: see RFC 3461:6.3a */ + uschar *xdec_envid; + if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0) + fprintf(fp, "Original-Envelope-ID: %s\n", dsn_envid); + else + fprintf(fp, "X-Original-Envelope-ID: error decoding xtext formatted ENVID\n"); + } + fputc('\n', fp); + + for (address_item * addr = handled_addr; addr; addr = addr->next) + { + host_item * hu; + + 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 (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 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_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 * *************************************************/ @@ -6401,7 +6997,7 @@ 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. */ @@ -7353,157 +7949,9 @@ 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 - ); +maybe_send_dsn(addr_succeed); - /* 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 */ - } - } - /* If any addresses failed, we must send a message to somebody, unless af_ignore_error is set, in which case no action is taken. It is possible for several messages to get sent if there are addresses with different @@ -7511,14 +7959,8 @@ 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). */ @@ -7588,439 +8030,10 @@ /* 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 */ @@ -8448,27 +8461,23 @@ 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); diff -urN ../exim-4.96.orig/src/receive.c ./src/receive.c --- ../exim-4.96.orig/src/receive.c 2022-06-23 16:41:10.000000000 +0300 +++ ./src/receive.c 2023-08-31 21:15:35.409041000 +0300 @@ -513,7 +513,7 @@ */ void -receive_add_recipient(uschar *recipient, int pno) +receive_add_recipient(uschar * recipient, int pno) { if (recipients_count >= recipients_list_max) { @@ -2657,7 +2657,7 @@ that this has happened, in order to give a better error if there are no recipients left. */ - else if (recipient != NULL) + else if (recipient) { if (tree_search(tree_nonrecipients, recipient) == NULL) receive_add_recipient(recipient, -1); @@ -2667,7 +2667,7 @@ /* Move on past this address */ - s = ss + (*ss? 1:0); + s = ss + (*ss ? 1 : 0); while (isspace(*s)) s++; } /* Next address */ @@ -3854,10 +3854,10 @@ if (rc == LOCAL_SCAN_ACCEPT) { if (local_scan_data) - for (uschar * s = local_scan_data; *s != 0; s++) if (*s == '\n') *s = ' '; - for (int i = 0; i < recipients_count; i++) + for (uschar * s = local_scan_data; *s; s++) if (*s == '\n') *s = ' '; + for (recipient_item * r = recipients_list; + r < recipients_list + recipients_count; r++) { - recipient_item *r = recipients_list + i; r->address = rewrite_address_qualify(r->address, TRUE); if (r->errors_to) r->errors_to = rewrite_address_qualify(r->errors_to, TRUE); @@ -3940,6 +3940,19 @@ signal(SIGTERM, SIG_IGN); signal(SIGINT, SIG_IGN); #endif /* HAVE_LOCAL_SCAN */ + +/* If we are faking a reject or defer, avoid sennding a DSN for the +actually-accepted message */ + +if (fake_response != OK) + for (recipient_item * r = recipients_list; + r < recipients_list + recipients_count; r++) + { + DEBUG(D_receive) if (r->dsn_flags & (rf_notify_success | rf_notify_delay)) + debug_printf("DSN: clearing flags due to fake-response for message\n"); + r->dsn_flags = r->dsn_flags & ~(rf_notify_success | rf_notify_delay) + | rf_notify_never; + } /* Ensure the first time flag is set in the newly-received message. */