Introduce EXPERIMENTAL_DANE feature
[exim.git] / src / src / deliver.c
index 7af101ac0587120ba4335a66c3c07cdf18642ebc..78b669ad2cdbddd9ddb5acc852d79ef1c294fd6f 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;
@@ -137,11 +141,13 @@ the first address. */
 if (addr->host_list == NULL)
   {
   deliver_host = deliver_host_address = US"";
+  deliver_host_port = 0;
   }
 else
   {
   deliver_host = addr->host_list->name;
   deliver_host_address = addr->host_list->address;
+  deliver_host_port = addr->host_list->port;
   }
 
 deliver_recipients = addr;
@@ -693,7 +699,15 @@ d_tlslog(uschar * s, int * sizep, int * ptrp, address_item * addr)
   if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
        addr->cipher != NULL)
     s = string_append(s, sizep, ptrp, 2, US" CV=",
-      testflag(addr, af_cert_verified)? "yes":"no");
+      testflag(addr, af_cert_verified)
+      ?
+#ifdef EXPERIMENTAL_DANE
+        testflag(addr, af_dane_verified)
+      ? "dane"
+      :
+#endif
+        "yes"
+      : "no");
   if ((log_extra_selector & LX_tls_peerdn) != 0 && addr->peerdn != NULL)
     s = string_append(s, sizep, ptrp, 3, US" DN=\"",
       string_printing(addr->peerdn), US"\"");
@@ -701,6 +715,70 @@ d_tlslog(uschar * s, int * sizep, int * ptrp, address_item * addr)
 }
 #endif
 
+
+
+
+#ifdef EXPERIMENTAL_TPDA
+int
+tpda_raise_event(uschar * action, uschar * event, uschar * ev_data)
+{
+uschar * s;
+if (action)
+  {
+  DEBUG(D_deliver)
+    debug_printf("TPDA(%s): tpda_event_action=|%s| tpda_delivery_IP=%s\n",
+      event,
+      action, deliver_host_address);
+
+  tpda_event = event;
+  tpda_data =  ev_data;
+
+  if (!(s = expand_string(action)) && *expand_string_message)
+    log_write(0, LOG_MAIN|LOG_PANIC,
+      "failed to expand tpda_event_action %s in %s: %s\n",
+      event, transport_name, expand_string_message);
+
+  tpda_event = tpda_data = NULL;
+
+  /* If the expansion returns anything but an empty string, flag for
+  the caller to modify his normal processing
+  */
+  if (s && *s)
+    {
+    DEBUG(D_deliver)
+      debug_printf("TPDA(%s): event_action returned \"%s\"\n", s);
+    return DEFER;
+    }
+  }
+return OK;
+}
+
+static void
+tpda_msg_event(uschar * event, address_item * addr)
+{
+uschar * save_domain = deliver_domain;
+uschar * save_local =  deliver_localpart;
+
+if (!addr->transport)
+  return;
+
+router_name =    addr->router ? addr->router->name : NULL;
+transport_name = addr->transport->name;
+deliver_domain = addr->domain;
+deliver_localpart = addr->local_part;
+
+(void) tpda_raise_event(addr->transport->tpda_event_action, event,
+         addr->host_used || Ustrcmp(addr->transport->driver_name, "lmtp") == 0
+         ? addr->message : NULL);
+
+deliver_localpart = save_local;
+deliver_domain =    save_domain;
+router_name = transport_name = NULL;
+}
+#endif /*EXPERIMENTAL_TPDA*/
+
+
+
 /* If msg is NULL this is a delivery log and logchar is used. Otherwise
 this is a nonstandard call; no two-character delivery flag is written
 but sender-host and sender are prefixed and "msg" is inserted in the log line.
@@ -724,12 +802,7 @@ have a pointer to the host item that succeeded; local deliveries can have a
 pointer to a single host item in their host list, for use by the transport. */
 
 #ifdef EXPERIMENTAL_TPDA
-  tpda_delivery_ip = NULL;     /* presume no successful remote delivery */
-  tpda_delivery_port = 0;
-  tpda_delivery_fqdn = NULL;
-  tpda_delivery_local_part = NULL;
-  tpda_delivery_domain = NULL;
-  tpda_delivery_confirmation = NULL;
+  /* presume no successful remote delivery */
   lookup_dnssec_authenticated = NULL;
 #endif
 
