From 6c1c3d1dbe1a62ffd24ad9b3cd9efdfe275c74c5 Mon Sep 17 00:00:00 2001 From: Wolfgang Breyha Date: Wed, 21 May 2014 16:21:46 +0100 Subject: [PATCH] RFC3461 support - MIME DSN messages. Bug 118 --- doc/doc-txt/experimental-spec.txt | 58 +++++ src/README.DSN | 141 +++++++++++ src/exim_monitor/em_globals.c | 5 + src/src/EDITME | 2 + src/src/config.h.defaults | 1 + src/src/deliver.c | 373 +++++++++++++++++++++++++++++- src/src/exim.c | 13 ++ src/src/globals.c | 15 ++ src/src/globals.h | 7 + src/src/local_scan.h | 4 + src/src/macros.h | 23 ++ src/src/readconf.c | 3 + src/src/receive.c | 4 + src/src/route.c | 28 +++ src/src/smtp_in.c | 171 ++++++++++++++ src/src/spool_in.c | 71 +++++- src/src/spool_out.c | 30 ++- src/src/structs.h | 9 + src/src/tls.c | 2 +- src/src/transport.c | 5 + src/src/transports/smtp.c | 100 ++++++++ 21 files changed, 1060 insertions(+), 5 deletions(-) create mode 100644 src/README.DSN diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt index 588543454..031c5f4c1 100644 --- a/doc/doc-txt/experimental-spec.txt +++ b/doc/doc-txt/experimental-spec.txt @@ -1146,6 +1146,64 @@ QUIT 221 mail.example.net closing connection +DSN Support +-------------------------------------------------------------- + +DSN Support tries to add RFC 3461 support to Exim. It adds support for +*) the additional parameters for MAIL FROM and RCPT TO +*) RFC complient MIME DSN messages for all of + success, failure and delay notifications +*) dsn_advertise_hosts main option to select which hosts are able + to use the extension +*) dsn_lasthop router switch to end DSN processing + +In case of failure reports this means that the last three parts, the message body +intro, size info and final text, of the defined template are ignored since there is no +logical place to put them in the MIME message. + +All the other changes are made without changing any defaults + +Building exim: +-------------- + +Define +EXPERIMENTAL_DSN=YES +in your Local/Makefile. + +Configuration: +-------------- +All DSNs are sent in MIME format if you built exim with EXPERIMENTAL_DSN=YES +No option needed to activate it, and no way to turn it off. + +Failure and delay DSNs are triggered as usual except a sender used NOTIFY=... +to prevent them. + +Support for Success DSNs is added and activated by NOTIFY=SUCCESS by clients. + +Add +dsn_advertise_hosts = * +or a more restrictive host_list to announce DSN in EHLO answers + +Those hosts can then use NOTIFY,ENVID,RET,ORCPT options. + +If a message is relayed to a DSN aware host without changing the envelope +recipient the options are passed along and no success DSN is generated. + +A redirect router will always trigger a success DSN if requested and the DSN +options are not passed any further. + +A success DSN always contains the recipient address as submitted by the +client as required by RFC. Rewritten addresses are never exposed. + +If you used DSN patch up to 1.3 before remove all "dsn_process" switches from +your routers since you don't need them anymore. There is no way to "gag" +success DSNs anymore. Announcing DSN means answering as requested. + +You can prevent Exim from passing DSN options along to other DSN aware hosts by defining +dsn_lasthop +in a router. Exim will then send the success DSN himself if requested as if +the next hop does not support DSN. +Adding it to a redirect router makes no difference. Certificate name checking -------------------------------------------------------------- diff --git a/src/README.DSN b/src/README.DSN new file mode 100644 index 000000000..68d16415c --- /dev/null +++ b/src/README.DSN @@ -0,0 +1,141 @@ +Exim DSN Patch (4.82) +--------------------- + +This patch is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This patch is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this patch; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. + +Installation & Usage +-------------------- +See docs/experimental-spec.txt + +Credits +------- + +The original work for the patch was done by Philip Hazel in Exim 3 + +The extract was taken and re-applied to Exim 4 by the following :- +Phil Bingham (phil.bingham@cwipapps.net) +Steve Falla (steve.falla@cwipapps.net) +Ray Edah (ray.edah@cwipapps.net) +Andrew Johnson (andrew.johnson@cwippaps.net) +Adrian Hungate (adrian.hungate@cwipapps.net) + +Now Primarily maintained by :- +Andrew Johnson (andrew.johnson@cwippaps.net) + +Updated for 4.82, improved and submitted to +http://bugs.exim.org/show_bug.cgi?id=118 +by :- +Wolfgang Breyha (wbreyha@gmx.net) + +Contributions +------------- +Andrey J. Melnikoff (TEMHOTA) (temnota@kmv.ru) + + +ChangeLog +--------- +14-Apr-2006 : Changed subject to "Delivery Status Notification" + +17-May-2006 : debug_printf in spool-in.c were not wrapped with #ifndef COMPILE_UTILITY + thanks to Andrey J. Melnikoff for this information + +12-Sep-2006 : Now supports Exim 4.63 + +12-Sep-2006 : src/EDITME did not include the #define SUPPORT_DSN as stated + in the documentation, this has now been corrected + thanks to Robert Kehl for this information + +28-Jul-2008 : New version for exim 4.69 released. + +02-Jul-2010 : New version for exim 4.72 released. + +25-Apr-2014 : Version 1.4 + *) fix ENVID and ORCPT addition in SMTP transport + *) p was not moved to the end of the string. new content + added afterwards overwrites ENVID and/or ORCPT + *) change spool file format to be compatible with the + extensible format of exim 4 by prepending new values and + setting the extended bitmask accordingly + *) use SUPPORT_DSN_LEGACY=yes in Makefile to be able to read + the legacy format of older patches until all messages are out of queue. + *) change "dsn" boolean toggle to "dsn_advertise_hosts" to + be able to select who actually can use the extension + *) Add all RFC 3461 MUST fields to delivery-status section + *) convert xtext in ENVID + *) add all successful rcpts to ONE message instead of sending several messages + +26-Apr-2014 : Version 1.5 + fixes: + *) fixed wrong order for ENVID + *) fixed wrong Final-Recipient value + *) af_ignore_failure is ignored for success reports + *) fixed DSN_LEGACY switch + improvements: + *) added MIME "failure" reports + *) bounce_return_message is ignored (required by RFC) + *) in case RET= is defined we honor these values + otherwise bounce_return_body is honored. + *) bounce_return_size_limit is always honored. + *) message body intro and final text is ignored + *) do not send report if DSN flags say NO + *) added MIME "delay" reports + *) do not send report if DSN flags say NO + *) changed from SUPPORT_DSN to EXPERIMENTAL_DSN + *) updated documentation + +01-May-2014 : Version 1.6 + fixes: + *) code cleanup + *) use text/rfc822-headers were applicable + *) fix NOTIFY=FAILURE + + improvements: + *) do not truncated MIME messages + *) if bounce_return_size_limit is smaller then the actual message + only the header is returned + *) if bounce_return_body or bounce_return_size_limit prevents Exim + from returning the requested (RET=FULL) body this fact is added + as X-Exim-DSN-Information Header + *) this also means that all of the last three parts of the "failure" + template are not used anymore + + *) dsn_process switch removed + *) every router "processes" DSN by default + *) there is no possibilty to "gag" DSN anymore since this violates RFC + *) dsn_lasthop switch added for routers + *) if dsn_lasthop is set by a router it is handled as relaying to a + non DSN aware relay. success mails are sent if Exim successfully + delivers the message. + *) redirect routers always "act" as if dsn_lasthop is set + + *) address_item.dsn_aware changed from uschar to int for easier handling. + +02-May-2014 : fixes: + *) Reporting-MTA: use smtp_active_hostname instead of qualify_domain from + original patch. + +20-May-2014 : fixes: + *) removed support for EXPERIMENTAL_DSN_LEGACY for codebase inclusion + *) fixed build of exim_monitor tree + *) fixed late declaration of dsn_all_lasthop + +----------------- + +Support for this patch up to 1.3 (limited though it is) will only be provided through the SourceForge +project page (http://sourceforge.net/projects/eximdsn/) + +From 1.4 onward feel free to ask on the exim-users mailinglist or add comments to +http://bugs.exim.org/show_bug.cgi?id=118 + diff --git a/src/exim_monitor/em_globals.c b/src/exim_monitor/em_globals.c index b0a912e5f..d5205d08f 100644 --- a/src/exim_monitor/em_globals.c +++ b/src/exim_monitor/em_globals.c @@ -145,6 +145,11 @@ BOOL dkim_disable_verify = FALSE; BOOL dont_deliver = FALSE; +#ifdef EXPERIMENTAL_DSN +int dsn_ret = 0; +uschar *dsn_envid = NULL; +#endif + #ifdef WITH_CONTENT_SCAN int fake_response = OK; #endif diff --git a/src/src/EDITME b/src/src/EDITME index 83ca43cd1..0d31ba5c1 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -489,6 +489,8 @@ EXIM_MONITOR=eximon.bin # ownership # EXPERIMENTAL_CERTNAMES=yes +# Uncomment the following line to add DSN support +# EXPERIMENTAL_DSN=yes ############################################################################### # THESE ARE THINGS YOU MIGHT WANT TO SPECIFY # diff --git a/src/src/config.h.defaults b/src/src/config.h.defaults index 3ab73d861..0bb97a231 100644 --- a/src/src/config.h.defaults +++ b/src/src/config.h.defaults @@ -168,6 +168,7 @@ it's a default value. */ #define EXPERIMENTAL_CERTNAMES #define EXPERIMENTAL_DCC #define EXPERIMENTAL_DMARC +#define EXPERIMENTAL_DSN #define EXPERIMENTAL_OCSP #define EXPERIMENTAL_PROXY #define EXPERIMENTAL_REDIS diff --git a/src/src/deliver.c b/src/src/deliver.c index 7af101ac0..68c04877e 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -63,6 +63,10 @@ static address_item *addr_new = NULL; static address_item *addr_remote = NULL; static address_item *addr_route = NULL; static address_item *addr_succeed = NULL; +#ifdef EXPERIMENTAL_DSN +static address_item *addr_dsntmp = NULL; +static address_item *addr_senddsn = NULL; +#endif static FILE *message_log = NULL; static BOOL update_spool; @@ -3049,6 +3053,15 @@ while (!done) break; #endif + #ifdef EXPERIMENTAL_DSN + case 'D': + if (addr == NULL) break; + 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; + #endif + case 'A': if (addr == NULL) { @@ -4192,6 +4205,13 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++) rmt_dlv_checked_write(fd, "P", 1); #endif + #ifdef EXPERIMENTAL_DSN + big_buffer[0] = 'D'; + memcpy(big_buffer+1, &addr->dsn_aware, sizeof(addr->dsn_aware)); + rmt_dlv_checked_write(fd, big_buffer, sizeof(addr->dsn_aware) + 1); + DEBUG(D_deliver) debug_printf("DSN write: addr->dsn_aware = %d\n", addr->dsn_aware); + #endif + /* Retry information: for most success cases this will be null. */ for (r = addr->retries; r != NULL; r = r->next) @@ -5342,6 +5362,14 @@ if (process_recipients != RECIP_IGNORE) if (r->pno >= 0) new->onetime_parent = recipients_list[r->pno].address; + #ifdef EXPERIMENTAL_DSN + /* If DSN support is enabled, set the dsn flags and the original receipt + to be passed on to other DSN enabled MTAs */ + new->dsn_flags = r->dsn_flags & rf_dsnflags; + new->dsn_orcpt = r->orcpt; + DEBUG(D_deliver) debug_printf("DSN: set orcpt: %s flags: %d\n", new->dsn_orcpt, new->dsn_flags); + #endif + switch (process_recipients) { /* RECIP_DEFER is set when a system filter freezes a message. */ @@ -6286,6 +6314,12 @@ if (addr_remote != NULL) regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE); #endif + #ifdef EXPERIMENTAL_DSN + /* Set the regex to check for DSN support on remote MTA */ + if (regex_DSN == NULL) regex_DSN = + regex_must_compile(US"\\n250[\\s\\-]DSN(\\s|\\n|$)", FALSE, TRUE); + #endif + /* Now sort the addresses if required, and do the deliveries. The yield of do_remote_deliveries is FALSE when mua_wrapper is set and all addresses cannot be delivered in one transaction. */ @@ -6390,6 +6424,166 @@ prevents actual delivery. */ else if (!dont_deliver) retry_update(&addr_defer, &addr_failed, &addr_succeed); +#ifdef EXPERIMENTAL_DSN +/* Send DSN for successful messages */ +addr_dsntmp = addr_succeed; +addr_senddsn = NULL; + +while(addr_dsntmp != NULL) + { + DEBUG(D_deliver) + debug_printf("DSN: processing router : %s\n", addr_dsntmp->router->name); + + DEBUG(D_deliver) + debug_printf("DSN: processing successful delivery address: %s\n", addr_dsntmp->address); + + /* af_ignore_error not honored here. it's not an error */ + + DEBUG(D_deliver) debug_printf("DSN: Sender_address: %s\n", sender_address); + DEBUG(D_deliver) debug_printf("DSN: orcpt: %s flags: %d\n", addr_dsntmp->dsn_orcpt, addr_dsntmp->dsn_flags); + DEBUG(D_deliver) debug_printf("DSN: envid: %s ret: %d\n", dsn_envid, dsn_ret); + DEBUG(D_deliver) debug_printf("DSN: Final recipient: %s\n", addr_dsntmp->address); + DEBUG(D_deliver) debug_printf("DSN: Remote SMTP server supports DSN: %d\n", addr_dsntmp->dsn_aware); + + /* send report if next hop not DSN aware or a router flagged "last DSN hop" + and a report was requested */ + if (((addr_dsntmp->dsn_aware != dsn_support_yes) || + ((addr_dsntmp->dsn_flags & rf_dsnlasthop) != 0)) + && + (((addr_dsntmp->dsn_flags & rf_dsnflags) != 0) && + ((addr_dsntmp->dsn_flags & rf_notify_success) != 0))) + { + /* copy and relink address_item and send report with all of them at once later */ + address_item *addr_next; + addr_next = addr_senddsn; + addr_senddsn = store_get(sizeof(address_item)); + memcpy(addr_senddsn, addr_dsntmp, sizeof(address_item)); + addr_senddsn->next = addr_next; + } + else + { + DEBUG(D_deliver) debug_printf("DSN: *** NOT SENDING DSN SUCCESS Message ***\n"); + } + + addr_dsntmp = addr_dsntmp->next; + } + +if (addr_senddsn != NULL) + { + pid_t pid; + int fd; + + /* create exim process to send message */ + pid = child_open_exim(&fd); + + 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 failure 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 */ + int topt = topt_add_return_path | topt_no_body; + uschar boundaryStr[64]; + + DEBUG(D_deliver) debug_printf("sending error message to: %s\n", sender_address); + + /* build unique id for MIME boundary */ + snprintf(boundaryStr, 63, "%d-eximdsn-%d", time(NULL), rand()); + DEBUG(D_deliver) debug_printf("DSN: MIME boundary: %s\n", boundaryStr); + + if (errors_reply_to != NULL) fprintf(f,"Reply-To: %s\n", errors_reply_to); + + fprintf(f,"Auto-Submitted: auto-generated\n"); + fprintf(f,"From: Mail Delivery System \n", qualify_domain_sender); + fprintf(f,"To: %s\n", sender_address); + fprintf(f,"Subject: Delivery Status Notification\n"); + fprintf(f,"Content-Type: multipart/report; report-type=delivery-status; boundary=%s\n", boundaryStr); + fprintf(f,"MIME-Version: 1.0\n\n"); + + fprintf(f,"--%s\n", boundaryStr); + fprintf(f,"Content-type: text/plain; charset=us-ascii\n\n"); + + fprintf(f,"This message was created automatically by mail delivery software.\n"); + fprintf(f," ----- The following addresses had successful delivery notifications -----\n"); + + addr_dsntmp = addr_senddsn; + while(addr_dsntmp != NULL) + { + if ((addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1) { + fprintf(f,"<%s> (relayed via non DSN router)\n\n", addr_dsntmp->address); + } + else if (addr_dsntmp->dsn_aware == dsn_support_no) { + fprintf(f,"<%s> (relayed to non-DSN-aware mailer)\n\n", addr_dsntmp->address); + } + else { + fprintf(f,"<%s> (relayed via non \"Remote SMTP\" router)\n\n", addr_dsntmp->address); + } + addr_dsntmp = addr_dsntmp->next; + } + fprintf(f,"--%s\n", boundaryStr); + fprintf(f,"Content-type: message/delivery-status\n\n"); + + fprintf(f,"Reporting-MTA: dns; %s\n", smtp_active_hostname); + if (dsn_envid != NULL) { + /* 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 formated ENVID\n"); + } + fprintf(f,"\n"); + + addr_dsntmp = addr_senddsn; + while(addr_dsntmp != NULL) + { + if (addr_dsntmp->dsn_orcpt != NULL) { + fprintf(f,"Original-Recipient: %s\n", addr_dsntmp->dsn_orcpt); + } + fprintf(f,"Action: delivered\n"); + fprintf(f,"Final-Recipient: rfc822;%s\n", addr_dsntmp->address); + fprintf(f,"Status: 2.0.0\n"); + if ((addr_dsntmp->host_used != NULL) && (addr_dsntmp->host_used->name != NULL)) + fprintf(f,"Remote-MTA: dns; %s\nDiagnostic-Code: smtp; 250 Ok\n", addr_dsntmp->host_used->name); + else + if ((addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1) + fprintf(f,"Diagnostic-Code: X-Exim; relayed via non DSN router\n"); + else + fprintf(f,"Diagnostic-Code: X-Exim; relayed via non SMTP router\n"); + fprintf(f,"\n"); + addr_dsntmp = addr_dsntmp->next; + } + + fprintf(f,"--%s\n", boundaryStr); + fprintf(f,"Content-type: text/rfc822-headers\n\n"); + + fflush(f); + transport_filter_argv = NULL; /* Just in case */ + return_path = sender_address; /* In case not previously set */ + + /* Write the original email out */ + transport_write_message(NULL, fileno(f), topt, 0, NULL, NULL, NULL, NULL, NULL, 0); + fflush(f); + + fprintf(f,"\n"); + fprintf(f,"--%s--\n", boundaryStr); + + fflush(f); + fclose(f); + rc = child_close(pid, 0); /* Waits for child to close, no timeout */ + } + } +#endif + /* 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 @@ -6447,8 +6641,13 @@ while (addr_failed != NULL) it from the list, throw away any saved message file, log it, and mark the recipient done. */ - if (testflag(addr_failed, af_ignore_error)) - { + if (testflag(addr_failed, af_ignore_error) +#ifdef EXPERIMENTAL_DSN + || (((addr_failed->dsn_flags & rf_dsnflags) != 0) + && ((addr_failed->dsn_flags & rf_notify_failure) != rf_notify_failure)) +#endif + ) + { addr = addr_failed; addr_failed = addr->next; if (addr->return_filename != NULL) Uunlink(addr->return_filename); @@ -6553,6 +6752,14 @@ while (addr_failed != NULL) moan_write_from(f); fprintf(f, "To: %s\n", bounce_recipient); +#ifdef EXPERIMENTAL_DSN + /* generate boundary string and output MIME-Headers */ + uschar boundaryStr[64]; + snprintf(boundaryStr, 63, "%d-eximdsn-%d", time(NULL), rand()); + fprintf(f,"Content-Type: multipart/report; report-type=delivery-status; boundary=%s\n", boundaryStr); + fprintf(f,"MIME-Version: 1.0\n"); +#endif + /* Open a template file if one is provided. Log failure to open, but carry on - default texts will be used. */ @@ -6580,6 +6787,12 @@ while (addr_failed != NULL) to_sender? ": returning message to sender" : ""); } +#ifdef EXPERIMENTAL_DSN + /* output human readable part as text/plain section */ + fprintf(f,"--%s\n", boundaryStr); + fprintf(f,"Content-type: text/plain; charset=us-ascii\n\n"); +#endif + emf_text = next_emf(emf, US"intro"); if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else { @@ -6704,6 +6917,32 @@ wording. */ fprintf(f, "\n"); } +#ifdef EXPERIMENTAL_DSN + /* output machine readable part */ + fprintf(f,"--%s\n", boundaryStr); + fprintf(f,"Content-type: message/delivery-status\n\n"); + + fprintf(f,"Reporting-MTA: dns; %s\n", smtp_active_hostname); + if (dsn_envid != NULL) { + /* 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 formated ENVID\n"); + } + fprintf(f,"\n"); + + for (addr = handled_addr; addr != NULL; addr = addr->next) + { + fprintf(f,"Action: failed\n"); + fprintf(f,"Final-Recipient: rfc822;%s\n", addr->address); + fprintf(f,"Status: 5.0.0\n"); + if ((addr->host_used != NULL) && (addr->host_used->name != NULL)) + fprintf(f,"Remote-MTA: dns; %s\nDiagnostic-Code: smtp; %d\n", addr->host_used->name, addr->basic_errno); + } +#endif + /* 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 @@ -6711,6 +6950,7 @@ wording. */ emf_text = next_emf(emf, US"copy"); +#ifndef EXPERIMENTAL_DSN if (bounce_return_message) { int topt = topt_add_return_path; @@ -6765,6 +7005,65 @@ wording. */ if (emf_text != NULL) fprintf(f, "%s", CS emf_text); (void)fclose(emf); } +#else + /* 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(f,"\n--%s\n", boundaryStr); + + uschar *dsnlimitmsg = US"X-Exim-DSN-Information: Due to administrative limits only headers are returned"; + uschar *dsnnotifyhdr = NULL; + int topt = topt_add_return_path; + /* RET=HDRS? top priority */ + if (dsn_ret == dsn_ret_hdrs) + topt |= topt_no_body; + else + /* 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; + } + /* size limited ... return headers only if limit reached */ + else if (bounce_return_size_limit > 0) + { + struct stat statbuf; + if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max) + { + topt |= topt_no_body; + dsnnotifyhdr = dsnlimitmsg; + } + } + + if (topt & topt_no_body) + fprintf(f,"Content-type: text/rfc822-headers\n\n"); + else + fprintf(f,"Content-type: message/rfc822\n\n"); + + fflush(f); + transport_filter_argv = NULL; /* Just in case */ + return_path = sender_address; /* In case not previously set */ + transport_write_message(NULL, fileno(f), topt, + 0, dsnnotifyhdr, NULL, NULL, NULL, NULL, 0); + fflush(f); + + /* we never add the final text. close the file */ + if (emf != NULL) + (void)fclose(emf); + + fprintf(f,"\n"); + fprintf(f,"--%s--\n", boundaryStr); +#endif /* Close the file, which should send an EOF to the child process that is receiving the message. Wait for it to finish. */ @@ -6996,6 +7295,10 @@ else if (addr_defer != (address_item *)(+1)) it also defers). */ if (!queue_2stage && delivery_attempted && +#ifdef EXPERIMENTAL_DSN + (((addr_defer->dsn_flags & rf_dsnflags) == 0) || + (addr_defer->dsn_flags & rf_notify_delay) == rf_notify_delay) && +#endif delay_warning[1] > 0 && sender_address[0] != 0 && (delay_warning_condition == NULL || expand_check_condition(delay_warning_condition, @@ -7080,6 +7383,14 @@ else if (addr_defer != (address_item *)(+1)) moan_write_from(f); fprintf(f, "To: %s\n", recipients); +#ifdef EXPERIMENTAL_DSN + /* generated boundary string and output MIME-Headers */ + uschar boundaryStr[64]; + snprintf(boundaryStr, 63, "%d-eximdsn-%d", time(NULL), rand()); + fprintf(f,"Content-Type: multipart/report; report-type=delivery-status; boundary=%s\n", boundaryStr); + fprintf(f,"MIME-Version: 1.0\n"); +#endif + wmf_text = next_emf(wmf, US"header"); if (wmf_text != NULL) fprintf(f, "%s\n", wmf_text); @@ -7087,6 +7398,12 @@ else if (addr_defer != (address_item *)(+1)) fprintf(f, "Subject: Warning: message %s delayed %s\n\n", message_id, warnmsg_delay); +#ifdef EXPERIMENTAL_DSN + /* output human readable part as text/plain section */ + fprintf(f,"--%s\n", boundaryStr); + fprintf(f,"Content-type: text/plain; charset=us-ascii\n\n"); +#endif + wmf_text = next_emf(wmf, US"intro"); if (wmf_text != NULL) fprintf(f, "%s", CS wmf_text); else { @@ -7124,6 +7441,10 @@ else if (addr_defer != (address_item *)(+1)) /* List the addresses, with error information if allowed */ +#ifdef EXPERIMENTAL_DSN + /* store addr_defer for machine readable part */ + address_item *addr_dsndefer = addr_defer; +#endif fprintf(f, "\n"); while (addr_defer != NULL) { @@ -7152,6 +7473,54 @@ else if (addr_defer != (address_item *)(+1)) "and when that happens, the message will be returned to you.\n"); } +#ifdef EXPERIMENTAL_DSN + /* output machine readable part */ + fprintf(f,"\n--%s\n", boundaryStr); + fprintf(f,"Content-type: message/delivery-status\n\n"); + + fprintf(f,"Reporting-MTA: dns; %s\n", smtp_active_hostname); + if (dsn_envid != NULL) { + /* 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 formated ENVID\n"); + } + fprintf(f,"\n"); + + while (addr_dsndefer != NULL) + { + if (addr_dsndefer->dsn_orcpt != NULL) { + fprintf(f,"Original-Recipient: %s\n", addr_dsndefer->dsn_orcpt); + } + fprintf(f,"Action: delayed\n"); + fprintf(f,"Final-Recipient: rfc822;%s\n", addr_dsndefer->address); + fprintf(f,"Status: 4.0.0\n"); + if ((addr_dsndefer->host_used != NULL) && (addr_dsndefer->host_used->name != NULL)) + fprintf(f,"Remote-MTA: dns; %s\nDiagnostic-Code: smtp; %d\n", + addr_dsndefer->host_used->name, addr_dsndefer->basic_errno); + addr_dsndefer = addr_dsndefer->next; + } + + fprintf(f,"\n--%s\n", boundaryStr); + fprintf(f,"Content-type: text/rfc822-headers\n\n"); + + fflush(f); + /* header only as required by RFC. only failure DSN needs to honor RET=FULL */ + int topt = 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 */ + transport_write_message(NULL, fileno(f), topt, 0, NULL, NULL, NULL, NULL, NULL, 0); + fflush(f); + + fprintf(f,"\n"); + fprintf(f,"--%s--\n", boundaryStr); + + fflush(f); +#endif + /* Close and wait for child process to complete, without a timeout. If there's an error, don't update the count. */ diff --git a/src/src/exim.c b/src/src/exim.c index ee2fd82de..09e9c1cba 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -837,6 +837,9 @@ fprintf(f, "Support for:"); #ifdef EXPERIMENTAL_CERTNAMES fprintf(f, " Experimental_Certnames"); #endif +#ifdef EXPERIMENTAL_DSN + fprintf(f, " Experimental_DSN"); +#endif fprintf(f, "\n"); fprintf(f, "Lookups (built-in):"); @@ -2659,6 +2662,16 @@ for (i = 1; i < argc; i++) break; } + #ifdef EXPERIMENTAL_DSN + /* -MCD: set the smtp_use_dsn flag; this indicates that the host + that exim is connected to supports the esmtp extension DSN */ + else if (strcmp(argrest, "CD") == 0) + { + smtp_use_dsn = TRUE; + break; + } + #endif + /* -MCP: set the smtp_use_pipelining flag; this is useful only when it preceded -MC (see above) */ diff --git a/src/src/globals.c b/src/src/globals.c index a25b06a05..f8166aadf 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -126,6 +126,13 @@ tls_support tls_out = { 0 /* tls_ocsp */ }; +#ifdef EXPERIMENTAL_DSN +uschar *dsn_envid = NULL; +int dsn_ret = 0; +const pcre *regex_DSN = NULL; +BOOL smtp_use_dsn = FALSE; +uschar *dsn_advertise_hosts = NULL; +#endif #ifdef SUPPORT_TLS BOOL gnutls_compat_mode = FALSE; @@ -346,6 +353,11 @@ address_item address_defaults = { NULL, /* authenticator */ NULL, /* auth_id */ NULL, /* auth_sndr */ + #ifdef EXPERIMENTAL_DSN + NULL, /* dsn_orcpt */ + 0, /* dsn_flags */ + 0, /* dsn_aware */ + #endif (uid_t)(-1), /* uid */ (gid_t)(-1), /* gid */ 0, /* flags */ @@ -1117,6 +1129,9 @@ router_instance router_defaults = { TRUE, /* verify_sender */ FALSE, /* uid_set */ FALSE, /* unseen */ +#ifdef EXPERIMENTAL_DSN + FALSE, /* dsn_lasthop */ +#endif self_freeze, /* self_code */ (uid_t)(-1), /* uid */ diff --git a/src/src/globals.h b/src/src/globals.h index 3d4cd3981..c2ab99b9c 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -126,6 +126,13 @@ extern uschar *tls_verify_certificates;/* Path for certificates to check */ extern uschar *tls_verify_hosts; /* Mandatory client verification */ #endif +#ifdef EXPERIMENTAL_DSN +extern uschar *dsn_envid; /* DSN envid string */ +extern int dsn_ret; /* DSN ret type*/ +extern const pcre *regex_DSN; /* For recognizing DSN settings */ +extern BOOL smtp_use_dsn; /* Global for passed connections */ +extern uschar *dsn_advertise_hosts; /* host for which TLS is advertised */ +#endif /* Input-reading functions for messages, so we can use special ones for incoming TCP/IP. */ diff --git a/src/src/local_scan.h b/src/src/local_scan.h index 057e4d428..770348a9b 100644 --- a/src/src/local_scan.h +++ b/src/src/local_scan.h @@ -128,6 +128,10 @@ typedef struct recipient_item { uschar *address; /* the recipient address */ int pno; /* parent number for "one_time" alias, or -1 */ uschar *errors_to; /* the errors_to address or NULL */ +#ifdef EXPERIMENTAL_DSN + uschar *orcpt; /* DSN orcpt */ + int dsn_flags; /* DSN flags */ +#endif #ifdef EXPERIMENTAL_BRIGHTMAIL uschar *bmi_optin; #endif diff --git a/src/src/macros.h b/src/src/macros.h index 53c0e3e27..b7dd337e4 100644 --- a/src/src/macros.h +++ b/src/src/macros.h @@ -788,6 +788,29 @@ enum { #define topt_no_body 0x040 /* Omit body */ #define topt_escape_headers 0x080 /* Apply escape check to headers */ +#ifdef EXPERIMENTAL_DSN +/* Flags for recipient_block, used in DSN support */ + +#define rf_dsnlasthop 0x01 /* Do not propagate DSN any further */ +#define rf_notify_never 0x02 /* NOTIFY= settings */ +#define rf_notify_success 0x04 +#define rf_notify_failure 0x08 +#define rf_notify_delay 0x10 + +#define rf_dsnflags (rf_notify_never | rf_notify_success | \ + rf_notify_failure | rf_notify_delay) + +/* DSN RET types */ + +#define dsn_ret_full 1 +#define dsn_ret_hdrs 2 + +#define dsn_support_unknown 0 +#define dsn_support_yes 1 +#define dsn_support_no 2 + +#endif + /* Codes for the host_find_failed and host_all_ignored options. */ #define hff_freeze 0 diff --git a/src/src/readconf.c b/src/src/readconf.c index db1d766b0..11f7184a6 100644 --- a/src/src/readconf.c +++ b/src/src/readconf.c @@ -229,6 +229,9 @@ static optionlist optionlist_config[] = { /* This option is now a no-op, retained for compability */ { "drop_cr", opt_bool, &drop_cr }, /*********************************************************/ +#ifdef EXPERIMENTAL_DSN + { "dsn_advertise_hosts", opt_stringptr, &dsn_advertise_hosts }, +#endif { "dsn_from", opt_stringptr, &dsn_from }, { "envelope_to_remove", opt_bool, &envelope_to_remove }, { "errors_copy", opt_stringptr, &errors_copy }, diff --git a/src/src/receive.c b/src/src/receive.c index 34aa5d91e..ea957c7cf 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -497,6 +497,10 @@ recipients_list[recipients_count].bmi_optin = bmi_current_optin; /* reset optin string pointer for next recipient */ bmi_current_optin = NULL; #endif +#ifdef EXPERIMENTAL_DSN +recipients_list[recipients_count].orcpt = NULL; +recipients_list[recipients_count].dsn_flags = 0; +#endif recipients_list[recipients_count++].errors_to = NULL; } diff --git a/src/src/route.c b/src/src/route.c index 0116e12af..6ba1d9f10 100644 --- a/src/src/route.c +++ b/src/src/route.c @@ -58,6 +58,10 @@ optionlist optionlist_routers[] = { (void *)offsetof(router_instance, domains) }, { "driver", opt_stringptr|opt_public, (void *)offsetof(router_instance, driver_name) }, + #ifdef EXPERIMENTAL_DSN + { "dsn_lasthop", opt_bool|opt_public, + (void *)offsetof(router_instance, dsn_lasthop) }, + #endif { "errors_to", opt_stringptr|opt_public, (void *)(offsetof(router_instance, errors_to)) }, { "expn", opt_bool|opt_public, @@ -270,6 +274,15 @@ for (r = routers; r != NULL; r = r->next) if (r->pass_router_name != NULL) set_router(r, r->pass_router_name, &(r->pass_router), TRUE); + + #ifdef EXPERIMENTAL_DSN + DEBUG(D_route) { + if (r->dsn_lasthop == FALSE) + debug_printf("DSN: %s propagating DSN\n", r->name); + else + debug_printf("DSN: %s lasthop set\n", r->name); + } + #endif } } @@ -1412,6 +1425,10 @@ new->p.errors_address = parent->p.errors_address; copyflag(new, addr, af_propagate); new->p.address_data = addr->p.address_data; +#ifdef EXPERIMENTAL_DSN +new->dsn_flags = addr->dsn_flags; +new->dsn_orcpt = addr->dsn_orcpt; +#endif /* As it has turned out, we haven't set headers_add or headers_remove for the @@ -1719,6 +1736,17 @@ for (r = (addr->start_router == NULL)? routers : addr->start_router; /* Run the router, and handle the consequences. */ +#ifdef EXPERIMENTAL_DSN +/* ... but let us check on DSN before. If this should be the last hop for DSN + set flag +*/ + if ((r->dsn_lasthop == TRUE) && ((addr->dsn_flags & rf_dsnlasthop) == 0)) + { + addr->dsn_flags |= rf_dsnlasthop; + HDEBUG(D_route) debug_printf("DSN: last hop for %s\n", addr->address); + } +#endif + HDEBUG(D_route) debug_printf("calling %s router\n", r->name); yield = (r->info->code)(r, addr, pw, verify, paddr_local, paddr_remote, diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index b7e60bfab..4ea6cd404 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -121,6 +121,9 @@ static BOOL auth_advertised; #ifdef SUPPORT_TLS static BOOL tls_advertised; #endif +#ifdef EXPERIMENTAL_DSN +static BOOL dsn_advertised; +#endif static BOOL esmtp; static BOOL helo_required = FALSE; static BOOL helo_verify = FALSE; @@ -216,6 +219,9 @@ enum { ENV_MAIL_OPT_SIZE, ENV_MAIL_OPT_BODY, ENV_MAIL_OPT_AUTH, #ifndef DISABLE_PRDR ENV_MAIL_OPT_PRDR, +#endif +#ifdef EXPERIMENTAL_DSN + ENV_MAIL_OPT_RET, ENV_MAIL_OPT_ENVID, #endif ENV_MAIL_OPT_NULL }; @@ -231,6 +237,10 @@ static env_mail_type_t env_mail_type_list[] = { { US"AUTH", ENV_MAIL_OPT_AUTH, TRUE }, #ifndef DISABLE_PRDR { US"PRDR", ENV_MAIL_OPT_PRDR, FALSE }, +#endif +#ifdef EXPERIMENTAL_DSN + { US"RET", ENV_MAIL_OPT_RET, TRUE }, + { US"ENVID", ENV_MAIL_OPT_ENVID, TRUE }, #endif { US"NULL", ENV_MAIL_OPT_NULL, FALSE } }; @@ -1488,6 +1498,13 @@ sender_address_unrewritten = NULL; /* Set only after verify rewrite */ sender_verified_list = NULL; /* No senders verified */ memset(sender_address_cache, 0, sizeof(sender_address_cache)); memset(sender_domain_cache, 0, sizeof(sender_domain_cache)); + +#ifdef EXPERIMENTAL_DSN +/* Reset the DSN flags */ +dsn_ret = 0; +dsn_envid = NULL; +#endif + authenticated_sender = NULL; #ifdef EXPERIMENTAL_BRIGHTMAIL bmi_run = 0; @@ -1837,6 +1854,9 @@ tls_in.sni = NULL; tls_in.ocsp = OCSP_NOT_REQ; tls_advertised = FALSE; #endif +#ifdef EXPERIMENTAL_DSN +dsn_advertised = FALSE; +#endif /* Reset ACL connection variables */ @@ -3126,6 +3146,10 @@ while (done <= 0) int ptr, size, rc; int c, i; auth_instance *au; +#ifdef EXPERIMENTAL_DSN + uschar *orcpt = NULL; + int flags; +#endif switch(smtp_read_command(TRUE)) { @@ -3470,6 +3494,9 @@ while (done <= 0) #ifdef SUPPORT_TLS tls_advertised = FALSE; #endif + #ifdef EXPERIMENTAL_DSN + dsn_advertised = FALSE; + #endif smtp_code = US"250 "; /* Default response code plus space*/ if (user_msg == NULL) @@ -3553,6 +3580,16 @@ while (done <= 0) s = string_cat(s, &size, &ptr, US"-8BITMIME\r\n", 11); } + #ifdef EXPERIMENTAL_DSN + /* Advertise DSN support if configured to do so. */ + if (verify_check_host(&dsn_advertise_hosts) != FAIL) + { + s = string_cat(s, &size, &ptr, smtp_code, 3); + s = string_cat(s, &size, &ptr, US"-DSN\r\n", 6); + dsn_advertised = TRUE; + } + #endif + /* Advertise ETRN if there's an ACL checking whether a host is permitted to issue it; a check is made when any host actually tries. */ @@ -3808,6 +3845,45 @@ while (done <= 0) arg_error = TRUE; break; + #ifdef EXPERIMENTAL_DSN + + /* Handle the two DSN options, but only if configured to do so (which + will have caused "DSN" to be given in the EHLO response). The code itself + is included only if configured in at build time. */ + + case ENV_MAIL_OPT_RET: + if (dsn_advertised) { + /* Check if RET has already been set */ + if (dsn_ret > 0) { + synprot_error(L_smtp_syntax_error, 501, NULL, + US"RET can be specified once only"); + goto COMMAND_LOOP; + } + dsn_ret = (strcmpic(value, US"HDRS") == 0)? dsn_ret_hdrs : + (strcmpic(value, US"FULL") == 0)? dsn_ret_full : 0; + DEBUG(D_receive) debug_printf("DSN_RET: %d\n", dsn_ret); + /* Check for invalid invalid value, and exit with error */ + if (dsn_ret == 0) { + synprot_error(L_smtp_syntax_error, 501, NULL, + US"Value for RET is invalid"); + goto COMMAND_LOOP; + } + } + break; + case ENV_MAIL_OPT_ENVID: + if (dsn_advertised) { + /* Check if the dsn envid has been already set */ + if (dsn_envid != NULL) { + synprot_error(L_smtp_syntax_error, 501, NULL, + US"ENVID can be specified once only"); + goto COMMAND_LOOP; + } + dsn_envid = string_copy(value); + DEBUG(D_receive) debug_printf("DSN_ENVID: %s\n", dsn_envid); + } + break; + #endif + /* Handle the AUTH extension. If the value given is not "<>" and either the ACL says "yes" or there is no ACL but the sending host is authenticated, we set it up as the authenticated sender. However, if the @@ -4084,6 +4160,86 @@ while (done <= 0) rcpt_fail_count++; break; } + + #ifdef EXPERIMENTAL_DSN + /* Set the DSN flags orcpt and dsn_flags from the session*/ + orcpt = NULL; + flags = 0; + + if (esmtp) for(;;) + { + uschar *name, *value, *end; + int size; + + if (!extract_option(&name, &value)) + { + break; + } + + if (dsn_advertised && strcmpic(name, US"ORCPT") == 0) + { + /* Check whether orcpt has been already set */ + if (orcpt != NULL) { + synprot_error(L_smtp_syntax_error, 501, NULL, + US"ORCPT can be specified once only"); + goto COMMAND_LOOP; + } + orcpt = string_copy(value); + DEBUG(D_receive) debug_printf("DSN orcpt: %s\n", orcpt); + } + + else if (dsn_advertised && strcmpic(name, US"NOTIFY") == 0) + { + /* Check if the notify flags have been already set */ + if (flags > 0) { + synprot_error(L_smtp_syntax_error, 501, NULL, + US"NOTIFY can be specified once only"); + goto COMMAND_LOOP; + } + if (strcmpic(value, US"NEVER") == 0) flags |= rf_notify_never; else + { + uschar *p = value; + while (*p != 0) + { + uschar *pp = p; + while (*pp != 0 && *pp != ',') pp++; + if (*pp == ',') *pp++ = 0; + if (strcmpic(p, US"SUCCESS") == 0) { + DEBUG(D_receive) debug_printf("DSN: Setting notify success\n"); + flags |= rf_notify_success; + } + else if (strcmpic(p, US"FAILURE") == 0) { + DEBUG(D_receive) debug_printf("DSN: Setting notify failure\n"); + flags |= rf_notify_failure; + } + else if (strcmpic(p, US"DELAY") == 0) { + DEBUG(D_receive) debug_printf("DSN: Setting notify delay\n"); + flags |= rf_notify_delay; + } + else { + /* Catch any strange values */ + synprot_error(L_smtp_syntax_error, 501, NULL, + US"Invalid value for NOTIFY parameter"); + goto COMMAND_LOOP; + } + p = pp; + } + DEBUG(D_receive) debug_printf("DSN Flags: %x\n", flags); + } + } + + /* Unknown option. Stick back the terminator characters and break + the loop. An error for a malformed address will occur. */ + + else + { + DEBUG(D_receive) debug_printf("Invalid RCPT option: %s : %s\n", name, value); + name[-1] = ' '; + value[-1] = '='; + break; + } + } + #endif /* Apply SMTP rewriting then extract the working address. Don't allow "<>" as a recipient address */ @@ -4198,6 +4354,21 @@ while (done <= 0) if (user_msg == NULL) smtp_printf("250 Accepted\r\n"); else smtp_user_msg(US"250", user_msg); receive_add_recipient(recipient, -1); + + #ifdef EXPERIMENTAL_DSN + /* Set the dsn flags in the recipients_list */ + if (orcpt != NULL) + recipients_list[recipients_count-1].orcpt = orcpt; + else + recipients_list[recipients_count-1].orcpt = NULL; + + if (flags != 0) + recipients_list[recipients_count-1].dsn_flags = flags; + else + recipients_list[recipients_count-1].dsn_flags = 0; + DEBUG(D_receive) debug_printf("DSN: orcpt: %s flags: %d\n", recipients_list[recipients_count-1].orcpt, recipients_list[recipients_count-1].dsn_flags); + #endif + } /* The recipient was discarded */ diff --git a/src/src/spool_in.c b/src/src/spool_in.c index ba775bbce..5e604fa15 100644 --- a/src/src/spool_in.c +++ b/src/src/spool_in.c @@ -296,6 +296,11 @@ tls_in.ocsp = OCSP_NOT_REQ; spam_score_int = NULL; #endif +#ifdef EXPERIMENTAL_DSN +dsn_ret = 0; +dsn_envid = NULL; +#endif + /* Generate the full name and open the file. If message_subdir is already set, just look in the given directory. Otherwise, look in both the split and unsplit directories, as for the data file above. */ @@ -470,6 +475,17 @@ for (;;) case 'd': if (Ustrcmp(p, "eliver_firsttime") == 0) deliver_firsttime = TRUE; + #ifdef EXPERIMENTAL_DSN + /* Check if the dsn flags have been set in the header file */ + else if (Ustrncmp(p, "sn_ret", 6) == 0) + { + dsn_ret= atoi(big_buffer + 8); + } + else if (Ustrncmp(p, "sn_envid", 8) == 0) + { + dsn_envid = string_copy(big_buffer + 11); + } + #endif break; case 'f': @@ -615,6 +631,10 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++) { int nn; int pno = -1; + #ifdef EXPERIMENTAL_DSN + int dsn_flags = 0; + uschar *orcpt = NULL; + #endif uschar *errors_to = NULL; uschar *p; @@ -657,6 +677,9 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++) ends with , where pno is the parent number for one_time addresses, and len is the length of the errors_to address (zero meaning none). + + Bit 02 indicates that, again reading from right to left, the data continues + with orcpt len(orcpt),dsn_flags */ while (isdigit(*p)) p--; @@ -687,6 +710,13 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++) else if (*p == '#') { int flags; + + #ifdef EXPERIMENTAL_DSN + #ifndef COMPILE_UTILITY + DEBUG(D_deliver) debug_printf("**** SPOOL_IN - Exim 4 standard format spoolfile\n"); + #endif /* COMPILE_UTILITY */ + #endif + (void)sscanf(CS p+1, "%d", &flags); if ((flags & 0x01) != 0) /* one_time data exists */ @@ -699,15 +729,54 @@ for (recipients_count = 0; recipients_count < rcount; recipients_count++) { p -= len; errors_to = string_copy(p); - } + } + } + + *(--p) = 0; /* Terminate address */ +#ifdef EXPERIMENTAL_DSN + if ((flags & 0x02) != 0) /* one_time data exists */ + { + int len; + while (isdigit(*(--p)) || *p == ',' || *p == '-'); + (void)sscanf(CS p+1, "%d,%d", &len, &dsn_flags); + *p = 0; + if (len > 0) + { + p -= len; + orcpt = string_copy(p); + } } *(--p) = 0; /* Terminate address */ +#endif /* EXPERIMENTAL_DSN */ } +#ifdef EXPERIMENTAL_DSN + #ifndef COMPILE_UTILITY + else + { + DEBUG(D_deliver) debug_printf("**** SPOOL_IN - No additional fields\n"); + } + + if ((orcpt != NULL) || (dsn_flags != 0)) + { + DEBUG(D_deliver) debug_printf("**** SPOOL_IN - address: |%s| orcpt: |%s| dsn_flags: %d\n", + big_buffer, orcpt, dsn_flags); + } + if (errors_to != NULL) + { + DEBUG(D_deliver) debug_printf("**** SPOOL_IN - address: |%s| errorsto: |%s|\n", + big_buffer, errors_to); + } + #endif /* COMPILE_UTILITY */ +#endif /* EXPERIMENTAL_DSN */ recipients_list[recipients_count].address = string_copy(big_buffer); recipients_list[recipients_count].pno = pno; recipients_list[recipients_count].errors_to = errors_to; + #ifdef EXPERIMENTAL_DSN + recipients_list[recipients_count].orcpt = orcpt; + recipients_list[recipients_count].dsn_flags = dsn_flags; + #endif } /* The remainder of the spool header file contains the headers for the message, diff --git a/src/src/spool_out.c b/src/src/spool_out.c index de81786b3..01b70341d 100644 --- a/src/src/spool_out.c +++ b/src/src/spool_out.c @@ -245,6 +245,14 @@ if (tls_in.ourcert) if (tls_in.ocsp) fprintf(f, "-tls_ocsp %d\n", tls_in.ocsp); #endif +#ifdef EXPERIMENTAL_DSN +/* Write the dsn flags to the spool header file */ +DEBUG(D_deliver) debug_printf("DSN: Write SPOOL :-dsn_envid %s\n", dsn_envid); +if (dsn_envid != NULL) fprintf(f, "-dsn_envid %s\n", dsn_envid); +DEBUG(D_deliver) debug_printf("DSN: Write SPOOL :-dsn_ret %d\n", dsn_ret); +if (dsn_ret != 0) fprintf(f, "-dsn_ret %d\n", dsn_ret); +#endif + /* To complete the envelope, write out the tree of non-recipients, followed by the list of recipients. These won't be disjoint the first time, when no checking has been done. If a recipient is a "one-time" alias, it is followed by @@ -255,14 +263,34 @@ fprintf(f, "%d\n", recipients_count); for (i = 0; i < recipients_count; i++) { recipient_item *r = recipients_list + i; - if (r->pno < 0 && r->errors_to == NULL) +#ifdef EXPERIMENTAL_DSN +DEBUG(D_deliver) debug_printf("DSN: Flags :%d\n", r->dsn_flags); +#endif + if (r->pno < 0 && r->errors_to == NULL + #ifdef EXPERIMENTAL_DSN + && r->dsn_flags == 0 + #endif + ) fprintf(f, "%s\n", r->address); else { uschar *errors_to = (r->errors_to == NULL)? US"" : r->errors_to; + #ifdef EXPERIMENTAL_DSN + /* for DSN SUPPORT extend exim 4 spool in a compatible way by + adding new values upfront and add flag 0x02 */ + uschar *orcpt = (r->orcpt == NULL)? US"" : r->orcpt; + fprintf(f, "%s %s %d,%d %s %d,%d#3\n", r->address, orcpt, Ustrlen(orcpt), r->dsn_flags, + errors_to, Ustrlen(errors_to), r->pno); + #else fprintf(f, "%s %s %d,%d#1\n", r->address, errors_to, Ustrlen(errors_to), r->pno); + #endif } + + #ifdef EXPERIMENTAL_DSN + DEBUG(D_deliver) debug_printf("DSN: **** SPOOL_OUT - address: |%s| errorsto: |%s| orcpt: |%s| dsn_flags: %d\n", + r->address, r->errors_to, r->orcpt, r->dsn_flags); + #endif } /* Put a blank line before the headers */ diff --git a/src/src/structs.h b/src/src/structs.h index a9edb4670..71ac5d8e3 100644 --- a/src/src/structs.h +++ b/src/src/structs.h @@ -285,6 +285,9 @@ typedef struct router_instance { BOOL verify_sender; /* Use this router when verifying a sender */ BOOL uid_set; /* Flag to indicate uid is set */ BOOL unseen; /* If TRUE carry on, even after success */ +#ifdef EXPERIMENTAL_DSN + BOOL dsn_lasthop; /* If TRUE, this router is a DSN endpoint */ +#endif int self_code; /* Encoded version of "self" */ uid_t uid; /* Fixed uid value */ @@ -553,6 +556,12 @@ typedef struct address_item { uschar *auth_id; /* auth "login" name used by transport */ uschar *auth_sndr; /* AUTH arg to SMTP MAIL, used by transport */ + #ifdef EXPERIMENTAL_DSN + uschar *dsn_orcpt; /* DSN orcpt value */ + int dsn_flags; /* DSN flags */ + int dsn_aware; /* DSN aware flag */ + #endif + uid_t uid; /* uid for transporting */ gid_t gid; /* gid for transporting */ diff --git a/src/src/tls.c b/src/src/tls.c index ff2d20a24..841807f45 100644 --- a/src/src/tls.c +++ b/src/src/tls.c @@ -331,7 +331,7 @@ else if ((subjdn = tls_cert_subject(cert, NULL))) return FALSE; } # endif /*EXPERIMENTAL_CERTNAMES*/ -#endif /*SUPPORY_TLS*/ +#endif /*SUPPORT_TLS*/ /* vi: aw ai sw=2 */ diff --git a/src/src/transport.c b/src/src/transport.c index 00b8fa9d8..f0b748639 100644 --- a/src/src/transport.c +++ b/src/src/transport.c @@ -1827,6 +1827,11 @@ if ((pid = fork()) == 0) argv = child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0); + #ifdef EXPERIMENTAL_DSN + /* Call with the dsn flag */ + if (smtp_use_dsn) argv[i++] = US"-MCD"; + #endif + if (smtp_authenticated) argv[i++] = US"-MCA"; #ifdef SUPPORT_TLS diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index c175d2ffe..38dcfa080 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -266,6 +266,16 @@ smtp_transport_options_block smtp_transport_option_defaults = { #endif }; +#ifdef EXPERIMENTAL_DSN +/* some DSN flags for use later */ + +static int rf_list[] = {rf_notify_never, rf_notify_success, + rf_notify_failure, rf_notify_delay }; + +static uschar *rf_names[] = { "NEVER", "SUCCESS", "FAILURE", "DELAY" }; +#endif + + /* Local statics */ @@ -1196,6 +1206,9 @@ BOOL pass_message = FALSE; BOOL prdr_offered = FALSE; BOOL prdr_active; #endif +#ifdef EXPERIMENTAL_DSN +BOOL dsn_all_lasthop = TRUE; +#endif smtp_inblock inblock; smtp_outblock outblock; int max_rcpt = tblock->max_addresses; @@ -1603,6 +1616,13 @@ if (continue_hostname == NULL {DEBUG(D_transport) debug_printf("PRDR usable\n");} #endif +#ifdef EXPERIMENTAL_DSN + /* Note if the server supports DSN */ + smtp_use_dsn = esmtp && pcre_exec(regex_DSN, NULL, CS buffer, (int)Ustrlen(CS buffer), 0, + PCRE_EOPT, NULL, 0) >= 0; + DEBUG(D_transport) debug_printf("use_dsn=%d\n", smtp_use_dsn); +#endif + /* Note if the response to EHLO specifies support for the AUTH extension. If it has, check that this host is one we want to authenticate to, and do the business. The host name and address must be available when the @@ -1700,6 +1720,38 @@ if (prdr_offered) prdr_is_active: #endif +#ifdef EXPERIMENTAL_DSN +/* check if all addresses have lasthop flag */ +/* do not send RET and ENVID if true */ +dsn_all_lasthop = TRUE; +for (addr = first_addr; + address_count < max_rcpt && addr != NULL; + addr = addr->next) + if ((addr->dsn_flags & rf_dsnlasthop) != 1) + dsn_all_lasthop = FALSE; + +/* Add any DSN flags to the mail command */ + +if ((smtp_use_dsn) && (dsn_all_lasthop == FALSE)) + { + if (dsn_ret == dsn_ret_hdrs) + { + strcpy(p, " RET=HDRS"); + while (*p) p++; + } + else if (dsn_ret == dsn_ret_full) + { + strcpy(p, " RET=FULL"); + while (*p) p++; + } + if (dsn_envid != NULL) + { + string_format(p, sizeof(buffer) - (p-buffer), " ENVID=%s", dsn_envid); + while (*p) p++; + } + } +#endif + /* If an authenticated_sender override has been specified for this transport instance, expand it. If the expansion is forced to fail, and there was already an authenticated_sender for this message, the original value will be used. @@ -1762,18 +1814,66 @@ for (addr = first_addr; int count; BOOL no_flush; + #ifdef EXPERIMENTAL_DSN + if(smtp_use_dsn) + addr->dsn_aware = dsn_support_yes; + else + addr->dsn_aware = dsn_support_no; + #endif + if (addr->transport_return != PENDING_DEFER) continue; address_count++; no_flush = smtp_use_pipelining && (!mua_wrapper || addr->next != NULL); + #ifdef EXPERIMENTAL_DSN + /* Add any DSN flags to the rcpt command and add to the sent string */ + + p = buffer; + *p = 0; + + if ((smtp_use_dsn) && ((addr->dsn_flags & rf_dsnlasthop) != 1)) + { + if ((addr->dsn_flags & rf_dsnflags) != 0) + { + int i; + BOOL first = TRUE; + strcpy(p, " NOTIFY="); + while (*p) p++; + for (i = 0; i < 4; i++) + { + if ((addr->dsn_flags & rf_list[i]) != 0) + { + if (!first) *p++ = ','; + first = FALSE; + strcpy(p, rf_names[i]); + while (*p) p++; + } + } + } + + if (addr->dsn_orcpt != NULL) { + string_format(p, sizeof(buffer) - (p-buffer), " ORCPT=%s", + addr->dsn_orcpt); + while (*p) p++; + } + } + #endif + + /* Now send the RCPT command, and process outstanding responses when necessary. After a timeout on RCPT, we just end the function, leaving the yield as OK, because this error can often mean that there is a problem with just one address, so we don't want to delay the host. */ + #ifdef EXPERIMENTAL_DSN + count = smtp_write_command(&outblock, no_flush, "RCPT TO:<%s>%s%s\r\n", + transport_rcpt_address(addr, tblock->rcpt_include_affixes), igquotstr, buffer); + #else count = smtp_write_command(&outblock, no_flush, "RCPT TO:<%s>%s\r\n", transport_rcpt_address(addr, tblock->rcpt_include_affixes), igquotstr); + #endif + if (count < 0) goto SEND_FAILED; if (count > 0) { -- 2.25.1