Recast more internal string routines to use growable-strings
[exim.git] / src / src / deliver.c
index 0bd67d82345ce3d4387dd61cc785b9732fc7ca69..0b403bad281d589b5d02954d89fc743aa83a4a0f 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The main code for delivering a message. */
@@ -10,6 +10,7 @@
 
 #include "exim.h"
 #include "transports/smtp.h"
+#include <sys/uio.h>
 #include <assert.h>
 
 
@@ -79,6 +80,52 @@ static uschar *used_return_path = NULL;
 
 
 
+/*************************************************
+*          read as much as requested             *
+*************************************************/
+
+/* The syscall read(2) doesn't always returns as much as we want. For
+several reasons it might get less. (Not talking about signals, as syscalls
+are restartable). When reading from a network or pipe connection the sender
+might send in smaller chunks, with delays between these chunks. The read(2)
+may return such a chunk.
+
+The more the writer writes and the smaller the pipe between write and read is,
+the more we get the chance of reading leass than requested. (See bug 2130)
+
+This function read(2)s until we got all the data we *requested*.
+
+Note: This function may block. Use it only if you're sure about the
+amount of data you will get.
+
+Argument:
+  fd          the file descriptor to read from
+  buffer      pointer to a buffer of size len
+  len         the requested(!) amount of bytes
+
+Returns:      the amount of bytes read
+*/
+static ssize_t
+readn(int fd, void * buffer, size_t len)
+{
+  void * next = buffer;
+  void * end = buffer + len;
+
+  while (next < end)
+    {
+    ssize_t got = read(fd, next, end - next);
+
+    /* I'm not sure if there are signals that can interrupt us,
+    for now I assume the worst */
+    if (got == -1 && errno == EINTR) continue;
+    if (got <= 0) return next - buffer;
+    next += got;
+    }
+
+  return len;
+}
+
+
 /*************************************************
 *             Make a new address item            *
 *************************************************/
@@ -714,10 +761,9 @@ d_log_interface(gstring * g)
 if (LOGGING(incoming_interface) && LOGGING(outgoing_interface)
     && sending_ip_address)
   {
-  g = string_append(g, 2, US" I=[", sending_ip_address);
-  g = LOGGING(outgoing_port)
-    ? string_append(g, 2, US"]:", string_sprintf("%d", sending_port))
-    : string_catn(g, US"]", 1);
+  g = string_fmt_append(g, " I=[%s]", sending_ip_address);
+  if (LOGGING(outgoing_port))
+    g = string_fmt_append(g, "%d", sending_port);
   }
 return g;
 }
@@ -737,21 +783,21 @@ if (LOGGING(dnssec) && h->dnssec == DS_YES)
 g = string_append(g, 3, US" [", h->address, US"]");
 
 if (LOGGING(outgoing_port))
-  g = string_append(g, 2, US":", string_sprintf("%d", h->port));
+  g = string_fmt_append(g, ":%d", h->port);
 
 #ifdef SUPPORT_SOCKS
 if (LOGGING(proxy) && proxy_local_address)
   {
   g = string_append(g, 3, US" PRX=[", proxy_local_address, US"]");
   if (LOGGING(outgoing_port))
-    g = string_append(g, 2, US":", string_sprintf("%d", proxy_local_port));
+    g = string_fmt_append(g, ":%d", proxy_local_port);
   }
 #endif
 
 g = d_log_interface(g);
 
 if (testflag(addr, af_tcp_fastopen))
-  g = string_catn(g, US" TFO", 4);
+  g = string_catn(g, US" TFO*", testflag(addr, af_tcp_fastopen_data) ? 5 : 4);
 
 return g;
 }
@@ -770,7 +816,7 @@ if (LOGGING(tls_certificate_verified) && addr->cipher)
   s = string_append(s, 2, US" CV=",
     testflag(addr, af_cert_verified)
     ?
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
       testflag(addr, af_dane_verified)
     ? "dane"
     :
@@ -804,7 +850,7 @@ if (action)
   if (!(s = expand_string(action)) && *expand_string_message)
     log_write(0, LOG_MAIN|LOG_PANIC,
       "failed to expand event_action %s in %s: %s\n",
-      event, transport_name, expand_string_message);
+      event, transport_name ? transport_name : US"main", expand_string_message);
 
   event_name = event_data = NULL;
 
@@ -830,20 +876,33 @@ const uschar * save_host = deliver_host;
 const uschar * save_address = deliver_host_address;
 const int      save_port =   deliver_host_port;
 
-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;
 deliver_host =   addr->host_used ? addr->host_used->name : NULL;
 
-(void) event_raise(addr->transport->event_action, event,
-         addr->host_used
-          || Ustrcmp(addr->transport->driver_name, "smtp") == 0
-         || Ustrcmp(addr->transport->driver_name, "lmtp") == 0
-        ? addr->message : NULL);
+if (!addr->transport)
+  {
+  if (Ustrcmp(event, "msg:fail:delivery") == 0)
+    {
+     /* An address failed with no transport involved. This happens when
+     a filter was used which triggered a fail command (in such a case
+     a transport isn't needed).  Convert it to an internal fail event. */
+
+    (void) event_raise(event_action, US"msg:fail:internal", addr->message);
+    }
+  }
+else
+  {
+  transport_name = addr->transport->name;
+
+  (void) event_raise(addr->transport->event_action, event,
+           addr->host_used
+           || Ustrcmp(addr->transport->driver_name, "smtp") == 0
+           || Ustrcmp(addr->transport->driver_name, "lmtp") == 0
+           || Ustrcmp(addr->transport->driver_name, "autoreply") == 0
+          ? addr->message : NULL);
+  }
 
 deliver_host_port =    save_port;
 deliver_host_address = save_address;
@@ -983,10 +1042,8 @@ else
   and all parents are not being included, don't add on the top address. First
   of all, do a caseless comparison; if this succeeds, do a caseful comparison
   on the local parts. */
-  /*XXX dodgy coding.  the string at "cmp" might not be nul-terminated if
-  we had to extend the allocation! */
 
-  g->s[g->ptr] = '\0';
+  string_from_gstring(g);      /* ensure nul-terminated */
   if (  strcmpic(cmp, topaddr->address) == 0
      && Ustrncmp(cmp, topaddr->address, Ustrchr(cmp, '@') - cmp) == 0
      && !addr->onetime_parent
@@ -1042,7 +1099,7 @@ if ((diff->tv_usec -= then->tv_usec) < 0)
 
 
 
-static uschar *
+uschar *
 string_timediff(struct timeval * diff)
 {
 static uschar buf[sizeof("0.000s")];
@@ -1050,7 +1107,7 @@ static uschar buf[sizeof("0.000s")];
 if (diff->tv_sec >= 5 || !LOGGING(millisec))
   return readconf_printtime((int)diff->tv_sec);
 
-sprintf(CS buf, "%d.%03ds", (int)diff->tv_sec, (int)diff->tv_usec/1000);
+sprintf(CS buf, "%u.%03us", (uint)diff->tv_sec, (uint)diff->tv_usec/1000);
 return buf;
 }
 
@@ -1138,8 +1195,7 @@ if (addr->router)
 g = string_append(g, 2, US" T=", addr->transport->name);
 
 if (LOGGING(delivery_size))
-  g = string_append(g, 2, US" S=",
-    string_sprintf("%d", transport_count));
+  g = string_fmt_append(g, " S=%d", transport_count);
 
 /* Local delivery */
 
@@ -1189,6 +1245,16 @@ else
       }
     }
 