@@ -778,13 +851,8 @@ if ((log_extra_selector & LX_delivery_size) != 0)
 
 if (addr->transport->info->local)
   {
-  if (addr->host_list != NULL)
-    {
+  if (addr->host_list)
     s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name);
-    #ifdef EXPERIMENTAL_TPDA
-      tpda_delivery_fqdn = addr->host_list->name;
-    #endif
-    }
   if (addr->shadow_message != NULL)
     s = string_cat(s, &size, &ptr, addr->shadow_message,
       Ustrlen(addr->shadow_message));
@@ -800,24 +868,20 @@ else
     if (continue_sequence > 1)
       s = string_cat(s, &size, &ptr, US"*", 1);
 
-    #ifdef EXPERIMENTAL_TPDA
-    tpda_delivery_ip =           addr->host_used->address;
-    tpda_delivery_port =         addr->host_used->port;
-    tpda_delivery_fqdn =         addr->host_used->name;
-    tpda_delivery_local_part =   addr->local_part;
-    tpda_delivery_domain =       addr->domain;
-    tpda_delivery_confirmation = addr->message;
+#ifdef EXPERIMENTAL_TPDA
+    deliver_host_address = addr->host_used->address;
+    deliver_host_port =    addr->host_used->port;
 
     /* DNS lookup status */
     lookup_dnssec_authenticated = addr->host_used->dnssec==DS_YES ? US"yes"
                              : addr->host_used->dnssec==DS_NO ? US"no"
                              : NULL;
-    #endif
+#endif
     }
 
-  #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
   s = d_tlslog(s, &size, &ptr, addr);
-  #endif
+#endif
 
   if (addr->authenticator)
     {
@@ -830,25 +894,25 @@ else
       }
     }
 
-  #ifndef DISABLE_PRDR
+#ifndef DISABLE_PRDR
   if (addr->flags & af_prdr_used)
     s = string_append(s, &size, &ptr, 1, US" PRDR");
-  #endif
+#endif
   }
 
 /* confirmation message (SMTP (host_used) and LMTP (driver_name)) */
 
-if ((log_extra_selector & LX_smtp_confirmation) != 0 &&
-    addr->message != NULL &&
-    ((addr->host_used != NULL) || (Ustrcmp(addr->transport->driver_name, "lmtp") == 0)))
+if (log_extra_selector & LX_smtp_confirmation &&
+    addr->message &&
+    (addr->host_used || Ustrcmp(addr->transport->driver_name, "lmtp") == 0))
   {
   int i;
   uschar *p = big_buffer;
   uschar *ss = addr->message;
   *p++ = '\"';
-  for (i = 0; i < 100 && ss[i] != 0; i++)
+  for (i = 0; i < 256 && ss[i] != 0; i++)      /* limit logged amount */
     {
-    if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\';
+    if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\'; /* quote \ and " */
     *p++ = ss[i];
     }
   *p++ = '\"';
@@ -859,16 +923,12 @@ if ((log_extra_selector & LX_smtp_confirmation) != 0 &&
 /* Time on queue and actual time taken to deliver */
 
 if ((log_extra_selector & LX_queue_time) != 0)
-  {
   s = string_append(s, &size, &ptr, 2, US" QT=",
-    readconf_printtime(time(NULL) - received_time));
-  }
+    readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) );
 
 if ((log_extra_selector & LX_deliver_time) != 0)
-  {
   s = string_append(s, &size, &ptr, 2, US" DT=",
     readconf_printtime(addr->more_errno));
-  }
 
 /* string_cat() always leaves room for the terminator. Release the
 store we used to build the line after writing it. */
@@ -877,21 +937,10 @@ s[ptr] = 0;
 log_write(0, flags, "%s", s);
 
 #ifdef EXPERIMENTAL_TPDA
-if (addr->transport->tpda_delivery_action)
-  {
-  DEBUG(D_deliver)
-    debug_printf("  TPDA(Delivery): tpda_deliver_action=|%s| tpda_delivery_IP=%s\n",
-      addr->transport->tpda_delivery_action, tpda_delivery_ip);
-
-  router_name =    addr->router->name;
-  transport_name = addr->transport->name;
-  if (!expand_string(addr->transport->tpda_delivery_action) && *expand_string_message)
-    log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand tpda_deliver_action in %s: %s\n",
-      transport_name, expand_string_message);
-  router_name = NULL;
-  transport_name = NULL;
-  }
+/*XXX cutthrough calls this also for non-delivery...*/
+tpda_msg_event(US"msg:delivery", addr);
 #endif
