RFC3461 support - MIME DSN messages. Bug 118
authorWolfgang Breyha <wbreyha@gmx.net>
Wed, 21 May 2014 15:21:46 +0000 (16:21 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Wed, 21 May 2014 15:41:49 +0000 (16:41 +0100)
21 files changed:
doc/doc-txt/experimental-spec.txt
src/README.DSN [new file with mode: 0644]
src/exim_monitor/em_globals.c
src/src/EDITME
src/src/config.h.defaults
src/src/deliver.c
src/src/exim.c
src/src/globals.c
src/src/globals.h
src/src/local_scan.h
src/src/macros.h
src/src/readconf.c
src/src/receive.c
src/src/route.c
src/src/smtp_in.c
src/src/spool_in.c
src/src/spool_out.c
src/src/structs.h
src/src/tls.c
src/src/transport.c
src/src/transports/smtp.c

index 5885434..031c5f4 100644 (file)
@@ -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 (file)
index 0000000..68d1641
--- /dev/null
@@ -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
+
index b0a912e..d5205d0 100644 (file)
@@ -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
index 83ca43c..0d31ba5 100644 (file)
@@ -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                  #
index 3ab73d8..0bb97a2 100644 (file)
@@ -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
index 7af101a..68c0487 100644 (file)
@@ -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 <Mailer-Daemon@%s>\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. */
 
index ee2fd82..09e9c1c 100644 (file)
@@ -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) */
 
index a25b06a..f8166aa 100644 (file)
@@ -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 */
index 3d4cd39..c2ab99b 100644 (file)
@@ -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. */
index 057e4d4..770348a 100644 (file)
@@ -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
index 53c0e3e..b7dd337 100644 (file)
@@ -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
index db1d766..11f7184 100644 (file)
@@ -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 },
index 34aa5d9..ea957c7 100644 (file)
@@ -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;
 }
 
index 0116e12..6ba1d9f 100644 (file)
@@ -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,
index b7e60bf..4ea6cd4 100644 (file)
@@ -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;
@@ -217,6 +220,9 @@ enum {
 #ifndef DISABLE_PRDR
   ENV_MAIL_OPT_PRDR,
 #endif
+#ifdef EXPERIMENTAL_DSN
+  ENV_MAIL_OPT_RET, ENV_MAIL_OPT_ENVID,
+#endif
   ENV_MAIL_OPT_NULL
   };
 typedef struct {
@@ -232,6 +238,10 @@ static env_mail_type_t env_mail_type_list[] = {
 #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 */
index ba775bb..5e604fa 100644 (file)
@@ -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 <errors_to address><space><len>,<pno> 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,
index de81786..01b7034 100644 (file)
@@ -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 */
index a9edb46..71ac5d8 100644 (file)
@@ -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 */
 
index ff2d20a..841807f 100644 (file)
@@ -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
 */
index 00b8fa9..f0b7486 100644 (file)
@@ -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
index c175d2f..38dcfa0 100644 (file)
@@ -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)
     {