+  if (LOGGING(pipelining))
+    {
+    if (testflag(addr, af_pipelining))
+      g = string_catn(g, US" L", 2);
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+    if (testflag(addr, af_early_pipe))
+      g = string_catn(g, US"*", 1);
+#endif
+    }
+
 #ifndef DISABLE_PRDR
   if (testflag(addr, af_prdr_used))
     g = string_catn(g, US" PRDR", 5);
@@ -1277,13 +1343,12 @@ if (driver_name)
   {
   if (driver_kind[1] == 't' && addr->router)
     g = string_append(g, 2, US" R=", addr->router->name);
-  g = string_cat(g, string_sprintf(" %c=%s", toupper(driver_kind[1]), driver_name));
+  g = string_fmt_append(g, " %c=%s", toupper(driver_kind[1]), driver_name);
   }
 else if (driver_kind)
   g = string_append(g, 2, US" ", driver_kind);
 
-/*XXX need an s+s+p sprintf */
-g = string_cat(g, string_sprintf(" defer (%d)", addr->basic_errno));
+g = string_fmt_append(g, " defer (%d)", addr->basic_errno);
 
 if (addr->basic_errno > 0)
   g = string_append(g, 2, US": ",
@@ -1297,8 +1362,7 @@ if (addr->host_used)
   if (LOGGING(outgoing_port))
     {
     int port = addr->host_used->port;
-    g = string_append(g, 2,
-         US":", port == PORT_NONE ? US"25" : string_sprintf("%d", port));
+    g = string_fmt_append(g, ":%d", port == PORT_NONE ? 25 : port);
     }
   }
 
@@ -1310,7 +1374,7 @@ if (addr->message)
 /* Log the deferment in the message log, but don't clutter it
 up with retry-time defers after the first delivery attempt. */
 