+
 store_reset(reset_point);
 return;
 }
@@ -1089,7 +1138,7 @@ if (result == OK)
     }
 
   /* Certificates for logging (via TPDA) */
-  #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
   tls_out.ourcert = addr->ourcert;
   addr->ourcert = NULL;
   tls_out.peercert = addr->peercert;
@@ -1098,11 +1147,14 @@ if (result == OK)
   tls_out.cipher = addr->cipher;
   tls_out.peerdn = addr->peerdn;
   tls_out.ocsp = addr->ocsp;
-  #endif
+# ifdef EXPERIMENTAL_DANE
+  tls_out.dane_verified = testflag(addr, af_dane_verified);
+# endif
+#endif
 
   delivery_log(LOG_MAIN, addr, logchar, NULL);
 
-  #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
   if (tls_out.ourcert)
     {
     tls_free_cert(tls_out.ourcert);
@@ -1116,7 +1168,10 @@ if (result == OK)
   tls_out.cipher = NULL;
   tls_out.peerdn = NULL;
   tls_out.ocsp = OCSP_NOT_REQ;
-  #endif
+# ifdef EXPERIMENTAL_DANE
+  tls_out.dane_verified = FALSE;
+# endif
+#endif
   }
 
 
@@ -1297,9 +1352,9 @@ else
   if (addr->host_used != NULL)
     s = d_hostlog(s, &size, &ptr, addr);
 
-  #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
   s = d_tlslog(s, &size, &ptr, addr);
-  #endif
+#endif
 
   if (addr->basic_errno > 0)
     s = string_append(s, &size, &ptr, 2, US": ",
@@ -1319,6 +1374,11 @@ else
     deliver_msglog("%s %s\n", now, s);
 
   log_write(0, LOG_MAIN, "** %s", s);
+
+#ifdef EXPERIMENTAL_TPDA
+  tpda_msg_event(US"msg:fail:delivery", addr);
+#endif
+
   store_reset(reset_point);
   }
 
@@ -1888,19 +1948,19 @@ if ((pid = fork()) == 0)
   diagnosis that it's reasonable to make them something that has to be explicitly requested.
   */
 
-  #ifdef RLIMIT_CORE
+#ifdef RLIMIT_CORE
   struct rlimit rl;
   rl.rlim_cur = 0;
   rl.rlim_max = 0;
   if (setrlimit(RLIMIT_CORE, &rl) < 0)
     {
-    #ifdef SETRLIMIT_NOT_SUPPORTED
+ifdef SETRLIMIT_NOT_SUPPORTED
     if (errno != ENOSYS && errno != ENOTSUP)
-    #endif
+endif
       log_write(0, LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_CORE) failed: %s",
         strerror(errno));
     }
-  #endif
+#endif
 
   /* Reset the random number generator, so different processes don't all
   have the same sequence. */
@@ -2987,7 +3047,7 @@ while (!done)
     it in with the other info, in order to keep each message short enough to
     guarantee it won't be split in the pipe. */
 
-    #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
     case 'X':
     if (addr == NULL) goto ADDR_MISMATCH;          /* Below, in 'A' handler */
     switch (*ptr++)
@@ -3015,17 +3075,17 @@ while (!done)
        (void) tls_import_cert(ptr, &addr->ourcert);
       break;
 
-      #ifdef EXPERIMENTAL_OCSP
+# ifndef DISABLE_OCSP
       case '4':
       addr->ocsp = OCSP_NOT_REQ;
       if (*ptr)
        addr->ocsp = *ptr - '0';
       break;
-      #endif
+endif
       }
     while (*ptr++);
     break;
-    #endif     /*SUPPORT_TLS*/
+#endif /*SUPPORT_TLS*/
 
     case 'C':  /* client authenticator information */
     switch (*ptr++)