-if (deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
+if (f.deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
   deliver_msglog("%s %s\n", now, g->s);
 
 /* Write the main log and reset the store.
@@ -1335,6 +1399,16 @@ failure_log(address_item * addr, uschar * driver_kind, uschar * now)
 void * reset_point;
 gstring * g = reset_point = string_get(256);
 
+#ifndef DISABLE_EVENT
+/* Message failures for which we will send a DSN get their event raised
+later so avoid doing it here. */
+
+if (  !addr->prop.ignore_error
+   && !(addr->dsn_flags & (rf_dsnflags & ~rf_notify_failure))
+   )
+  msg_event_raise(US"msg:fail:delivery", addr);
+#endif
+
 /* Build up the log line for the message and main logs */
 
 /* Create the address string for logging. Must not do this earlier, because
@@ -1383,10 +1457,6 @@ else
 
 log_write(0, LOG_MAIN, "** %s", g->s);
 
-#ifndef DISABLE_EVENT
-msg_event_raise(US"msg:fail:delivery", addr);
-#endif
-
 store_reset(reset_point);
 return;
 }
@@ -1430,7 +1500,7 @@ if (driver_type == EXIM_DTYPE_TRANSPORT)
     {
     driver_name = addr->transport->name;
     driver_kind = US" transport";
-    disable_logging = addr->transport->disable_logging;
+    f.disable_logging = addr->transport->disable_logging;
     }
   else driver_kind = US"transporting";
   }
@@ -1440,7 +1510,7 @@ else if (driver_type == EXIM_DTYPE_ROUTER)
     {
     driver_name = addr->router->name;
     driver_kind = US" router";
-    disable_logging = addr->router->disable_logging;
+    f.disable_logging = addr->router->disable_logging;
     }
   else driver_kind = US"routing";
   }
@@ -1508,7 +1578,7 @@ if (addr->return_file >= 0 && addr->return_filename)
           log_write(0, LOG_MAIN, "<%s>: %s transport output: %s",
             addr->address, tb->name, sp);
           }
-        (void)fclose(f);
+      (void)fclose(f);
       }
 
     /* Handle returning options, but only if there is an address to return
@@ -1574,7 +1644,7 @@ if (result == OK)
   tls_out.cipher = addr->cipher;
   tls_out.peerdn = addr->peerdn;
   tls_out.ocsp = addr->ocsp;
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
   tls_out.dane_verified = testflag(addr, af_dane_verified);
 # endif
 #endif
@@ -1587,7 +1657,7 @@ if (result == OK)
   tls_out.cipher = NULL;
   tls_out.peerdn = NULL;
   tls_out.ocsp = OCSP_NOT_REQ;
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
   tls_out.dane_verified = FALSE;
 # endif
 #endif
@@ -1614,7 +1684,7 @@ else if (result == DEFER || result == PANIC)
 
   if (addr->special_action == SPECIAL_FREEZE)
     {
-    deliver_freeze = TRUE;
+    f.deliver_freeze = TRUE;
     deliver_frozen_at = time(NULL);
     update_spool = TRUE;
     }
@@ -1622,7 +1692,7 @@ else if (result == DEFER || result == PANIC)
   /* If doing a 2-stage queue run, we skip writing to either the message
   log or the main log for SMTP defers. */
 
-  if (!queue_2stage || addr->basic_errno != 0)
+  if (!f.queue_2stage || addr->basic_errno != 0)
     deferral_log(addr, now, logflags, driver_name, driver_kind);
   }
 
@@ -1655,10 +1725,10 @@ else
     {
     frozen_info = addr->special_action == SPECIAL_FREEZE
       ? US""
-      : sender_local && !local_error_message
+      : f.sender_local && !f.local_error_message
       ? US" (message created with -f <>)"
       : US" (delivery error message)";
-    deliver_freeze = TRUE;
+    f.deliver_freeze = TRUE;
     deliver_frozen_at = time(NULL);
     update_spool = TRUE;
 
@@ -1683,7 +1753,7 @@ else
 
 /* Ensure logging is turned on again in all cases */
 
-disable_logging = FALSE;
+f.disable_logging = FALSE;
 }
 
 
@@ -1718,13 +1788,12 @@ addr->basic_errno = code;
 if (format)
   {
   va_list ap;
-  uschar buffer[512];
+  gstring * g;
+
   va_start(ap, format);
-  if (!string_vformat(buffer, sizeof(buffer), CS format, ap))
-    log_write(0, LOG_MAIN|LOG_PANIC_DIE,
-      "common_error expansion was longer than " SIZE_T_FMT, sizeof(buffer));
+  g = string_vformat(NULL, TRUE, CS format, ap);
   va_end(ap);
-  addr->message = string_copy(buffer);
+  addr->message = string_from_gstring(g);
   }
 
 for (addr2 = addr->next; addr2; addr2 = addr2->next)
@@ -2109,7 +2178,7 @@ if (tp->return_path)
   uschar *new_return_path = expand_string(tp->return_path);
   if (!new_return_path)
     {
-    if (!expand_string_forcedfail)
+    if (!f.expand_string_forcedfail)
       {
       common_error(TRUE, addr, ERRNO_EXPANDFAIL,
         US"Failed to expand return path \"%s\" in %s transport: %s",
@@ -2508,7 +2577,7 @@ if (!shadowing)
       /* In the test harness, wait just a bit to let the subprocess finish off
       any debug output etc first. */
 
-      if (running_in_test_harness) millisleep(300);
+      if (f.running_in_test_harness) millisleep(300);
 
       DEBUG(D_deliver) debug_printf("journalling %s", big_buffer);
       len = Ustrlen(big_buffer);
@@ -2668,7 +2737,7 @@ while (addr_local)
   struct timeval deliver_time;
   address_item *addr2, *addr3, *nextaddr;
   int logflags = LOG_MAIN;
-  int logchar = dont_deliver? '*' : '=';
+  int logchar = f.dont_deliver? '*' : '=';
   transport_instance *tp;
   uschar * serialize_key = NULL;
 
@@ -2686,7 +2755,7 @@ while (addr_local)
   if (!(tp = addr->transport))
     {
     logflags |= LOG_PANIC;
-    disable_logging = FALSE;  /* Jic */
+    f.disable_logging = FALSE;  /* Jic */
     addr->message = addr->router
       ? string_sprintf("No transport set by %s router", addr->router->name)
       : string_sprintf("No transport set by system filter");
@@ -2704,7 +2773,7 @@ while (addr_local)
 
   /* There are weird cases where logging is disabled */
 
-  disable_logging = tp->disable_logging;
+  f.disable_logging = tp->disable_logging;
 
   /* Check for batched addresses and possible amalgamation. Skip all the work
   if either batch_max <= 1 or there aren't any other addresses for local
@@ -2891,7 +2960,7 @@ while (addr_local)
             retry_record->expired);
           }
 
-        if (queue_running && !deliver_force)
+        if (f.queue_running && !f.deliver_force)
           {
           ok = (now - retry_record->time_stamp > retry_data_expire)
            || (now >= retry_record->next_try)
@@ -3248,8 +3317,8 @@ small items (less than PIPE_BUF, which seems to be at least 512 in any Unix and
 often bigger) so even if we are reading while the subprocess is still going, we
 should never have only a partial item in the buffer.
 
-hs12: This assumption is not true anymore, since we got quit large items (certificate
-information and such)
+hs12: This assumption is not true anymore, since we get quite large items (certificate
+information and such).
 
 Argument:
   poffset     the offset of the parlist item
@@ -3311,14 +3380,13 @@ while (!done)
   If we get less, we can assume the subprocess do be done and do not expect any further
   information from it. */
 
-  got = readn(fd, pipeheader, required);
-  if (got != required)
+  if ((got = readn(fd, pipeheader, required)) != required)
     {
-      msg = string_sprintf("got %d of %d bytes (pipeheader) "
-        "from transport process %d for transport %s",
-        got, PIPE_HEADER_SIZE, pid, addr->transport->driver_name);
-      done = TRUE;
-      break;
+    msg = string_sprintf("got " SSIZE_T_FMT " of %d bytes (pipeheader) "
+      "from transport process %d for transport %s",
+      got, PIPE_HEADER_SIZE, pid, addr->transport->driver_name);
+    done = TRUE;
+    break;
     }
 
   pipeheader[PIPE_HEADER_SIZE] = '\0';
@@ -3348,14 +3416,13 @@ while (!done)
   /* Same as above, the transport process will write the bytes announced
   in a timely manner, so we can just wait for the bytes, getting less than expected
   is considered a problem of the subprocess, we do not expect anything else from it. */
-  got = readn(fd, big_buffer, required);
-  if (got != required)
+  if ((got = readn(fd, big_buffer, required)) != required)
     {
-      msg = string_sprintf("got only %d of %d bytes (pipedata) "
-        "from transport process %d for transport %s",
-        got, required, pid, addr->transport->driver_name);
-      done = TRUE;
-      break;
+    msg = string_sprintf("got only " SSIZE_T_FMT " of " SIZE_T_FMT
+      " bytes (pipedata) from transport process %d for transport %s",
+      got, required, pid, addr->transport->driver_name);
+    done = TRUE;
+    break;
     }
 
   /* Handle each possible type of item, assuming the complete item is
@@ -3508,6 +3575,16 @@ while (!done)
       break;
 #endif
 
+    case 'L':
+      switch (*subid)
+       {
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+       case 2: setflag(addr, af_early_pipe);   /*FALLTHROUGH*/
+#endif
+       case 1: setflag(addr, af_pipelining); break;
+       }
+      break;
+
     case 'K':
       setflag(addr, af_chunking_used);
       break;
@@ -3515,6 +3592,7 @@ while (!done)
     case 'T':
       setflag(addr, af_tcp_fastopen_conn);
       if (*subid > '0') setflag(addr, af_tcp_fastopen);
+      if (*subid > '1') setflag(addr, af_tcp_fastopen_data);
       break;
 
     case 'D':
@@ -4208,7 +4286,7 @@ for (delivery_count = 0; addr_remote; delivery_count++)
 
   if (!(tp = addr->transport))
     {
-    disable_logging = FALSE;  /* Jic */
+    f.disable_logging = FALSE;  /* Jic */
     panicmsg = US"No transport set by router";
     goto panic_continue;
     }
@@ -4403,7 +4481,7 @@ for (delivery_count = 0; addr_remote; delivery_count++)
     uschar *new_return_path = expand_string(tp->return_path);
     if (new_return_path)
       return_path = new_return_path;
-    else if (!expand_string_forcedfail)
+    else if (!f.expand_string_forcedfail)
       {
       panicmsg = string_sprintf("Failed to expand return path \"%s\": %s",
        tp->return_path, expand_string_message);
@@ -4435,7 +4513,7 @@ for (delivery_count = 0; addr_remote; delivery_count++)
   treat it as if it is a continued connection (apart from the counter used
   for the log line mark). */
 
-  if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
+  if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
     {
     DEBUG(D_deliver)
       debug_printf("lazy-callout-close: have conn still open from verification\n");
@@ -4454,7 +4532,7 @@ for (delivery_count = 0; addr_remote; delivery_count++)
   we must check that the continue host is on the list. Otherwise, the
   host is set in the transport. */
 
-  continue_more = FALSE;           /* In case got set for the last lot */
+  f.continue_more = FALSE;           /* In case got set for the last lot */
   if (continue_transport)
     {
     BOOL ok = Ustrcmp(continue_transport, tp->name) == 0;
@@ -4529,12 +4607,12 @@ for (delivery_count = 0; addr_remote; delivery_count++)
     connected to is too hard to manage.  Perhaps we need a finer-grain
     interface to the transport. */
 
-    for (next = addr_remote; next && !continue_more; next = next->next)
+    for (next = addr_remote; next && !f.continue_more; next = next->next)
       {
       host_item *h;
       for (h = next->host_list; h; h = h->next)
         if (Ustrcmp(h->name, continue_hostname) == 0)
-          { continue_more = TRUE; break; }
+          { f.continue_more = TRUE; break; }
       }
     }
 
@@ -4613,18 +4691,16 @@ all pipes, so I do not see a reason to use non-blocking IO here
 
   search_tidyup();
 
-
   if ((pid = fork()) == 0)
     {
     int fd = pfd[pipe_write];
     host_item *h;
-    DEBUG(D_deliver) debug_selector |= D_pid;  // hs12
 
     /* Setting this global in the subprocess means we need never clear it */
     transport_name = tp->name;
 
     /* There are weird circumstances in which logging is disabled */
-    disable_logging = tp->disable_logging;
+    f.disable_logging = tp->disable_logging;
 
     /* Show pids on debug output if parallelism possible */
 
@@ -4639,7 +4715,7 @@ all pipes, so I do not see a reason to use non-blocking IO here
     predictable settings for each delivery process, so do something explicit
     here rather they rely on the fixed reset in the random number function. */
 
-    random_seed = running_in_test_harness ? 42 + 2*delivery_count : 0;
+    random_seed = f.running_in_test_harness ? 42 + 2*delivery_count : 0;
 
     /* Set close-on-exec on the pipe so that it doesn't get passed on to
     a new process that may be forked to do another delivery down the same
@@ -4742,7 +4818,7 @@ all pipes, so I do not see a reason to use non-blocking IO here
 
       /* The certificate verification status goes into the flags */
       if (tls_out.certificate_verified) setflag(addr, af_cert_verified);
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
       if (tls_out.dane_verified)        setflag(addr, af_dane_verified);
 #endif
 
@@ -4813,12 +4889,22 @@ all pipes, so I do not see a reason to use non-blocking IO here
        rmt_dlv_checked_write(fd, 'P', '0', NULL, 0);
 #endif
 
+      if (testflag(addr, af_pipelining))
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+       if (testflag(addr, af_early_pipe))
+         rmt_dlv_checked_write(fd, 'L', '2', NULL, 0);
+       else
+#endif
+         rmt_dlv_checked_write(fd, 'L', '1', NULL, 0);
+
       if (testflag(addr, af_chunking_used))
        rmt_dlv_checked_write(fd, 'K', '0', NULL, 0);
 
       if (testflag(addr, af_tcp_fastopen_conn))
        rmt_dlv_checked_write(fd, 'T',
-         testflag(addr, af_tcp_fastopen) ? '1' : '0', NULL, 0);
+         testflag(addr, af_tcp_fastopen) ? testflag(addr, af_tcp_fastopen_data)
+         ? '2' : '1' : '0',
+         NULL, 0);
 
       memcpy(big_buffer, &addr->dsn_aware, sizeof(addr->dsn_aware));
       rmt_dlv_checked_write(fd, 'D', '0', big_buffer, sizeof(addr->dsn_aware));
@@ -4942,12 +5028,13 @@ all pipes, so I do not see a reason to use non-blocking IO here
   release its TLS library context (if any) as responsibility was passed to
   the delivery child process. */
 
-  if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
+  if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
     {
 #ifdef SUPPORT_TLS
-    tls_close(FALSE, FALSE);
+    if (cutthrough.is_tls)
+      tls_close(cutthrough.cctx.tls_ctx, TLS_NO_SHUTDOWN);
 #endif
-    (void) close(cutthrough.fd);
+    (void) close(cutthrough.cctx.sock);
     release_cutthrough_connection(US"passed to transport proc");
     }
 
@@ -4989,7 +5076,7 @@ all pipes, so I do not see a reason to use non-blocking IO here
   newly created process get going before we create another process. This should
   ensure repeatability in the tests. We only need to wait a tad. */
 
-  else if (running_in_test_harness) millisleep(500);
+  else if (f.running_in_test_harness) millisleep(500);
 
   continue;
 
@@ -5512,8 +5599,9 @@ message size. This use of strcpy() is OK because the length id is checked when
 it is obtained from a command line (the -M or -q options), and otherwise it is
 known to be a valid message id. */
 
-Ustrcpy(message_id, id);
-deliver_force = forced;
+if (id != message_id)
+  Ustrcpy(message_id, id);
+f.deliver_force = forced;
 return_count = 0;
 message_size = 0;
 
@@ -5661,7 +5749,7 @@ Otherwise it might be needed again. */
 can happen, but in the default situation, unless forced, no delivery is
 attempted. */
 
-if (deliver_freeze)
+if (f.deliver_freeze)
   {
 #ifdef SUPPORT_MOVE_FROZEN_MESSAGES
   /* Moving to another directory removes the message from Exim's view. Other
@@ -5704,8 +5792,8 @@ if (deliver_freeze)
          || auto_thaw <= 0
          || now <= deliver_frozen_at + auto_thaw
           )
-       && (  !forced || !deliver_force_thaw
-         || !admin_user || continue_hostname
+       && (  !forced || !f.deliver_force_thaw
+         || !f.admin_user || continue_hostname
        )  )
       {
       (void)close(deliver_datafile);
@@ -5719,7 +5807,7 @@ if (deliver_freeze)
 
     if (forced)
       {
-      deliver_manual_thaw = TRUE;
+      f.deliver_manual_thaw = TRUE;
       log_write(0, LOG_MAIN, "Unfrozen by forced delivery");
       }
     else log_write(0, LOG_MAIN, "Unfrozen by auto-thaw");
@@ -5727,7 +5815,7 @@ if (deliver_freeze)
 
   /* We get here if any of the rules for unfreezing have triggered. */
 
-  deliver_freeze = FALSE;
+  f.deliver_freeze = FALSE;
   update_spool = TRUE;
   }
 
@@ -5802,8 +5890,8 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
     }
 
   return_path = sender_address;
-  enable_dollar_recipients = TRUE;   /* Permit $recipients in system filter */
-  system_filtering = TRUE;
+  f.enable_dollar_recipients = TRUE;   /* Permit $recipients in system filter */
+  f.system_filtering = TRUE;
 
   /* Any error in the filter file causes a delivery to be abandoned. */
 
@@ -5851,8 +5939,8 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
   /* Reset things. If the filter message is an empty string, which can happen
   for a filter "fail" or "freeze" command with no text, reset it to NULL. */
 
-  system_filtering = FALSE;
-  enable_dollar_recipients = FALSE;
+  f.system_filtering = FALSE;
+  f.enable_dollar_recipients = FALSE;
   if (filter_message && filter_message[0] == 0) filter_message = NULL;
 
   /* Save the values of the system filter variables so that user filters
@@ -5875,9 +5963,9 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
   unset "delivered", which is forced by the "freeze" command to make -bF
   work properly. */
 
-  else if (rc == FF_FREEZE && !deliver_manual_thaw)
+  else if (rc == FF_FREEZE && !f.deliver_manual_thaw)
     {
-    deliver_freeze = TRUE;
+    f.deliver_freeze = TRUE;
     deliver_frozen_at = time(NULL);
     process_recipients = RECIP_DEFER;
     frozen_info = string_sprintf(" by the system filter%s%s",
@@ -6107,26 +6195,26 @@ if (process_recipients != RECIP_IGNORE)
       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);
+       new->dsn_orcpt ? new->dsn_orcpt : US"", new->dsn_flags);
 
       switch (process_recipients)
         {
         /* RECIP_DEFER is set when a system filter freezes a message. */
 
         case RECIP_DEFER:
-        new->next = addr_defer;
-        addr_defer = new;
-        break;
+         new->next = addr_defer;
+         addr_defer = new;
+         break;
 
 
         /* RECIP_FAIL_FILTER is set when a system filter has obeyed a "fail"
         command. */
 
         case RECIP_FAIL_FILTER:
-        new->message =
-          filter_message ? filter_message : US"delivery cancelled";
-        setflag(new, af_pass_message);
-        goto RECIP_QUEUE_FAILED;   /* below */
+         new->message =
+           filter_message ? filter_message : US"delivery cancelled";
+         setflag(new, af_pass_message);
+         goto RECIP_QUEUE_FAILED;   /* below */
 
 
         /* RECIP_FAIL_TIMEOUT is set when a message is frozen, but is older
@@ -6136,15 +6224,15 @@ if (process_recipients != RECIP_IGNORE)
         been logged. */
 
         case RECIP_FAIL_TIMEOUT:
-        new->message  = US"delivery cancelled; message timed out";
-        goto RECIP_QUEUE_FAILED;   /* below */
+         new->message  = US"delivery cancelled; message timed out";
+         goto RECIP_QUEUE_FAILED;   /* below */
 
 
         /* RECIP_FAIL is set when -Mg has been used. */
 
         case RECIP_FAIL:
-        new->message  = US"delivery cancelled by administrator";
-        /* Fall through */
+         new->message  = US"delivery cancelled by administrator";
+         /* Fall through */
 
         /* Common code for the failure cases above. If this is not a bounce
         message, put the address on the failed list so that it is used to
@@ -6152,11 +6240,11 @@ if (process_recipients != RECIP_IGNORE)
         The incident has already been logged. */
 
         RECIP_QUEUE_FAILED:
-        if (sender_address[0] != 0)
-          {
-          new->next = addr_failed;
-          addr_failed = new;
-          }
+         if (sender_address[0])
+           {
+           new->next = addr_failed;
+           addr_failed = new;
+           }
         break;
 
 
@@ -6165,17 +6253,17 @@ if (process_recipients != RECIP_IGNORE)
         is a bounce message, it will get frozen. */
 
         case RECIP_FAIL_LOOP:
-        new->message = US"Too many \"Received\" headers - suspected mail loop";
-        post_process_one(new, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
-        break;
+         new->message = US"Too many \"Received\" headers - suspected mail loop";
+         post_process_one(new, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+         break;
 
 
         /* Value should be RECIP_ACCEPT; take this as the safe default. */
 
         default:
-        if (!addr_new) addr_new = new; else addr_last->next = new;
-        addr_last = new;
-        break;
+         if (!addr_new) addr_new = new; else addr_last->next = new;
+         addr_last = new;
+         break;
         }
 
 #ifndef DISABLE_EVENT
@@ -6183,17 +6271,23 @@ if (process_recipients != RECIP_IGNORE)
        {
        uschar * save_local =  deliver_localpart;
        const uschar * save_domain = deliver_domain;
+       uschar * addr = new->address, * errmsg = NULL;
+       int start, end, dom;
 
-       deliver_localpart = expand_string(
-                     string_sprintf("${local_part:%s}", new->address));
-       deliver_domain =    expand_string(
-                     string_sprintf("${domain:%s}", new->address));
+       if (!parse_extract_address(addr, &errmsg, &start, &end, &dom, TRUE))
+         log_write(0, LOG_MAIN|LOG_PANIC,
+                "failed to parse address '%.100s': %s\n", addr, errmsg);
+       else
+         {
+         deliver_localpart =
+           string_copyn(addr+start, dom ? (dom-1) - start : end - start);
+         deliver_domain = dom ? CUS string_copyn(addr+dom, end - dom) : CUS"";
 
-       (void) event_raise(event_action,
-                     US"msg:fail:internal", new->message);
+         event_raise(event_action, US"msg:fail:internal", new->message);
 
-       deliver_localpart = save_local;
-       deliver_domain =    save_domain;
+         deliver_localpart = save_local;
+         deliver_domain = save_domain;
+         }
        }
 #endif
       }
@@ -6252,7 +6346,7 @@ deliver_out_buffer = store_malloc(DELIVER_OUT_BUFFER_SIZE);
  . If new addresses have been generated by the routers, da capo.
 */
 
-header_rewritten = FALSE;          /* No headers rewritten yet */
+f.header_rewritten = FALSE;          /* No headers rewritten yet */
 while (addr_new)           /* Loop until all addresses dealt with */
   {
   address_item *addr, *parent;
@@ -6598,7 +6692,7 @@ while (addr_new)           /* Loop until all addresses dealt with */
     which keep the retry record fresh, which can lead to us perpetually
     deferring messages. */
 
-    else if (  (  queue_running && !deliver_force
+    else if (  (  f.queue_running && !f.deliver_force
               || continue_hostname
               )
             && (  (  domain_retry_record
@@ -6642,7 +6736,7 @@ while (addr_new)           /* Loop until all addresses dealt with */
   those domains. During queue runs, queue_domains is forced to be unset.
   Optimize by skipping this pass through the addresses if nothing is set. */
 
-  if (!deliver_force && queue_domains)
+  if (!f.deliver_force && queue_domains)
     {
     address_item *okaddr = NULL;
     while (addr_route)
@@ -6936,14 +7030,14 @@ remember them for all subsequent deliveries. This can be delayed till later if
 there is only address to be delivered - if it succeeds the spool write need not
 happen. */
 
-if (  header_rewritten
+if (  f.header_rewritten
    && (  addr_local && (addr_local->next || addr_remote)
       || addr_remote && addr_remote->next
    )  )
   {
   /* Panic-dies on error */
   (void)spool_write_header(message_id, SW_DELIVERING, NULL);
-  header_rewritten = FALSE;
+  f.header_rewritten = FALSE;
   }
 
 
@@ -7023,13 +7117,13 @@ if (addr_local)
   DEBUG(D_deliver|D_transport)
     debug_printf(">>>>>>>>>>>>>>>> Local deliveries >>>>>>>>>>>>>>>>\n");
   do_local_deliveries();
-  disable_logging = FALSE;
+  f.disable_logging = FALSE;
   }
 
 /* If queue_run_local is set, we do not want to attempt any remote deliveries,
 so just queue them all. */
 
-if (queue_run_local)
+if (f.queue_run_local)
   while (addr_remote)
     {
     address_item *addr = addr_remote;
@@ -7081,7 +7175,7 @@ if (addr_remote)
     if (remote_sort_domains) sort_remote_deliveries();
     do_remote_deliveries(TRUE);
     }
-  disable_logging = FALSE;
+  f.disable_logging = FALSE;
   }
 
 
@@ -7160,7 +7254,7 @@ retry cutoff time has expired for all alternative destinations. Bypass the
 updating of the database if the -N flag is set, which is a debugging thing that
 prevents actual delivery. */
 
-else if (!dont_deliver)
+else if (!f.dont_deliver)
   retry_update(&addr_defer, &addr_failed, &addr_succeed);
 
 /* Send DSN for successful messages if requested */
@@ -7176,11 +7270,12 @@ for (addr_dsntmp = addr_succeed; addr_dsntmp; addr_dsntmp = addr_dsntmp->next)
       "DSN: envid: %s  ret: %d\n"
       "DSN: Final recipient: %s\n"
       "DSN: Remote SMTP server supports DSN: %d\n",
-      addr_dsntmp->router->name,
+      addr_dsntmp->router ? addr_dsntmp->router->name : US"(unknown)",
       addr_dsntmp->address,
       sender_address,
-      addr_dsntmp->dsn_orcpt, addr_dsntmp->dsn_flags,
-      dsn_envid, dsn_ret,
+      addr_dsntmp->dsn_orcpt ? addr_dsntmp->dsn_orcpt : US"NULL",
+      addr_dsntmp->dsn_flags,
+      dsn_envid ? dsn_envid : US"NULL", dsn_ret,
       addr_dsntmp->address,
       addr_dsntmp->dsn_aware
       );
@@ -7190,7 +7285,6 @@ for (addr_dsntmp = addr_succeed; addr_dsntmp; addr_dsntmp = addr_dsntmp->next)
   if (  (  addr_dsntmp->dsn_aware != dsn_support_yes
        || addr_dsntmp->dsn_flags & rf_dsnlasthop
         )
-     && addr_dsntmp->dsn_flags & rf_dsnflags
      && addr_dsntmp->dsn_flags & rf_notify_success
      )
     {
@@ -7224,7 +7318,7 @@ if (addr_senddsn)
     }
   else  /* Creation of child succeeded */
     {
-    FILE *f = fdopen(fd, "wb");
+    FILE * f = fdopen(fd, "wb");
     /* header only as required by RFC. only failure DSN needs to honor RET=FULL */
     uschar * bound;
     transport_ctx tctx = {{0}};
@@ -7257,11 +7351,9 @@ if (addr_senddsn)
         addr_dsntmp = addr_dsntmp->next)
       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->dsn_flags & rf_dsnlasthop ? "via non DSN router"
+       : addr_dsntmp->dsn_aware == dsn_support_no ? "to non-DSN-aware mailer"
+       : "via non \"Remote SMTP\" router"
        );
 
     fprintf(f, "--%s\n"
@@ -7296,7 +7388,7 @@ if (addr_senddsn)
          addr_dsntmp->host_used->name);
       else
        fprintf(f, "Diagnostic-Code: X-Exim; relayed via non %s router\n\n",
-         (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1 ? "DSN" : "SMTP");
+         addr_dsntmp->dsn_flags & rf_dsnlasthop ? "DSN" : "SMTP");
       }
 
     fprintf(f, "--%s\nContent-type: text/rfc822-headers\n\n", bound);
@@ -7307,8 +7399,10 @@ if (addr_senddsn)
 
     /* Write the original email out */
 
-    tctx.u.fd = fileno(f);
+    tctx.u.fd = fd;
     tctx.options = topt_add_return_path | topt_no_body;
+    /*XXX hmm, retval ignored.
+    Could error for any number of reasons, and they are not handled. */
     transport_write_message(&tctx, 0);
     fflush(f);
 
@@ -7339,9 +7433,9 @@ while (addr_failed)
   /* There are weird cases when logging is disabled in the transport. However,
   there may not be a transport (address failed by a router). */
 
-  disable_logging = FALSE;
+  f.disable_logging = FALSE;
   if (addr_failed->transport)
-    disable_logging = addr_failed->transport->disable_logging;
+    f.disable_logging = addr_failed->transport->disable_logging;
 
   DEBUG(D_deliver)
     debug_printf("processing failed address %s\n", addr_failed->address);
@@ -7377,14 +7471,16 @@ while (addr_failed)
   mark the recipient done. */
 
   if (  addr_failed->prop.ignore_error
-     || (  addr_failed->dsn_flags & rf_dsnflags
-        && (addr_failed->dsn_flags & rf_notify_failure) != rf_notify_failure
-     )  )
+     || addr_failed->dsn_flags & (rf_dsnflags & ~rf_notify_failure)
+     )
     {
     addr = addr_failed;
     addr_failed = addr->next;
     if (addr->return_filename) Uunlink(addr->return_filename);
 
+#ifndef DISABLE_EVENT
+    msg_event_raise(US"msg:fail:delivery", addr);
+#endif
     log_write(0, LOG_MAIN, "%s%s%s%s: error ignored",
       addr->address,
       !addr->parent ? US"" : US" <",
@@ -7423,8 +7519,8 @@ while (addr_failed)
       int filecount = 0;
       int rcount = 0;
       uschar *bcc, *emf_text;
-      FILE *f = fdopen(fd, "wb");
-      FILE *emf = NULL;
+      FILE * fp = fdopen(fd, "wb");
+      FILE * emf = NULL;
       BOOL to_sender = strcmpic(sender_address, bounce_recipient) == 0;
       int max = (bounce_return_size_limit/DELIVER_IN_BUFFER_SIZE + 1) *
         DELIVER_IN_BUFFER_SIZE;
@@ -7462,10 +7558,10 @@ while (addr_failed)
         if (testflag(addr, af_hide_child)) continue;
         if (rcount >= 50)
           {
-          fprintf(f, "\n");
+          fprintf(fp, "\n");
           rcount = 0;
           }
-        fprintf(f, "%s%s",
+        fprintf(fp, "%s%s",
           rcount++ == 0
          ? "X-Failed-Recipients: "
          : ",\n  ",
@@ -7473,20 +7569,20 @@ while (addr_failed)
          ? string_printing(addr->parent->address)
          : string_printing(addr->address));
         }
-      if (rcount > 0) fprintf(f, "\n");
+      if (rcount > 0) fprintf(fp, "\n");
 
       /* Output the standard headers */
 
       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", bounce_recipient);
+        fprintf(fp, "Reply-To: %s\n", errors_reply_to);
+      fprintf(fp, "Auto-Submitted: auto-replied\n");
+      moan_write_from(fp);
+      fprintf(fp, "To: %s\n", bounce_recipient);
 
       /* generate boundary string and output MIME-Headers */
       bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
 
-      fprintf(f, "Content-Type: multipart/report;"
+      fprintf(fp, "Content-Type: multipart/report;"
            " report-type=delivery-status; boundary=%s\n"
          "MIME-Version: 1.0\n",
        bound);
@@ -7502,46 +7598,46 @@ while (addr_failed)
       /* Quietly copy to configured additional addresses if required. */
 
       if ((bcc = moan_check_errorcopy(bounce_recipient)))
-       fprintf(f, "Bcc: %s\n", bcc);
+       fprintf(fp, "Bcc: %s\n", bcc);
 
       /* The texts for the message can be read from a template file; if there
       isn't one, or if it is too short, built-in texts are used. The first
       emf text is a Subject: and any other headers. */
 
       if ((emf_text = next_emf(emf, US"header")))
-       fprintf(f, "%s\n", emf_text);
+       fprintf(fp, "%s\n", emf_text);
       else
-        fprintf(f, "Subject: Mail delivery failed%s\n\n",
+        fprintf(fp, "Subject: Mail delivery failed%s\n\n",
           to_sender? ": returning message to sender" : "");
 
       /* output human readable part as text/plain section */
-      fprintf(f, "--%s\n"
+      fprintf(fp, "--%s\n"
          "Content-type: text/plain; charset=us-ascii\n\n",
        bound);
 
       if ((emf_text = next_emf(emf, US"intro")))
-       fprintf(f, "%s", CS emf_text);
+       fprintf(fp, "%s", CS emf_text);
       else
         {
-        fprintf(f,
+        fprintf(fp,
 /* This message has been reworded several times. It seems to be confusing to
 somebody, however it is worded. I have retreated to the original, simple
 wording. */
 "This message was created automatically by mail delivery software.\n");
 
         if (bounce_message_text)
-         fprintf(f, "%s", CS bounce_message_text);
+         fprintf(fp, "%s", CS bounce_message_text);
         if (to_sender)
-          fprintf(f,
+          fprintf(fp,
 "\nA message that you sent could not be delivered to one or more of its\n"
 "recipients. This is a permanent error. The following address(es) failed:\n");
         else
-          fprintf(f,
+          fprintf(fp,
 "\nA message sent by\n\n  <%s>\n\n"
 "could not be delivered to one or more of its recipients. The following\n"
 "address(es) failed:\n", sender_address);
         }
-      fputc('\n', f);
+      fputc('\n', fp);
 
       /* Process the addresses, leaving them on the msgchain if they have a
       file name for a return message. (There has already been a check in
@@ -7552,12 +7648,12 @@ wording. */
       paddr = &msgchain;
       for (addr = msgchain; addr; addr = *paddr)
         {
-        if (print_address_information(addr, f, US"  ", US"\n    ", US""))
-          print_address_error(addr, f, US"");
+        if (print_address_information(addr, fp, US"  ", US"\n    ", US""))
+          print_address_error(addr, fp, US"");
 
         /* End the final line for the address */
 
-        fputc('\n', f);
+        fputc('\n', fp);
 
         /* Leave on msgchain if there's a return file. */
 
@@ -7578,7 +7674,7 @@ wording. */
           }
         }
 
-      fputc('\n', f);
+      fputc('\n', fp);
 
       /* Get the next text, whether we need it or not, so as to be
       positioned for the one after. */
@@ -7597,9 +7693,9 @@ wording. */
         address_item *nextaddr;
 
         if (emf_text)
-         fprintf(f, "%s", CS emf_text);
+         fprintf(fp, "%s", CS emf_text);
        else
-          fprintf(f,
+          fprintf(fp,
             "The following text was generated during the delivery "
             "attempt%s:\n", (filecount > 1)? "s" : "");
 
@@ -7610,24 +7706,24 @@ wording. */
 
           /* List all the addresses that relate to this file */
 
-         fputc('\n', f);
+         fputc('\n', fp);
           while(addr)                   /* Insurance */
             {
-            print_address_information(addr, f, US"------ ",  US"\n       ",
+            print_address_information(addr, fp, US"------ ",  US"\n       ",
               US" ------\n");
             if (addr->return_filename) break;
             addr = addr->next;
             }
-         fputc('\n', f);
+         fputc('\n', fp);
 
           /* Now copy the file */
 
           if (!(fm = Ufopen(addr->return_filename, "rb")))
-            fprintf(f, "    +++ Exim error... failed to open text file: %s\n",
+            fprintf(fp, "    +++ Exim error... failed to open text file: %s\n",
               strerror(errno));
           else
             {
-            while ((ch = fgetc(fm)) != EOF) fputc(ch, f);
+            while ((ch = fgetc(fm)) != EOF) fputc(ch, fp);
             (void)fclose(fm);
             }
           Uunlink(addr->return_filename);
@@ -7639,19 +7735,19 @@ wording. */
           addr->next = handled_addr;
           handled_addr = topaddr;
           }
-       fputc('\n', f);
+       fputc('\n', fp);
         }
 
       /* output machine readable part */
 #ifdef SUPPORT_I18N
       if (message_smtputf8)
-       fprintf(f, "--%s\n"
+       fprintf(fp, "--%s\n"
            "Content-type: message/global-delivery-status\n\n"
            "Reporting-MTA: dns; %s\n",
          bound, smtp_active_hostname);
       else
 #endif
-       fprintf(f, "--%s\n"
+       fprintf(fp, "--%s\n"
            "Content-type: message/delivery-status\n\n"
            "Reporting-MTA: dns; %s\n",
          bound, smtp_active_hostname);
@@ -7661,40 +7757,42 @@ wording. */
         /* 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);
+          fprintf(fp, "Original-Envelope-ID: %s\n", dsn_envid);
         else
-          fprintf(f, "X-Original-Envelope-ID: error decoding xtext formatted ENVID\n");
+          fprintf(fp, "X-Original-Envelope-ID: error decoding xtext formatted ENVID\n");
         }
-      fputc('\n', f);
+      fputc('\n', fp);
 
       for (addr = handled_addr; addr; addr = addr->next)
         {
        host_item * hu;
-        fprintf(f, "Action: failed\n"
+        fprintf(fp, "Action: failed\n"
            "Final-Recipient: rfc822;%s\n"
            "Status: 5.0.0\n",
            addr->address);
         if ((hu = addr->host_used) && hu->name)
          {
-         const uschar * s;
-         fprintf(f, "Remote-MTA: dns; %s\n", hu->name);
+         fprintf(fp, "Remote-MTA: dns; %s\n", hu->name);
 #ifdef EXPERIMENTAL_DSN_INFO
+         {
+         const uschar * s;
          if (hu->address)
            {
            uschar * p = hu->port == 25
              ? US"" : string_sprintf(":%d", hu->port);
-           fprintf(f, "Remote-MTA: X-ip; [%s]%s\n", hu->address, p);
+           fprintf(fp, "Remote-MTA: X-ip; [%s]%s\n", hu->address, p);
            }
          if ((s = addr->smtp_greeting) && *s)
-           fprintf(f, "X-Remote-MTA-smtp-greeting: X-str; %s\n", s);
+           fprintf(fp, "X-Remote-MTA-smtp-greeting: X-str; %s\n", s);
          if ((s = addr->helo_response) && *s)
-           fprintf(f, "X-Remote-MTA-helo-response: X-str; %s\n", s);
+           fprintf(fp, "X-Remote-MTA-helo-response: X-str; %s\n", s);
          if ((s = addr->message) && *s)
-           fprintf(f, "X-Exim-Diagnostic: X-str; %s\n", s);
+           fprintf(fp, "X-Exim-Diagnostic: X-str; %s\n", s);
+         }
 #endif
-         print_dsn_diagnostic_code(addr, f);
+         print_dsn_diagnostic_code(addr, fp);
          }
-       fputc('\n', f);
+       fputc('\n', fp);
         }
 
       /* Now copy the message, trying to give an intelligible comment if
@@ -7715,7 +7813,7 @@ wording. */
          bounce_return_size_limit is always honored.
       */
 
-      fprintf(f, "--%s\n", bound);
+      fprintf(fp, "--%s\n", bound);
 
       dsnlimitmsg = US"X-Exim-DSN-Information: Due to administrative limits only headers are returned";
       dsnnotifyhdr = NULL;
@@ -7753,44 +7851,45 @@ wording. */
       if (message_smtputf8)
        fputs(topt & topt_no_body ? "Content-type: message/global-headers\n\n"
                                  : "Content-type: message/global\n\n",
-             f);
+             fp);
       else
 #endif
        fputs(topt & topt_no_body ? "Content-type: text/rfc822-headers\n\n"
                                  : "Content-type: message/rfc822\n\n",
-             f);
+             fp);
 
-      fflush(f);
+      fflush(fp);
       transport_filter_argv = NULL;   /* Just in case */
       return_path = sender_address;   /* In case not previously set */
        {                             /* Dummy transport for headers add */
        transport_ctx tctx = {{0}};
        transport_instance tb = {0};
 
-       tctx.u.fd = fileno(f);
+       tctx.u.fd = fileno(fp);
        tctx.tblock = &tb;
        tctx.options = topt;
        tb.add_headers = dsnnotifyhdr;
 
+       /*XXX no checking for failure!  buggy! */
        transport_write_message(&tctx, 0);
        }
-      fflush(f);
+      fflush(fp);
 
       /* we never add the final text. close the file */
       if (emf)
         (void)fclose(emf);
 
-      fprintf(f, "\n--%s--\n", bound);
+      fprintf(fp, "\n--%s--\n", bound);
 
       /* Close the file, which should send an EOF to the child process
       that is receiving the message. Wait for it to finish. */
 
-      (void)fclose(f);
+      (void)fclose(fp);
       rc = child_close(pid, 0);     /* Waits for child to close, no timeout */
 
       /* In the test harness, let the child do it's thing first. */
 
-      if (running_in_test_harness) millisleep(500);
+      if (f.running_in_test_harness) millisleep(500);
 
       /* If the process failed, there was some disaster in setting up the
       error message. Unless the message is very old, ensure that addr_defer
@@ -7807,7 +7906,7 @@ wording. */
         if (now - received_time.tv_sec < retry_maximum_timeout && !addr_defer)
           {
           addr_defer = (address_item *)(+1);
-          deliver_freeze = TRUE;
+          f.deliver_freeze = TRUE;
           deliver_frozen_at = time(NULL);
           /* Panic-dies on error */
           (void)spool_write_header(message_id, SW_DELIVERING, NULL);
@@ -7836,7 +7935,7 @@ wording. */
     }
   }
 
-disable_logging = FALSE;  /* In case left set */
+f.disable_logging = FALSE;  /* In case left set */
 
 /* Come here from the mua_wrapper case if routing goes wrong */
 
@@ -7889,13 +7988,12 @@ if (!addr_defer)
   /* Log the end of this message, with queue time if requested. */
 
   if (LOGGING(queue_time_overall))
-    log_write(0, LOG_MAIN, "Completed QT=%s",
-      string_timesince(&received_time));
+    log_write(0, LOG_MAIN, "Completed QT=%s", string_timesince(&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;
+  f.deliver_freeze = FALSE;
 
 #ifndef DISABLE_EVENT
   (void) event_raise(event_action, US"msg:complete", NULL);
@@ -8013,10 +8111,10 @@ else if (addr_defer != (address_item *)(+1))
   is not sent. Another attempt will be made at the next delivery attempt (if
   it also defers). */
 
-  if (  !queue_2stage
+  if (  !f.queue_2stage
      && delivery_attempted
-     && (  ((addr_defer->dsn_flags & rf_dsnflags) == 0)
-        || (addr_defer->dsn_flags & rf_notify_delay) == rf_notify_delay
+     && (  !(addr_defer->dsn_flags & rf_dsnflags)
+        || addr_defer->dsn_flags & rf_notify_delay
        )
      && delay_warning[1] > 0
      && sender_address[0] != 0
@@ -8035,7 +8133,7 @@ else if (addr_defer != (address_item *)(+1))
     time off the list. In queue runs, the list pointer gets updated in the
     calling process. */
 
-    if (running_in_test_harness && fudged_queue_times[0] != 0)
+    if (f.running_in_test_harness && fudged_queue_times[0] != 0)
       {
       int qt = readconf_readtime(fudged_queue_times, '/', FALSE);
       if (qt >= 0)
@@ -8239,6 +8337,7 @@ else if (addr_defer != (address_item *)(+1))
         return_path = sender_address;   /* In case not previously set */
 
         /* Write the original email out */
+       /*XXX no checking for failure!  buggy! */
         transport_write_message(&tctx, 0);
         fflush(f);
 
@@ -8266,9 +8365,9 @@ else if (addr_defer != (address_item *)(+1))
   /* If this was a first delivery attempt, unset the first time flag, and
   ensure that the spool gets updated. */
 
-  if (deliver_firsttime)
+  if (f.deliver_firsttime)
     {
-    deliver_firsttime = FALSE;
+    f.deliver_firsttime = FALSE;
     update_spool = TRUE;
     }
 
@@ -8279,9 +8378,9 @@ else if (addr_defer != (address_item *)(+1))
   For the "tell" message, we turn \n back into newline. Also, insert a newline
   near the start instead of the ": " string. */
 
-  if (deliver_freeze)
+  if (f.deliver_freeze)
     {
-    if (freeze_tell && freeze_tell[0] != 0 && !local_error_message)
+    if (freeze_tell && freeze_tell[0] != 0 && !f.local_error_message)
       {
       uschar *s = string_copy(frozen_info);
       uschar *ss = Ustrstr(s, " by the system filter: ");
@@ -8322,9 +8421,9 @@ else if (addr_defer != (address_item *)(+1))
 
   DEBUG(D_deliver)
     debug_printf("delivery deferred: update_spool=%d header_rewritten=%d\n",
-      update_spool, header_rewritten);
+      update_spool, f.header_rewritten);
 
-  if (update_spool || header_rewritten)
+  if (update_spool || f.header_rewritten)
     /* Panic-dies on error */
     (void)spool_write_header(message_id, SW_DELIVERING, NULL);
   }
@@ -8359,7 +8458,7 @@ if (remove_journal)
   /* Move the message off the spool if requested */
 
 #ifdef SUPPORT_MOVE_FROZEN_MESSAGES
-  if (deliver_freeze && move_frozen_messages)
+  if (f.deliver_freeze && move_frozen_messages)
     (void)spool_move_message(id, message_subdir, US"", US"F");
 #endif
   }
@@ -8391,7 +8490,7 @@ deliver_init(void)
 #ifdef EXIM_TFO_PROBE
 tfo_probe();
 #else
-tcp_fastopen_ok = TRUE;
+f.tcp_fastopen_ok = TRUE;
 #endif
 
 
@@ -8402,12 +8501,16 @@ if (!regex_SIZE) regex_SIZE =
   regex_must_compile(US"\\n250[\\s\\-]SIZE(\\s|\\n|$)", FALSE, TRUE);
 
 if (!regex_AUTH) regex_AUTH =
-  regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)",
-    FALSE, TRUE);
+  regex_must_compile(AUTHS_REGEX, FALSE, TRUE);
 
 #ifdef SUPPORT_TLS
 if (!regex_STARTTLS) regex_STARTTLS =
   regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
+
+# ifdef EXPERIMENTAL_REQUIRETLS
+if (!regex_REQUIRETLS) regex_REQUIRETLS =
+  regex_must_compile(US"\\n250[\\s\\-]REQUIRETLS(\\s|\\n|$)", FALSE, TRUE);
+# endif
 #endif
 
 if (!regex_CHUNKING) regex_CHUNKING =
@@ -8428,6 +8531,11 @@ if (!regex_DSN) regex_DSN  =
 
 if (!regex_IGNOREQUOTA) regex_IGNOREQUOTA =
   regex_must_compile(US"\\n250[\\s\\-]IGNOREQUOTA(\\s|\\n|$)", FALSE, TRUE);
+
+#ifdef EXPERIMENTAL_PIPE_CONNECT
+if (!regex_EARLY_PIPE) regex_EARLY_PIPE =
+  regex_must_compile(US"\\n250[\\s\\-]" EARLY_PIPE_FEATURE_NAME "(\\s|\\n|$)", FALSE, TRUE);
+#endif
 }
 
 
@@ -8437,17 +8545,17 @@ deliver_get_sender_address (uschar * id)
 int rc;
 uschar * new_sender_address,
        * save_sender_address;
-BOOL save_qr = queue_running;
+BOOL save_qr = f.queue_running;
 uschar * spoolname;
 
 /* make spool_open_datafile non-noisy on fail */
 
-queue_running = TRUE;
+f.queue_running = TRUE;
 
 /* Side effect: message_subdir is set for the (possibly split) spool directory */
 
 deliver_datafile = spool_open_datafile(id);
-queue_running = save_qr;
+f.queue_running = save_qr;
 if (deliver_datafile < 0)
   return NULL;
 
@@ -8483,9 +8591,9 @@ delivery_re_exec(int exec_type)
 {
 uschar * where;
 
-if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
+if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
   {
-  int pfd[2], channel_fd = cutthrough.fd, pid;
+  int channel_fd = cutthrough.cctx.sock;
 
   smtp_peer_options = cutthrough.peer_options;
   continue_sequence = 0;
@@ -8493,6 +8601,8 @@ if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
 #ifdef SUPPORT_TLS
   if (cutthrough.is_tls)
     {
+    int pfd[2], pid;
+
     smtp_peer_options |= OPTION_TLS;
     sending_ip_address = cutthrough.snd_ip;
     sending_port = cutthrough.snd_port;
@@ -8507,13 +8617,13 @@ if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
 
     else if (pid == 0)         /* child: fork again to totally disconnect */
       {
-      close(pfd[1]);
-      if ((pid = fork()))
-       _exit(pid ? EXIT_FAILURE : EXIT_SUCCESS);
-      smtp_proxy_tls(big_buffer, big_buffer_size, pfd[0], 5*60);
-      exim_exit(0);
+      if (f.running_in_test_harness) millisleep(100); /* let parent debug out */
+      /* does not return */
+      smtp_proxy_tls(cutthrough.cctx.tls_ctx, big_buffer, big_buffer_size,
+                     pfd, 5*60);
       }
 
+    DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid);
     close(pfd[0]);
     waitpid(pid, NULL, 0);
     (void) close(channel_fd);  /* release the client socket */
@@ -8531,6 +8641,7 @@ else
   }
 return;                /* compiler quietening; control does not reach here. */
 
+#ifdef SUPPORT_TLS
 fail:
   log_write(0,
     LOG_MAIN | (exec_type == CEE_EXEC_EXIT ? LOG_PANIC : LOG_PANIC_DIE),
@@ -8540,6 +8651,7 @@ fail:
   Note: this must be _exit(), not exit(). */
 
   _exit(EX_EXECFAILED);
+#endif
 }
 
 /* vi: aw ai sw=2