@@ -3049,6 +3109,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)
       {
@@ -3945,11 +4014,11 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
     that it can use either of them, though it prefers O_NONBLOCK, which
     distinguishes between EOF and no-more-data. */
 
-    #ifdef O_NONBLOCK
+#ifdef O_NONBLOCK
     (void)fcntl(pfd[pipe_read], F_SETFL, O_NONBLOCK);
-    #else
+#else
     (void)fcntl(pfd[pipe_read], F_SETFL, O_NDELAY);
-    #endif
+#endif
 
     /* If the maximum number of subprocesses already exist, wait for a process
     to finish. If we ran out of file descriptors, parmax will have been reduced
@@ -4116,9 +4185,12 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
 
       /* The certificate verification status goes into the flags */
       if (tls_out.certificate_verified) setflag(addr, af_cert_verified);
+#ifdef EXPERIMENTAL_DANE
+      if (tls_out.dane_verified)        setflag(addr, af_dane_verified);
+#endif
 
       /* Use an X item only if there's something to send */
-      #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
       if (addr->cipher)
         {
         ptr = big_buffer;
@@ -4154,7 +4226,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
          *ptr++ = 0;
         rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
        }
-      # ifdef EXPERIMENTAL_OCSP
+# ifndef DISABLE_OCSP
       if (addr->ocsp > OCSP_NOT_REQ)
        {
        ptr = big_buffer;
@@ -4162,8 +4234,8 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
        while(*ptr++);
         rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
        }
-      # endif
-      #endif   /*SUPPORT_TLS*/
+# endif
+#endif /*SUPPORT_TLS*/
 
       if (client_authenticator)
         {
@@ -4187,10 +4259,17 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++)
         rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer);
        }
 
-      #ifndef DISABLE_PRDR
+#ifndef DISABLE_PRDR
       if (addr->flags & af_prdr_used)
        rmt_dlv_checked_write(fd, "P", 1);
-      #endif
+#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. */
 
@@ -4905,7 +4984,7 @@ attempted. */
 
 if (deliver_freeze)
   {
-  #ifdef SUPPORT_MOVE_FROZEN_MESSAGES
+#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
   /* Moving to another directory removes the message from Exim's view. Other
   tools must be used to deal with it. Logging of this action happens in
   spool_move_message() and its subfunctions. */
@@ -4913,7 +4992,7 @@ if (deliver_freeze)
   if (move_frozen_messages &&
       spool_move_message(id, message_subdir, US"", US"F"))
     return continue_closedown();   /* yields DELIVER_NOT_ATTEMPTED */
-  #endif
+#endif
 
   /* For all frozen messages (bounces or not), timeout_frozen_after sets the
   maximum time to keep messages that are frozen. Thaw if we reach it, with a
@@ -5342,6 +5421,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. */
@@ -5410,6 +5497,25 @@ if (process_recipients != RECIP_IGNORE)
         addr_last = new;
         break;
         }
+
+#ifdef EXPERIMENTAL_TPDA
+      if (process_recipients != RECIP_ACCEPT)
+       {
+       uschar * save_local =  deliver_localpart;
+       uschar * save_domain = deliver_domain;
+
+       deliver_localpart = expand_string(
+                     string_sprintf("${local_part:%s}", new->address));
+       deliver_domain =    expand_string(
+                     string_sprintf("${domain:%s}", new->address));
+
+       (void) tpda_raise_event(delivery_event_action,
+                     US"msg:fail:internal", new->message);
+
+       deliver_localpart = save_local;
+       deliver_domain =    save_domain;
+       }
+#endif
       }
     }
   }
@@ -6276,15 +6382,21 @@ if (addr_remote != NULL)
     regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
       FALSE, TRUE);
 
-  #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
   if (regex_STARTTLS == NULL) regex_STARTTLS =
     regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
-  #endif
+#endif
 
-  #ifndef DISABLE_PRDR
+#ifndef DISABLE_PRDR
   if (regex_PRDR == NULL) regex_PRDR =
     regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE);
-  #endif
+#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
@@ -6390,6 +6502,169 @@ 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, sizeof(boundaryStr)-1, TIME_T_FMT "-eximdsn-%d",
+      time(NULL), rand());
+    DEBUG(D_deliver) debug_printf("DSN: MIME boundary: %s\n", boundaryStr);
+  
+    if (errors_reply_to)
+      fprintf(f, "Reply-To: %s\n", errors_reply_to);
+    fprintf(f, "Auto-Submitted: auto-generated\n"
+       "From: Mail Delivery System <Mailer-Daemon@%s>\n"
+       "To: %s\n"
+       "Subject: Delivery Status Notification\n"
+       "Content-Type: multipart/report; report-type=delivery-status; boundary=%s\n"
+       "MIME-Version: 1.0\n\n"
+
+       "--%s\n"
+       "Content-type: text/plain; charset=us-ascii\n\n"
+   
+       "This message was created automatically by mail delivery software.\n"
+       " ----- The following addresses had successful delivery notifications -----\n",
+      qualify_domain_sender, sender_address, boundaryStr, boundaryStr);
+
+    addr_dsntmp = addr_senddsn;
+    while(addr_dsntmp)
+      {
+      fprintf(f, "<%s> (relayed %s)\n\n",
+       addr_dsntmp->address,
+       (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1
+         ? "via non DSN router"
+         : addr_dsntmp->dsn_aware == dsn_support_no
+         ? "to non-DSN-aware mailer"
+         : "via non \"Remote SMTP\" router"
+       );
+      addr_dsntmp = addr_dsntmp->next;
+      }
+    fprintf(f, "--%s\n"
+       "Content-type: message/delivery-status\n\n"
+       "Reporting-MTA: dns; %s\n",
+      boundaryStr, 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");
+      }
+    fputc('\n', f);
+
+    for (addr_dsntmp = addr_senddsn;
+        addr_dsntmp;
+        addr_dsntmp = addr_dsntmp->next)
+      {
+      if (addr_dsntmp->dsn_orcpt)
+        fprintf(f,"Original-Recipient: %s\n", addr_dsntmp->dsn_orcpt);
+
+      fprintf(f, "Action: delivered\n"
+         "Final-Recipient: rfc822;%s\n"
+         "Status: 2.0.0\n",
+       addr_dsntmp->address);
+
+      if (addr_dsntmp->host_used && addr_dsntmp->host_used->name)
+        fprintf(f, "Remote-MTA: dns; %s\nDiagnostic-Code: smtp; 250 Ok\n",
+         addr_dsntmp->host_used->name);
+      else
+       fprintf(f,"Diagnostic-Code: X-Exim; relayed via non %s router\n",
+         (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1 ? "DSN" : "SMTP");
+      fputc('\n', f);
+      }
+
+    fprintf(f, "--%s\nContent-type: text/rfc822-headers\n\n", boundaryStr);
+           
+    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 /*EXPERIMENTAL_DSN*/
+
 /* 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 +6722,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);
@@ -6500,6 +6780,12 @@ while (addr_failed != NULL)
       BOOL to_sender = strcmpic(sender_address, bounce_recipient) == 0;
       int max = (bounce_return_size_limit/DELIVER_IN_BUFFER_SIZE + 1) *
         DELIVER_IN_BUFFER_SIZE;
+#ifdef EXPERIMENTAL_DSN
+      uschar boundaryStr[64];
+      uschar *dsnlimitmsg;
+      uschar *dsnnotifyhdr;
+      int topt;
+#endif
 
       DEBUG(D_deliver)
         debug_printf("sending error message to: %s\n", bounce_recipient);
@@ -6553,57 +6839,70 @@ 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 */
+      snprintf(boundaryStr, sizeof(boundaryStr)-1, TIME_T_FMT "-eximdsn-%d",
+       time(NULL), rand());
+
+      fprintf(f, "Content-Type: multipart/report;"
+           " report-type=delivery-status; boundary=%s\n"
+         "MIME-Version: 1.0\n",
+       boundaryStr);
+#endif
+
       /* Open a template file if one is provided. Log failure to open, but
       carry on - default texts will be used. */
 
-      if (bounce_message_file != NULL)
-        {
-        emf = Ufopen(bounce_message_file, "rb");
-        if (emf == NULL)
+      if (bounce_message_file)
+        if (!(emf = Ufopen(bounce_message_file, "rb")))
           log_write(0, LOG_MAIN|LOG_PANIC, "Failed to open %s for error "
             "message texts: %s", bounce_message_file, strerror(errno));
-        }
 
       /* Quietly copy to configured additional addresses if required. */
 
-      bcc = moan_check_errorcopy(bounce_recipient);
-      if (bcc != NULL) fprintf(f, "Bcc: %s\n", bcc);
+      if ((bcc = moan_check_errorcopy(bounce_recipient)))
+       fprintf(f, "Bcc: %s\n", bcc);
 
       /* The texts for the message can be read from a template file; if there
       isn't one, or if it is too short, built-in texts are used. The first
       emf text is a Subject: and any other headers. */
 
-      emf_text = next_emf(emf, US"header");
-      if (emf_text != NULL) fprintf(f, "%s\n", emf_text); else
-        {
+      if ((emf_text = next_emf(emf, US"header")))
+       fprintf(f, "%s\n", emf_text);
+      else
         fprintf(f, "Subject: Mail delivery failed%s\n\n",
           to_sender? ": returning message to sender" : "");
-        }
 
-      emf_text = next_emf(emf, US"intro");
-      if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else
+#ifdef EXPERIMENTAL_DSN
+      /* output human readable part as text/plain section */
+      fprintf(f, "--%s\n"
+         "Content-type: text/plain; charset=us-ascii\n\n",
+       boundaryStr);
+#endif
+
+      if ((emf_text = next_emf(emf, US"intro")))
+       fprintf(f, "%s", CS emf_text);
+      else
         {
         fprintf(f,
 /* This message has been reworded several times. It seems to be confusing to
 somebody, however it is worded. I have retreated to the original, simple
 wording. */
 "This message was created automatically by mail delivery software.\n");
-        if (bounce_message_text != NULL) fprintf(f, "%s", CS bounce_message_text);
+
+        if (bounce_message_text)
+         fprintf(f, "%s", CS bounce_message_text);
         if (to_sender)
-          {
           fprintf(f,
 "\nA message that you sent could not be delivered to one or more of its\n"
 "recipients. This is a permanent error. The following address(es) failed:\n");
-          }
         else
-          {
           fprintf(f,
 "\nA message sent by\n\n  <%s>\n\n"
 "could not be delivered to one or more of its recipients. The following\n"
 "address(es) failed:\n", sender_address);
-          }
         }
-      fprintf(f, "\n");
+      fputc('\n', f);
 
       /* Process the addresses, leaving them on the msgchain if they have a
       file name for a return message. (There has already been a check in
@@ -6640,7 +6939,7 @@ wording. */
           }
         }
 
-      fprintf(f, "\n");
+      fputc('\n', f);
 
       /* Get the next text, whether we need it or not, so as to be
       positioned for the one after. */
@@ -6654,11 +6953,13 @@ wording. */
       fd, and the return_filename field in the *last* one will be set (to the
       name of the file). */
 
-      if (msgchain != NULL)
+      if (msgchain)
         {
         address_item *nextaddr;
 
-        if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else
+        if (emf_text)
+         fprintf(f, "%s", CS emf_text);
+       else
           fprintf(f,
             "The following text was generated during the delivery "
             "attempt%s:\n", (filecount > 1)? "s" : "");
@@ -6670,15 +6971,15 @@ wording. */
 
           /* List all the addresses that relate to this file */
 
-          fprintf(f, "\n");
-          while(addr != NULL)                   /* Insurance */
+         fputc('\n', f);
+          while(addr)                   /* Insurance */
             {
             print_address_information(addr, f, US"------ ",  US"\n       ",
               US" ------\n");
-            if (addr->return_filename != NULL) break;
+            if (addr->return_filename) break;
             addr = addr->next;
             }
-          fprintf(f, "\n");
+         fputc('\n', f);
 
           /* Now copy the file */
 
@@ -6701,9 +7002,39 @@ wording. */
           addr->next = handled_addr;
           handled_addr = topaddr;
           }
-        fprintf(f, "\n");
+       fputc('\n', f);
         }
 
+#ifdef EXPERIMENTAL_DSN
+      /* output machine readable part */
+      fprintf(f, "--%s\n"
+         "Content-type: message/delivery-status\n\n"
+         "Reporting-MTA: dns; %s\n",
+       boundaryStr, smtp_active_hostname);
+
+      if (dsn_envid)
+       {
+        /* must be decoded from xtext: see RFC 3461:6.3a */
+        uschar *xdec_envid;
+        if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0)
+          fprintf(f, "Original-Envelope-ID: %s\n", dsn_envid);
+        else
+          fprintf(f, "X-Original-Envelope-ID: error decoding xtext formated ENVID\n");
+        }
+      fputc('\n', f);
+      for (addr = handled_addr; addr; addr = addr->next)
+        {
+        fprintf(f, "Action: failed\n"
+           "Final-Recipient: rfc822;%s\n"
+           "Status: 5.0.0\n",
+           addr->address);
+        if (addr->host_used && addr->host_used->name)
+          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,12 +7042,15 @@ wording. */
 
       emf_text = next_emf(emf, US"copy");
 
+#ifndef EXPERIMENTAL_DSN
       if (bounce_return_message)
         {
         int topt = topt_add_return_path;
         if (!bounce_return_body) topt |= topt_no_body;
 
-        if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else
+        if (emf_text)
+         fprintf(f, "%s", CS emf_text);
+       else
           {
           if (bounce_return_body) fprintf(f,
 "------ This is a copy of the message, including all the headers. ------\n");
@@ -6739,18 +7073,19 @@ wording. */
           {
           struct stat statbuf;
           if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max)
-            {
-            if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else
-              {
+           {
+            if (emf_text)
+             fprintf(f, "%s", CS emf_text);
+           else
               fprintf(f,
 "------ The body of the message is " OFF_T_FMT " characters long; only the first\n"
 "------ %d or so are included here.\n", statbuf.st_size, max);
-              }
-            }
+           }
           }
 
-        fprintf(f, "\n");
+       fputc('\n', f);
         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,
@@ -6759,12 +7094,71 @@ wording. */
 
       /* Write final text and close the template file if one is open */
 
-      if (emf != NULL)
+      if (emf)
         {
-        emf_text = next_emf(emf, US"final");
-        if (emf_text != NULL) fprintf(f, "%s", CS emf_text);
+        if ((emf_text = next_emf(emf, US"final")))
+         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);
+
+      dsnlimitmsg = US"X-Exim-DSN-Information: Due to administrative limits only headers are returned";
+      dsnnotifyhdr = NULL;
+      topt = topt_add_return_path;
+
+      /* RET=HDRS? top priority */
+      if (dsn_ret == dsn_ret_hdrs)
+        topt |= topt_no_body;
+      else
+        /* 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)
+        (void)fclose(emf);
+      fprintf(f, "\n--%s--\n", boundaryStr);
+#endif /*EXPERIMENTAL_DSN*/
 
       /* Close the file, which should send an EOF to the child process
       that is receiving the message. Wait for it to finish. */
@@ -6873,13 +7267,17 @@ if (addr_defer == NULL)
 
   if ((log_extra_selector & LX_queue_time_overall) != 0)
     log_write(0, LOG_MAIN, "Completed QT=%s",
-      readconf_printtime(time(NULL) - received_time));
+      readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) );
   else
     log_write(0, LOG_MAIN, "Completed");
 
   /* Unset deliver_freeze so that we won't try to move the spool files further down */
   deliver_freeze = FALSE;
-  }
+
+#ifdef EXPERIMENTAL_TPDA
+  (void) tpda_raise_event(delivery_event_action, US"msg:complete", NULL);
+#endif
+}
 
 /* If there are deferred addresses, we are keeping this message because it is
 not yet completed. Lose any temporary files that were catching output from
@@ -6996,6 +7394,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,
@@ -7060,8 +7462,11 @@ else if (addr_defer != (address_item *)(+1))
         uschar *wmf_text;
         FILE *wmf = NULL;
         FILE *f = fdopen(fd, "wb");
+#ifdef EXPERIMENTAL_DSN
+       uschar boundaryStr[64];
+#endif
 
-        if (warn_message_file != NULL)
+        if (warn_message_file)
           {
           wmf = Ufopen(warn_message_file, "rb");
           if (wmf == NULL)
@@ -7074,21 +7479,39 @@ else if (addr_defer != (address_item *)(+1))
           string_sprintf("%d minutes", show_time/60):
           string_sprintf("%d hours", show_time/3600);
 
-        if (errors_reply_to != NULL)
+        if (errors_reply_to)
           fprintf(f, "Reply-To: %s\n", errors_reply_to);
         fprintf(f, "Auto-Submitted: auto-replied\n");
         moan_write_from(f);
         fprintf(f, "To: %s\n", recipients);
 
-        wmf_text = next_emf(wmf, US"header");
-        if (wmf_text != NULL)
+#ifdef EXPERIMENTAL_DSN
+        /* generated boundary string and output MIME-Headers */
+        snprintf(boundaryStr, sizeof(boundaryStr)-1,
+         TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
+
+        fprintf(f, "Content-Type: multipart/report;"
+           " report-type=delivery-status; boundary=%s\n"
+           "MIME-Version: 1.0\n",
+         boundaryStr);
+#endif
+
+        if ((wmf_text = next_emf(wmf, US"header")))
           fprintf(f, "%s\n", wmf_text);
         else
           fprintf(f, "Subject: Warning: message %s delayed %s\n\n",
             message_id, warnmsg_delay);
 
-        wmf_text = next_emf(wmf, US"intro");
-        if (wmf_text != NULL) fprintf(f, "%s", CS wmf_text); else
+#ifdef EXPERIMENTAL_DSN
+        /* output human readable part as text/plain section */
+        fprintf(f, "--%s\n"
+           "Content-type: text/plain; charset=us-ascii\n\n",
+         boundaryStr);
+#endif
+
+        if ((wmf_text = next_emf(wmf, US"intro")))
+         fprintf(f, "%s", CS wmf_text);
+       else
           {
           fprintf(f,
 "This message was created automatically by mail delivery software.\n");
@@ -7098,49 +7521,52 @@ else if (addr_defer != (address_item *)(+1))
 "A message that you sent has not yet been delivered to one or more of its\n"
 "recipients after more than ");
 
-          else fprintf(f,
+          else
+           fprintf(f,
 "A message sent by\n\n  <%s>\n\n"
 "has not yet been delivered to one or more of its recipients after more than \n",
-          sender_address);
+             sender_address);
 
-          fprintf(f, "%s on the queue on %s.\n\n", warnmsg_delay,
-            primary_hostname);
-          fprintf(f, "The message identifier is:     %s\n", message_id);
+          fprintf(f, "%s on the queue on %s.\n\n"
+             "The message identifier is:     %s\n",
+           warnmsg_delay, primary_hostname, message_id);
 
           for (h = header_list; h != NULL; h = h->next)
-            {
             if (strncmpic(h->text, US"Subject:", 8) == 0)
               fprintf(f, "The subject of the message is: %s", h->text + 9);
             else if (strncmpic(h->text, US"Date:", 5) == 0)
               fprintf(f, "The date of the message is:    %s", h->text + 6);
-            }
-          fprintf(f, "\n");
+          fputc('\n', f);
 
           fprintf(f, "The address%s to which the message has not yet been "
             "delivered %s:\n",
-            (addr_defer->next == NULL)? "" : "es",
-            (addr_defer->next == NULL)? "is": "are");
+            !addr_defer->next ? "" : "es",
+            !addr_defer->next ? "is": "are");
           }
 
         /* List the addresses, with error information if allowed */
 
-        fprintf(f, "\n");
-        while (addr_defer != NULL)
+#ifdef EXPERIMENTAL_DSN
+        /* store addr_defer for machine readable part */
+        address_item *addr_dsndefer = addr_defer;
+#endif
+        fputc('\n', f);
+        while (addr_defer)
           {
           address_item *addr = addr_defer;
           addr_defer = addr->next;
           if (print_address_information(addr, f, US"  ", US"\n    ", US""))
             print_address_error(addr, f, US"Delay reason: ");
-          fprintf(f, "\n");
+          fputc('\n', f);
           }
-        fprintf(f, "\n");
+        fputc('\n', f);
 
         /* Final text */
 
-        if (wmf != NULL)
+        if (wmf)
           {
-          wmf_text = next_emf(wmf, US"final");
-          if (wmf_text != NULL) fprintf(f, "%s", CS wmf_text);
+          if ((wmf_text = next_emf(wmf, US"final")))
+           fprintf(f, "%s", CS wmf_text);
           (void)fclose(wmf);
           }
         else
@@ -7152,6 +7578,58 @@ 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"
+           "Content-type: message/delivery-status\n\n"
+           "Reporting-MTA: dns; %s\n",
+         boundaryStr,
+         smtp_active_hostname);
+
+        if (dsn_envid)
+         {
+          /* must be decoded from xtext: see RFC 3461:6.3a */
+          uschar *xdec_envid;
+          if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0)
+            fprintf(f,"Original-Envelope-ID: %s\n", dsn_envid);
+          else
+            fprintf(f,"X-Original-Envelope-ID: error decoding xtext formated ENVID\n");
+          }
+        fputc('\n', f);
+
+        while (addr_dsndefer)
+          {
+          if (addr_dsndefer->dsn_orcpt)
+            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 && addr_dsndefer->host_used->name)
+            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"
+           "Content-type: text/rfc822-headers\n\n",
+         boundaryStr);
+
+        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--%s--\n", boundaryStr);
+
+        fflush(f);
+#endif /*EXPERIMENTAL_DSN*/
+
         /* Close and wait for child process to complete, without a timeout.
         If there's an error, don't update the count. */
 
@@ -7263,10 +7741,10 @@ if (remove_journal)
 
   /* Move the message off the spool if reqested */
 
-  #ifdef SUPPORT_MOVE_FROZEN_MESSAGES
+#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
   if (deliver_freeze && move_frozen_messages)
     (void)spool_move_message(id, message_subdir, US"", US"F");
-  #endif
+#endif
   }
 
 /* Closing the data file frees the lock; if the file has been unlinked it