Fix spurious detection of timeout while writing to transport filter
[exim.git] / src / src / transport.c
index fdd97822ae6c1c572374636e93be192bfb7dd690..e4aaa7debd889e8e2789883ddafa5c80337485cf 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. */
 
 /* General functions concerned with transportation, and generic options for all
@@ -11,105 +11,122 @@ transports. */
 
 #include "exim.h"
 
-#ifdef HAVE_LINUX_SENDFILE
-#include <sys/sendfile.h>
-#endif
-
-/* Structure for keeping list of addresses that have been added to
-Envelope-To:, in order to avoid duplication. */
-
-struct aci {
-  struct aci *next;
-  address_item *ptr;
-  };
-
-
-/* Static data for write_chunk() */
-
-static uschar *chunk_ptr;           /* chunk pointer */
-static uschar *nl_check;            /* string to look for at line start */
-static int     nl_check_length;     /* length of same */
-static uschar *nl_escape;           /* string to insert */
-static int     nl_escape_length;    /* length of same */
-static int     nl_partial_match;    /* length matched at chunk end */
-
-
 /* Generic options for transports, all of which live inside transport_instance
 data blocks and which therefore have the opt_public flag set. Note that there
 are other options living inside this structure which can be set only from
 certain transports. */
+#define LOFF(field) OPT_OFF(transport_instance, field)
 
 optionlist optionlist_transports[] = {
+  /*   name            type                                    value */
   { "*expand_group",    opt_stringptr|opt_hidden|opt_public,
-                 (void *)offsetof(transport_instance, expand_gid) },
+                 LOFF(expand_gid) },
   { "*expand_user",     opt_stringptr|opt_hidden|opt_public,
-                 (void *)offsetof(transport_instance, expand_uid) },
+                 LOFF(expand_uid) },
   { "*headers_rewrite_flags", opt_int|opt_public|opt_hidden,
-                 (void *)offsetof(transport_instance, rewrite_existflags) },
+                 LOFF(rewrite_existflags) },
   { "*headers_rewrite_rules", opt_void|opt_public|opt_hidden,
-                 (void *)offsetof(transport_instance, rewrite_rules) },
+                 LOFF(rewrite_rules) },
   { "*set_group",       opt_bool|opt_hidden|opt_public,
-                 (void *)offsetof(transport_instance, gid_set) },
+                 LOFF(gid_set) },
   { "*set_user",        opt_bool|opt_hidden|opt_public,
-                 (void *)offsetof(transport_instance, uid_set) },
+                 LOFF(uid_set) },
   { "body_only",        opt_bool|opt_public,
-                 (void *)offsetof(transport_instance, body_only) },
+                 LOFF(body_only) },
   { "current_directory", opt_stringptr|opt_public,
-                 (void *)offsetof(transport_instance, current_dir) },
+                 LOFF(current_dir) },
   { "debug_print",      opt_stringptr | opt_public,
-                 (void *)offsetof(transport_instance, debug_string) },
+                 LOFF(debug_string) },
   { "delivery_date_add", opt_bool|opt_public,
-                 (void *)(offsetof(transport_instance, delivery_date_add)) },
+                 LOFF(delivery_date_add) },
   { "disable_logging",  opt_bool|opt_public,
-                 (void *)(offsetof(transport_instance, disable_logging)) },
+                 LOFF(disable_logging) },
   { "driver",           opt_stringptr|opt_public,
-                 (void *)offsetof(transport_instance, driver_name) },
+                 LOFF(driver_name) },
   { "envelope_to_add",   opt_bool|opt_public,
-                 (void *)(offsetof(transport_instance, envelope_to_add)) },
+                 LOFF(envelope_to_add) },
 #ifndef DISABLE_EVENT
   { "event_action",     opt_stringptr | opt_public,
-                 (void *)offsetof(transport_instance, event_action) },
+                 LOFF(event_action) },
 #endif
   { "group",             opt_expand_gid|opt_public,
-                 (void *)offsetof(transport_instance, gid) },
+                 LOFF(gid) },
   { "headers_add",      opt_stringptr|opt_public|opt_rep_str,
-                 (void *)offsetof(transport_instance, add_headers) },
+                 LOFF(add_headers) },
   { "headers_only",     opt_bool|opt_public,
-                 (void *)offsetof(transport_instance, headers_only) },
+                 LOFF(headers_only) },
   { "headers_remove",   opt_stringptr|opt_public|opt_rep_str,
-                 (void *)offsetof(transport_instance, remove_headers) },
+                 LOFF(remove_headers) },
   { "headers_rewrite",  opt_rewrite|opt_public,
-                 (void *)offsetof(transport_instance, headers_rewrite) },
+                 LOFF(headers_rewrite) },
   { "home_directory",   opt_stringptr|opt_public,
-                 (void *)offsetof(transport_instance, home_dir) },
+                 LOFF(home_dir) },
   { "initgroups",       opt_bool|opt_public,
-                 (void *)offsetof(transport_instance, initgroups) },
+                 LOFF(initgroups) },
   { "max_parallel",     opt_stringptr|opt_public,
-                 (void *)offsetof(transport_instance, max_parallel) },
+                 LOFF(max_parallel) },
   { "message_size_limit", opt_stringptr|opt_public,
-                 (void *)offsetof(transport_instance, message_size_limit) },
+                 LOFF(message_size_limit) },
   { "rcpt_include_affixes", opt_bool|opt_public,
-                 (void *)offsetof(transport_instance, rcpt_include_affixes) },
+                 LOFF(rcpt_include_affixes) },
   { "retry_use_local_part", opt_bool|opt_public,
-                 (void *)offsetof(transport_instance, retry_use_local_part) },
+                 LOFF(retry_use_local_part) },
   { "return_path",      opt_stringptr|opt_public,
-                 (void *)(offsetof(transport_instance, return_path)) },
+                 LOFF(return_path) },
   { "return_path_add",   opt_bool|opt_public,
-                 (void *)(offsetof(transport_instance, return_path_add)) },
+                 LOFF(return_path_add) },
   { "shadow_condition", opt_stringptr|opt_public,
-                 (void *)offsetof(transport_instance, shadow_condition) },
+                 LOFF(shadow_condition) },
   { "shadow_transport", opt_stringptr|opt_public,
-                 (void *)offsetof(transport_instance, shadow) },
+                 LOFF(shadow) },
   { "transport_filter", opt_stringptr|opt_public,
-                 (void *)offsetof(transport_instance, filter_command) },
+                 LOFF(filter_command) },
   { "transport_filter_timeout", opt_time|opt_public,
-                 (void *)offsetof(transport_instance, filter_timeout) },
+                 LOFF(filter_timeout) },
   { "user",             opt_expand_uid|opt_public,
-                 (void *)offsetof(transport_instance, uid) }
+                 LOFF(uid) }
 };
 
-int optionlist_transports_size =
-  sizeof(optionlist_transports)/sizeof(optionlist);
+int optionlist_transports_size = nelem(optionlist_transports);
+
+#ifdef MACRO_PREDEF
+
+# include "macro_predef.h"
+
+void
+options_transports(void)
+{
+uschar buf[64];
+
+options_from_list(optionlist_transports, nelem(optionlist_transports), US"TRANSPORTS", NULL);
+
+for (transport_info * ti = transports_available; ti->driver_name[0]; ti++)
+  {
+  spf(buf, sizeof(buf), US"_DRIVER_TRANSPORT_%T", ti->driver_name);
+  builtin_macro_create(buf);
+  options_from_list(ti->options, (unsigned)*ti->options_count, US"TRANSPORT", ti->driver_name);
+  }
+}
+
+#else  /*!MACRO_PREDEF*/
+
+/* Structure for keeping list of addresses that have been added to
+Envelope-To:, in order to avoid duplication. */
+
+struct aci {
+  struct aci *next;
+  address_item *ptr;
+  };
+
+
+/* Static data for write_chunk() */
+
+static uschar *chunk_ptr;           /* chunk pointer */
+static uschar *nl_check;            /* string to look for at line start */
+static int     nl_check_length;     /* length of same */
+static uschar *nl_escape;           /* string to insert */
+static int     nl_escape_length;    /* length of same */
+static int     nl_partial_match;    /* length matched at chunk end */
 
 
 /*************************************************
@@ -125,8 +142,6 @@ the work. */
 void
 transport_init(void)
 {
-transport_instance *t;
-
 readconf_driver_init(US"transport",
   (driver_instance **)(&transports),     /* chain anchor */
   (driver_info *)transports_available,   /* available drivers */
@@ -139,14 +154,11 @@ readconf_driver_init(US"transport",
 /* Now scan the configured transports and check inconsistencies. A shadow
 transport is permitted only for local transports. */
 
-for (t = transports; t != NULL; t = t->next)
+for (transport_instance * t = transports; t; t = t->next)
   {
-  if (!t->info->local)
-    {
-    if (t->shadow != NULL)
-      log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
-        "shadow transport not allowed on non-local transport %s", t->name);
-    }
+  if (!t->info->local && t->shadow)
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
+      "shadow transport not allowed on non-local transport %s", t->name);
 
   if (t->body_only && t->headers_only)
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
@@ -161,6 +173,20 @@ for (t = transports; t != NULL; t = t->next)
 *             Write block of data                *
 *************************************************/
 
+static int
+tpt_write(int fd, uschar * block, int len, BOOL more, int options)
+{
+return
+#ifndef DISABLE_TLS
+  tls_out.active.sock == fd
+    ? tls_write(tls_out.active.tls_ctx, block, len, more) :
+#endif
+#ifdef MSG_MORE
+  more && !(options & topt_not_socket) ? send(fd, block, len, MSG_MORE) :
+#endif
+  write(fd, block, len);
+}
+
 /* Subroutine called by write_chunk() and at the end of the message actually
 to write a data block. Also called directly by some transports to write
 additional data to the file descriptor (e.g. prefix, suffix).
@@ -194,61 +220,69 @@ evermore, so stick a maximum repetition count on the loop to act as a
 longstop.
 
 Arguments:
-  fd        file descriptor to write to
+  tctx      transport context: file descriptor or string to write to
   block     block of bytes to write
   len       number of bytes to write
+  more     further data expected soon
 
 Returns:    TRUE on success, FALSE on failure (with errno preserved);
               transport_count is incremented by the number of bytes written
 */
 
-BOOL
-transport_write_block(int fd, uschar *block, int len)
+static BOOL
+transport_write_block_fd(transport_ctx * tctx, uschar * block, int len, BOOL more)
 {
-int i, rc, save_errno;
+int rc, save_errno;
 int local_timeout = transport_write_timeout;
+int connretry = 1;
+int fd = tctx->u.fd;
 
 /* This loop is for handling incomplete writes and other retries. In most
 normal cases, it is only ever executed once. */
 
-for (i = 0; i < 100; i++)
+for (int i = 0; i < 100; i++)
   {
   DEBUG(D_transport)
-    debug_printf("writing data block fd=%d size=%d timeout=%d\n",
-      fd, len, local_timeout);
+    debug_printf("writing data block fd=%d size=%d timeout=%d%s\n",
+      fd, len, local_timeout, more ? " (more expected)" : "");
 
-  /* This code makes use of alarm() in order to implement the timeout. This
-  isn't a very tidy way of doing things. Using non-blocking I/O with select()
-  provides a neater approach. However, I don't know how to do this when TLS is
-  in use. */
+  /* When doing TCP Fast Open we may get this far before the 3-way handshake
+  is complete, and write returns ENOTCONN.  Detect that, wait for the socket
+  to become writable, and retry once only. */
 
-  if (transport_write_timeout <= 0)   /* No timeout wanted */
+  for(;;)
     {
-    #ifdef SUPPORT_TLS
-    if (tls_out.active == fd) rc = tls_write(FALSE, block, len); else
-    #endif
-    rc = write(fd, block, len);
-    save_errno = errno;
-    }
-
-  /* Timeout wanted. */
+    fd_set fds;
+    /* This code makes use of alarm() in order to implement the timeout. This
+    isn't a very tidy way of doing things. Using non-blocking I/O with select()
+    provides a neater approach. However, I don't know how to do this when TLS is
+    in use. */
 
-  else
-    {
-    alarm(local_timeout);
-#ifdef SUPPORT_TLS
-    if (tls_out.active == fd)
-      rc = tls_write(FALSE, block, len);
-    else
-#endif
-      rc = write(fd, block, len);
-    save_errno = errno;
-    local_timeout = alarm(0);
-    if (sigalrm_seen)
+    if (transport_write_timeout <= 0)   /* No timeout wanted */
       {
-      errno = ETIMEDOUT;
-      return FALSE;
+      rc = tpt_write(fd, block, len, more, tctx->options);
+      save_errno = errno;
       }
+    else                               /* Timeout wanted. */
+      {
+      sigalrm_seen = FALSE;
+      ALARM(local_timeout);
+       rc = tpt_write(fd, block, len, more, tctx->options);
+       save_errno = errno;
+      local_timeout = ALARM_CLR(0);
+      if (sigalrm_seen)
+       {
+       errno = ETIMEDOUT;
+       return FALSE;
+       }
+      }
+
+    if (rc >= 0 || errno != ENOTCONN || connretry <= 0)
+      break;
+
+    FD_ZERO(&fds); FD_SET(fd, &fds);
+    select(fd+1, NULL, &fds, NULL, NULL);      /* could set timout? */
+    connretry--;
     }
 
   /* Hopefully, the most common case is success, so test that first. */
@@ -313,6 +347,22 @@ return FALSE;
 }
 
 
+BOOL
+transport_write_block(transport_ctx * tctx, uschar *block, int len, BOOL more)
+{
+if (!(tctx->options & topt_output_string))
+  return transport_write_block_fd(tctx, block, len, more);
+
+/* Write to expanding-string.  NOTE: not NUL-terminated */
+
+if (!tctx->u.msg)
+  tctx->u.msg = string_get(1024);
+
+tctx->u.msg = string_catn(tctx->u.msg, block, len);
+return TRUE;
+}
+
+
 
 
 /*************************************************
@@ -332,17 +382,34 @@ Returns:      the yield of transport_write_block()
 BOOL
 transport_write_string(int fd, const char *format, ...)
 {
+transport_ctx tctx = {{0}};
+gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
 va_list ap;
+
+/* Use taint-unchecked routines for writing into big_buffer, trusting
+that the result will never be expanded. */
+
 va_start(ap, format);
-if (!string_vformat(big_buffer, big_buffer_size, format, ap))
+if (!string_vformat(&gs, SVFMT_TAINT_NOCHK, format, ap))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong formatted string in transport");
 va_end(ap);
-return transport_write_block(fd, big_buffer, Ustrlen(big_buffer));
+tctx.u.fd = fd;
+return transport_write_block(&tctx, gs.s, gs.ptr, FALSE);
 }
 
 
 
 
+void
+transport_write_reset(int options)
+{
+if (!(options & topt_continuation)) chunk_ptr = deliver_out_buffer;
+nl_partial_match = -1;
+nl_check_length = nl_escape_length = 0;
+}
+
+
+
 /*************************************************
 *              Write character chunk             *
 *************************************************/
@@ -356,22 +423,21 @@ Static data is used to handle the case when the last character of the previous
 chunk was NL, or matched part of the data that has to be escaped.
 
 Arguments:
-  fd         file descript to write to
+  tctx       transport context - processing to be done during output,
+               and file descriptor to write to
   chunk      pointer to data to write
   len        length of data to write
-  tctx       transport context - processing to be done during output
 
 In addition, the static nl_xxx variables must be set as required.
 
 Returns:     TRUE on success, FALSE on failure (with errno preserved)
 */
 
-static BOOL
-write_chunk(int fd, transport_ctx * tctx, uschar *chunk, int len)
+BOOL
+write_chunk(transport_ctx * tctx, uschar *chunk, int len)
 {
 uschar *start = chunk;
 uschar *end = chunk + len;
-uschar *ptr;
 int mlen = DELIVER_OUT_BUFFER_SIZE - nl_escape_length - 2;
 
 /* The assumption is made that the check string will never stretch over move
@@ -408,7 +474,7 @@ if (nl_partial_match >= 0)
 for possible escaping. The code for the non-NL route should be as fast as
 possible. */
 
-for (ptr = start; ptr < end; ptr++)
+for (uschar * ptr = start; ptr < end; ptr++)
   {
   int ch, len;
 
@@ -423,27 +489,36 @@ for (ptr = start; ptr < end; ptr++)
     /* If CHUNKING, prefix with BDAT (size) NON-LAST.  Also, reap responses
     from previous SMTP commands. */
 
-    if (tctx &&  tctx->options & topt_use_bdat  &&  tctx->chunk_cb)
+    if (tctx->options & topt_use_bdat  &&  tctx->chunk_cb)
       {
-      if (  tctx->chunk_cb(fd, tctx, (unsigned)len, 0) != OK
-        || !transport_write_block(fd, deliver_out_buffer, len)
-        || tctx->chunk_cb(fd, tctx, 0, tc_reap_prev) != OK
+      if (  tctx->chunk_cb(tctx, (unsigned)len, 0) != OK
+        || !transport_write_block(tctx, deliver_out_buffer, len, FALSE)
+        || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
         )
        return FALSE;
       }
     else
-      if (!transport_write_block(fd, deliver_out_buffer, len))
+      if (!transport_write_block(tctx, deliver_out_buffer, len, FALSE))
        return FALSE;
     chunk_ptr = deliver_out_buffer;
     }
 
+  /* Remove CR before NL if required */
+
+  if (  *ptr == '\r' && ptr[1] == '\n'
+     && !(tctx->options & topt_use_crlf)
+     && f.spool_file_wireformat
+     )
+    ptr++;
+
   if ((ch = *ptr) == '\n')
     {
     int left = end - ptr - 1;  /* count of chars left after NL */
 
     /* Insert CR before NL if required */
 
-    if (tctx  &&  tctx->options & topt_use_crlf) *chunk_ptr++ = '\r';
+    if (tctx->options & topt_use_crlf && !f.spool_file_wireformat)
+      *chunk_ptr++ = '\r';
     *chunk_ptr++ = '\n';
     transport_newlines++;
 
@@ -517,17 +592,17 @@ if (include_affixes)
   return addr->address;
   }
 
-if (addr->suffix == NULL)
+if (!addr->suffix)
   {
-  if (addr->prefix == NULL) return addr->address;
+  if (!addr->prefix) return addr->address;
   return addr->address + Ustrlen(addr->prefix);
   }
 
 at = Ustrrchr(addr->address, '@');
-plen = (addr->prefix == NULL)? 0 : Ustrlen(addr->prefix);
+plen = addr->prefix ? Ustrlen(addr->prefix) : 0;
 slen = Ustrlen(addr->suffix);
 
-return string_sprintf("%.*s@%s", (at - addr->address - plen - slen),
+return string_sprintf("%.*s@%s", (int)(at - addr->address - plen - slen),
    addr->address + plen, at + 1);
 }
 
@@ -559,15 +634,15 @@ Arguments:
   pplist    address of anchor of the list of addresses not to output
   pdlist    address of anchor of the list of processed addresses
   first     TRUE if this is the first address; set it FALSE afterwards
-  fd        the file descriptor to write to
   tctx      transport context - processing to be done during output
+             and the file descriptor to write to
 
 Returns:    FALSE if writing failed
 */
 
 static BOOL
 write_env_to(address_item *p, struct aci **pplist, struct aci **pdlist,
-  BOOL *first, int fd, transport_ctx * tctx)
+  BOOL *first, transport_ctx * tctx)
 {
 address_item *pp;
 struct aci *ppp;
@@ -577,7 +652,7 @@ so that we don't handle it again. */
 
 for (ppp = *pdlist; ppp; ppp = ppp->next) if (p == ppp->ptr) return TRUE;
 
-ppp = store_get(sizeof(struct aci));
+ppp = store_get(sizeof(struct aci), FALSE);
 ppp->next = *pdlist;
 *pdlist = ppp;
 ppp->ptr = p;
@@ -589,7 +664,7 @@ for (pp = p;; pp = pp->parent)
   address_item *dup;
   for (dup = addr_duplicate; dup; dup = dup->next)
     if (dup->dupof == pp)   /* a dup of our address */
-      if (!write_env_to(dup, pplist, pdlist, first, fd, tctx))
+      if (!write_env_to(dup, pplist, pdlist, first, tctx))
        return FALSE;
   if (!pp->parent) break;
   }
@@ -601,20 +676,20 @@ if (ppp) return TRUE;
 
 /* Remember what we have output, and output it. */
 
-ppp = store_get(sizeof(struct aci));
+ppp = store_get(sizeof(struct aci), FALSE);
 ppp->next = *pplist;
 *pplist = ppp;
 ppp->ptr = pp;
 
-if (!*first && !write_chunk(fd, tctx, US",\n ", 3)) return FALSE;
+if (!*first && !write_chunk(tctx, US",\n ", 3)) return FALSE;
 *first = FALSE;
-return write_chunk(fd, tctx, pp->address, Ustrlen(pp->address));
+return write_chunk(tctx, pp->address, Ustrlen(pp->address));
 }
 
 
 
 
-/* Add/remove/rewwrite headers, and send them plus the empty-line sparator.
+/* Add/remove/rewrite headers, and send them plus the empty-line separator.
 
 Globals:
   header_list
@@ -622,17 +697,15 @@ Globals:
 Arguments:
   addr                  (chain of) addresses (for extra headers), or NULL;
                           only the first address is used
-  fd                    file descriptor to write the message to
   tctx                  transport context
   sendfn               function for output (transport or verify)
 
 Returns:                TRUE on success; FALSE on failure.
 */
 BOOL
-transport_headers_send(int fd, transport_ctx * tctx,
-  BOOL (*sendfn)(int fd, transport_ctx * tctx, uschar * s, int len))
+transport_headers_send(transport_ctx * tctx,
+  BOOL (*sendfn)(transport_ctx * tctx, uschar * s, int len))
 {
-header_line *h;
 const uschar *list;
 transport_instance * tblock = tctx ? tctx->tblock : NULL;
 address_item * addr = tctx ? tctx->addr : NULL;
@@ -644,13 +717,12 @@ match any entries therein.  It is a colon-sep list; expand the items
 separately and squash any empty ones.
 Then check addr->prop.remove_headers too, provided that addr is not NULL. */
 
-for (h = header_list; h; h = h->next) if (h->type != htype_old)
+for (header_line * h = header_list; h; h = h->next) if (h->type != htype_old)
   {
-  int i;
   BOOL include_header = TRUE;
 
   list = tblock ? tblock->remove_headers : NULL;
-  for (i = 0; i < 2; i++)    /* For remove_headers && addr->prop.remove_headers */
+  for (int i = 0; i < 2; i++)    /* For remove_headers && addr->prop.remove_headers */
     {
     if (list)
       {
@@ -661,16 +733,23 @@ for (h = header_list; h; h = h->next) if (h->type != htype_old)
        int len;
 
        if (i == 0)
-         if (!(s = expand_string(s)) && !expand_string_forcedfail)
+         if (!(s = expand_string(s)) && !f.expand_string_forcedfail)
            {
            errno = ERRNO_CHHEADER_FAIL;
            return FALSE;
            }
        len = s ? Ustrlen(s) : 0;
-       if (strncmpic(h->text, s, len) != 0) continue;
-       ss = h->text + len;
-       while (*ss == ' ' || *ss == '\t') ss++;
-       if (*ss == ':') break;
+       if (len && s[len-1] == '*')     /* trailing glob */
+         {
+         if (strncmpic(h->text, s, len-1) == 0) break;
+         }
+       else
+         {
+         if (strncmpic(h->text, s, len) != 0) continue;
+         ss = h->text + len;
+         while (*ss == ' ' || *ss == '\t') ss++;
+         if (*ss == ':') break;
+         }
        }
       if (s) { include_header = FALSE; break; }
       }
@@ -684,13 +763,13 @@ for (h = header_list; h; h = h->next) if (h->type != htype_old)
     {
     if (tblock && tblock->rewrite_rules)
       {
-      void *reset_point = store_get(0);
+      rmark reset_point = store_mark();
       header_line *hh;
 
       if ((hh = rewrite_header(h, NULL, NULL, tblock->rewrite_rules,
                  tblock->rewrite_existflags, FALSE)))
        {
-       if (!sendfn(fd, tctx, hh->text, hh->slen)) return FALSE;
+       if (!sendfn(tctx, hh->text, hh->slen)) return FALSE;
        store_reset(reset_point);
        continue;     /* With the next header line */
        }
@@ -698,15 +777,13 @@ for (h = header_list; h; h = h->next) if (h->type != htype_old)
 
     /* Either no rewriting rules, or it didn't get rewritten */
 
-    if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE;
+    if (!sendfn(tctx, h->text, h->slen)) return FALSE;
     }
 
   /* Header removed */
 
   else
-    {
     DEBUG(D_transport) debug_printf("removed header line:\n%s---\n", h->text);
-    }
   }
 
 /* Add on any address-specific headers. If there are multiple addresses,
@@ -722,10 +799,9 @@ Headers added to an address by a router are guaranteed to end with a newline.
 
 if (addr)
   {
-  int i;
   header_line *hprev = addr->prop.extra_headers;
-  header_line *hnext;
-  for (i = 0; i < 2; i++)
+  header_line *hnext, * h;
+  for (int i = 0; i < 2; i++)
     for (h = hprev, hprev = NULL; h; h = hnext)
       {
       hnext = h->next;
@@ -733,7 +809,7 @@ if (addr)
       hprev = h;
       if (i == 1)
        {
-       if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE;
+       if (!sendfn(tctx, h->text, h->slen)) return FALSE;
        DEBUG(D_transport)
          debug_printf("added header line(s):\n%s---\n", h->text);
        }
@@ -758,8 +834,8 @@ if (tblock && (list = CUS tblock->add_headers))
       int len = Ustrlen(s);
       if (len > 0)
        {
-       if (!sendfn(fd, tctx, s, len)) return FALSE;
-       if (s[len-1] != '\n' && !sendfn(fd, tctx, US"\n", 1))
+       if (!sendfn(tctx, s, len)) return FALSE;
+       if (s[len-1] != '\n' && !sendfn(tctx, US"\n", 1))
          return FALSE;
        DEBUG(D_transport)
          {
@@ -769,13 +845,13 @@ if (tblock && (list = CUS tblock->add_headers))
          }
        }
       }
-    else if (!expand_string_forcedfail)
+    else if (!f.expand_string_forcedfail)
       { errno = ERRNO_CHHEADER_FAIL; return FALSE; }
   }
 
 /* Separate headers from body with a blank line */
 
-return sendfn(fd, tctx, US"\n", 1);
+return sendfn(tctx, US"\n", 1);
 }
 
 
@@ -808,8 +884,10 @@ can include timeouts for certain transports, which are requested by setting
 transport_write_timeout non-zero.
 
 Arguments:
-  fd                    file descriptor to write the message to
   tctx
+    (fd, msg)          Either and fd, to write the message to,
+                       or a string: if null write message to allocated space
+                       otherwire take content as headers.
     addr                (chain of) addresses (for extra headers), or NULL;
                           only the first address is used
     tblock             optional transport instance block (NULL signifies NULL/0):
@@ -841,17 +919,16 @@ Returns:                TRUE on success; FALSE (with errno) on failure.
 */
 
 static BOOL
-internal_transport_write_message(int fd, transport_ctx * tctx, int size_limit)
+internal_transport_write_message(transport_ctx * tctx, int size_limit)
 {
-int len;
+int len, size = 0;
 
 /* Initialize pointer in output buffer. */
 
-chunk_ptr = deliver_out_buffer;
+transport_write_reset(tctx->options);
 
 /* Set up the data for start-of-line data checking and escaping */
 
-nl_partial_match = -1;
 if (tctx->check_string && tctx->escape_string)
   {
   nl_check = tctx->check_string;
@@ -859,8 +936,6 @@ if (tctx->check_string && tctx->escape_string)
   nl_escape = tctx->escape_string;
   nl_escape_length = Ustrlen(nl_escape);
   }
-else
-  nl_check_length = nl_escape_length = 0;
 
 /* Whether the escaping mechanism is applied to headers or not is controlled by
 an option (set for SMTP, not otherwise). Negate the length if not wanted till
@@ -870,10 +945,14 @@ if (!(tctx->options & topt_escape_headers))
   nl_check_length = -nl_check_length;
 
 /* Write the headers if required, including any that have to be added. If there
-are header rewriting rules, apply them. */
+are header rewriting rules, apply them.  The datasource is not the -D spoolfile
+so temporarily hide the global that adjusts for its format. */
 
 if (!(tctx->options & topt_no_headers))
   {
+  BOOL save_wireformat = f.spool_file_wireformat;
+  f.spool_file_wireformat = FALSE;
+
   /* Add return-path: if requested. */
 
   if (tctx->options & topt_add_return_path)
@@ -881,7 +960,7 @@ if (!(tctx->options & topt_no_headers))
     uschar buffer[ADDRESS_MAXLENGTH + 20];
     int n = sprintf(CS buffer, "Return-path: <%.*s>\n", ADDRESS_MAXLENGTH,
       return_path);
-    if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
+    if (!write_chunk(tctx, buffer, n)) goto bad;
     }
 
   /* Add envelope-to: if requested */
@@ -889,24 +968,23 @@ if (!(tctx->options & topt_no_headers))
   if (tctx->options & topt_add_envelope_to)
     {
     BOOL first = TRUE;
-    address_item *p;
     struct aci *plist = NULL;
     struct aci *dlist = NULL;
-    void *reset_point = store_get(0);
+    rmark reset_point = store_mark();
 
-    if (!write_chunk(fd, tctx, US"Envelope-to: ", 13)) return FALSE;
+    if (!write_chunk(tctx, US"Envelope-to: ", 13)) goto bad;
 
     /* Pick up from all the addresses. The plist and dlist variables are
     anchors for lists of addresses already handled; they have to be defined at
-    this level becuase write_env_to() calls itself recursively. */
+    this level because write_env_to() calls itself recursively. */
 
-    for (p = tctx->addr; p; p = p->next)
-      if (!write_env_to(p, &plist, &dlist, &first, fd, tctx))
-       return FALSE;
+    for (address_item * p = tctx->addr; p; p = p->next)
+      if (!write_env_to(p, &plist, &dlist, &first, tctx))
+       goto bad;
 
     /* Add a final newline and reset the store used for tracking duplicates */
 
-    if (!write_chunk(fd, tctx, US"\n", 1)) return FALSE;
+    if (!write_chunk(tctx, US"\n", 1)) goto bad;
     store_reset(reset_point);
     }
 
@@ -914,9 +992,11 @@ if (!(tctx->options & topt_no_headers))
 
   if (tctx->options & topt_add_delivery_date)
     {
-    uschar buffer[100];
-    int n = sprintf(CS buffer, "Delivery-date: %s\n", tod_stamp(tod_full));
-    if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
+    uschar * s = tod_stamp(tod_full);
+
+    if (  !write_chunk(tctx, US"Delivery-date: ", 15)
+       || !write_chunk(tctx, s, Ustrlen(s))
+       || !write_chunk(tctx, US"\n", 1)) goto bad;
     }
 
   /* Then the message's headers. Don't write any that are flagged as "old";
@@ -925,8 +1005,14 @@ if (!(tctx->options & topt_no_headers))
   match any entries therein. Then check addr->prop.remove_headers too, provided that
   addr is not NULL. */
 
-  if (!transport_headers_send(fd, tctx, &write_chunk))
+  if (!transport_headers_send(tctx, &write_chunk))
+    {
+bad:
+    f.spool_file_wireformat = save_wireformat;
     return FALSE;
+    }
+
+  f.spool_file_wireformat = save_wireformat;
   }
 
 /* When doing RFC3030 CHUNKING output, work out how much data would be in a
@@ -944,7 +1030,7 @@ suboptimal. */
 if (tctx->options & topt_use_bdat)
   {
   off_t fsize;
-  int hsize, size;
+  int hsize;
 
   if ((hsize = chunk_ptr - deliver_out_buffer) < 0)
     hsize = 0;
@@ -955,8 +1041,11 @@ if (tctx->options & topt_use_bdat)
     if (size_limit > 0  &&  fsize > size_limit)
       fsize = size_limit;
     size = hsize + fsize;
-    if (tctx->options & topt_use_crlf)
+    if (tctx->options & topt_use_crlf  &&  !f.spool_file_wireformat)
       size += body_linecount;  /* account for CRLF-expansion */
+
+    /* With topt_use_bdat we never do dot-stuffing; no need to
+    account for any expansion due to that. */
     }
 
   /* If the message is large, emit first a non-LAST chunk with just the
@@ -968,21 +1057,21 @@ if (tctx->options & topt_use_bdat)
   if (size > DELIVER_OUT_BUFFER_SIZE && hsize > 0)
     {
     DEBUG(D_transport)
-      debug_printf("sending small initial BDAT; hssize=%d\n", hsize);
-    if (  tctx->chunk_cb(fd, tctx, hsize, 0) != OK
-       || !transport_write_block(fd, deliver_out_buffer, hsize)
-       || tctx->chunk_cb(fd, tctx, 0, tc_reap_prev) != OK
+      debug_printf("sending small initial BDAT; hsize=%d\n", hsize);
+    if (  tctx->chunk_cb(tctx, hsize, 0) != OK
+       || !transport_write_block(tctx, deliver_out_buffer, hsize, FALSE)
+       || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
        )
       return FALSE;
     chunk_ptr = deliver_out_buffer;
     size -= hsize;
     }
 
-  /* Emit a LAST datachunk command. */
+  /* Emit a LAST datachunk command, and unmark the context for further
+  BDAT commands. */
 
-  if (tctx->chunk_cb(fd, tctx, size, tc_chunk_last) != OK)
+  if (tctx->chunk_cb(tctx, size, tc_chunk_last) != OK)
     return FALSE;
-
   tctx->options &= ~topt_use_bdat;
   }
 
@@ -992,18 +1081,64 @@ negative in cases where it isn't to apply to the headers). Then ensure the body
 is positioned at the start of its file (following the message id), then write
 it, applying the size limit if required. */
 
+/* If we have a wireformat -D file (CRNL lines, non-dotstuffed, no ending dot)
+and we want to send a body without dotstuffing or ending-dot, in-clear,
+then we can just dump it using sendfile.
+This should get used for CHUNKING output and also for writing the -K file for
+dkim signing,  when we had CHUNKING input.  */
+
+#ifdef OS_SENDFILE
+if (  f.spool_file_wireformat
+   && !(tctx->options & (topt_no_body | topt_end_dot))
+   && !nl_check_length
+   && tls_out.active.sock != tctx->u.fd
+   )
+  {
+  ssize_t copied = 0;
+  off_t offset = SPOOL_DATA_START_OFFSET;
+
+  /* Write out any header data in the buffer */
+
+  if ((len = chunk_ptr - deliver_out_buffer) > 0)
+    {
+    if (!transport_write_block(tctx, deliver_out_buffer, len, TRUE))
+      return FALSE;
+    size -= len;
+    }
+
+  DEBUG(D_transport) debug_printf("using sendfile for body\n");
+
+  while(size > 0)
+    {
+    if ((copied = os_sendfile(tctx->u.fd, deliver_datafile, &offset, size)) <= 0) break;
+    size -= copied;
+    }
+  return copied >= 0;
+  }
+#else
+DEBUG(D_transport) debug_printf("cannot use sendfile for body: no support\n");
+#endif
+
+DEBUG(D_transport)
+  if (!(tctx->options & topt_no_body))
+    debug_printf("cannot use sendfile for body: %s\n",
+      !f.spool_file_wireformat ? "spoolfile not wireformat"
+      : tctx->options & topt_end_dot ? "terminating dot wanted"
+      : nl_check_length ? "dot- or From-stuffing wanted"
+      : "TLS output wanted");
+
 if (!(tctx->options & topt_no_body))
   {
-  int size = size_limit;
+  unsigned long size = size_limit > 0 ? size_limit : ULONG_MAX;
 
   nl_check_length = abs(nl_check_length);
   nl_partial_match = 0;
   if (lseek(deliver_datafile, SPOOL_DATA_START_OFFSET, SEEK_SET) < 0)
     return FALSE;
-  while (  (len = MAX(DELIVER_IN_BUFFER_SIZE, size)) > 0
+  while (  (len = MIN(DELIVER_IN_BUFFER_SIZE, size)) > 0
        && (len = read(deliver_datafile, deliver_in_buffer, len)) > 0)
     {
-    if (!write_chunk(fd, tctx, deliver_in_buffer, len))
+    if (!write_chunk(tctx, deliver_in_buffer, len))
       return FALSE;
     size -= len;
     }
@@ -1013,212 +1148,22 @@ if (!(tctx->options & topt_no_body))
   if (len != 0) return FALSE;
   }
 
-/* Finished with the check string */
+/* Finished with the check string, and spool-format consideration */
 
 nl_check_length = nl_escape_length = 0;
+f.spool_file_wireformat = FALSE;
 
 /* If requested, add a terminating "." line (SMTP output). */
 
-if (tctx->options & topt_end_dot && !write_chunk(fd, tctx, US".\n", 2))
+if (tctx->options & topt_end_dot && !write_chunk(tctx, US".\n", 2))
   return FALSE;
 
 /* Write out any remaining data in the buffer before returning. */
 
 return (len = chunk_ptr - deliver_out_buffer) <= 0 ||
-  transport_write_block(fd, deliver_out_buffer, len);
-}
-
-
-#ifndef DISABLE_DKIM
-
-/***************************************************************************************************
-*    External interface to write the message, while signing it with DKIM and/or Domainkeys         *
-***************************************************************************************************/
-
-/* This function is a wrapper around transport_write_message().
-   It is only called from the smtp transport if DKIM or Domainkeys support
-   is compiled in.  The function sets up a replacement fd into a -K file,
-   then calls the normal function. This way, the exact bits that exim would
-   have put "on the wire" will end up in the file (except for TLS
-   encapsulation, which is the very very last thing). When we are done
-   signing the file, send the signed message down the original fd (or TLS fd).
-
-Arguments:
-  as for internal_transport_write_message() above, with additional arguments
-  for DKIM.
-
-Returns:       TRUE on success; FALSE (with errno) for any failure
-*/
-
-BOOL
-dkim_transport_write_message(int out_fd, transport_ctx * tctx,
-  struct ob_dkim * dkim)
-{
-int dkim_fd;
-int save_errno = 0;
-BOOL rc;
-uschar * dkim_spool_name;
-int sread = 0;
-int wwritten = 0;
-uschar *dkim_signature = NULL;
-int siglen = 0;
-off_t k_file_size;
-int options;
-
-/* If we can't sign, just call the original function. */
-
-if (!(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector))
-  return transport_write_message(out_fd, tctx, 0);
-
-dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
-                   string_sprintf("-%d-K", (int)getpid()));
-
-if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
-  {
-  /* Can't create spool file. Ugh. */
-  rc = FALSE;
-  save_errno = errno;
-  goto CLEANUP;
-  }
-
-/* Call original function to write the -K file; does the CRLF expansion */
-
-options = tctx->options;
-tctx->options &= ~topt_use_bdat;
-rc = transport_write_message(dkim_fd, tctx, 0);
-tctx->options = options;
-
-/* Save error state. We must clean up before returning. */
-if (!rc)
-  {
-  save_errno = errno;
-  goto CLEANUP;
-  }
-
-/* Rewind file and feed it to the goats^W DKIM lib */
-lseek(dkim_fd, 0, SEEK_SET);
-dkim_signature = dkim_exim_sign(dkim_fd,
-                               dkim->dkim_private_key,
-                               dkim->dkim_domain,
-                               dkim->dkim_selector,
-                               dkim->dkim_canon,
-                               dkim->dkim_sign_headers);
-if (dkim_signature)
-  siglen = Ustrlen(dkim_signature);
-else if (dkim->dkim_strict)
-  {
-  uschar *dkim_strict_result = expand_string(dkim->dkim_strict);
-  if (dkim_strict_result)
-    if ( (strcmpic(dkim->dkim_strict,US"1") == 0) ||
-        (strcmpic(dkim->dkim_strict,US"true") == 0) )
-      {
-      /* Set errno to something halfway meaningful */
-      save_errno = EACCES;
-      log_write(0, LOG_MAIN, "DKIM: message could not be signed,"
-       " and dkim_strict is set. Deferring message delivery.");
-      rc = FALSE;
-      goto CLEANUP;
-      }
-  }
-
-#ifndef HAVE_LINUX_SENDFILE
-if (options & topt_use_bdat)
-#endif
-  k_file_size = lseek(dkim_fd, 0, SEEK_END); /* Fetch file size */
-
-if (options & topt_use_bdat)
-  {
-
-  /* On big messages output a precursor chunk to get any pipelined
-  MAIL & RCPT commands flushed, then reap the responses so we can
-  error out on RCPT rejects before sending megabytes. */
-
-  if (siglen + k_file_size > DELIVER_OUT_BUFFER_SIZE && siglen > 0)
-    {
-    if (  tctx->chunk_cb(out_fd, tctx, siglen, 0) != OK
-       || !transport_write_block(out_fd, dkim_signature, siglen)
-       || tctx->chunk_cb(out_fd, tctx, 0, tc_reap_prev) != OK
-       )
-      goto err;
-    siglen = 0;
-    }
-
-  if (tctx->chunk_cb(out_fd, tctx, siglen + k_file_size, tc_chunk_last) != OK)
-    goto err;
-  }
-
-if(siglen > 0 && !transport_write_block(out_fd, dkim_signature, siglen))
-  goto err;
-
-#ifdef HAVE_LINUX_SENDFILE
-/* We can use sendfile() to shove the file contents
-   to the socket. However only if we don't use TLS,
-   as then there's another layer of indirection
-   before the data finally hits the socket. */
-if (tls_out.active != out_fd)
-  {
-  ssize_t copied = 0;
-  off_t offset = 0;
-
-  /* Rewind file */
-  lseek(dkim_fd, 0, SEEK_SET);
-
-  while(copied >= 0 && offset < k_file_size)
-    copied = sendfile(out_fd, dkim_fd, &offset, k_file_size - offset);
-  if (copied < 0)
-    goto err;
-  }
-else
-
-#endif
-
-  {
-  /* Rewind file */
-  lseek(dkim_fd, 0, SEEK_SET);
-
-  /* Send file down the original fd */
-  while((sread = read(dkim_fd, deliver_out_buffer, DELIVER_OUT_BUFFER_SIZE)) >0)
-    {
-    char *p = deliver_out_buffer;
-    /* write the chunk */
-
-    while (sread)
-      {
-#ifdef SUPPORT_TLS
-      wwritten = tls_out.active == out_fd
-       ? tls_write(FALSE, US p, sread)
-       : write(out_fd, p, sread);
-#else
-      wwritten = write(out_fd, p, sread);
-#endif
-      if (wwritten == -1)
-       goto err;
-      p += wwritten;
-      sread -= wwritten;
-      }
-    }
-
-  if (sread == -1)
-    {
-    save_errno = errno;
-    rc = FALSE;
-    }
-  }
-
-CLEANUP:
-  /* unlink -K file */
-  (void)close(dkim_fd);
-  Uunlink(dkim_spool_name);
-  errno = save_errno;
-  return rc;
-
-err:
-  save_errno = errno;
-  rc = FALSE;
-  goto CLEANUP;
+  transport_write_block(tctx, deliver_out_buffer, len, FALSE);
 }
 
-#endif
 
 
 
@@ -1230,9 +1175,9 @@ err:
 the real work, passing over all the arguments from this function. Otherwise,
 set up a filtering process, fork another process to call the internal function
 to write to the filter, and in this process just suck from the filter and write
-down the given fd. At the end, tidy up the pipes and the processes.
+down the fd in the transport context. At the end, tidy up the pipes and the
+processes.
 
-XXX
 Arguments:     as for internal_transport_write_message() above
 
 Returns:       TRUE on success; FALSE (with errno) for any failure
@@ -1240,18 +1185,15 @@ Returns:       TRUE on success; FALSE (with errno) for any failure
 */
 
 BOOL
-transport_write_message(int fd, transport_ctx * tctx, int size_limit)
+transport_write_message(transport_ctx * tctx, int size_limit)
 {
-unsigned wck_flags;
 BOOL last_filter_was_NL = TRUE;
+BOOL save_spool_file_wireformat = f.spool_file_wireformat;
 int rc, len, yield, fd_read, fd_write, save_errno;
 int pfd[2] = {-1, -1};
 pid_t filter_pid, write_pid;
-static transport_ctx dummy_tctx = {0};
 
-if (!tctx) tctx = &dummy_tctx;
-
-transport_filter_timed_out = FALSE;
+f.transport_filter_timed_out = FALSE;
 
 /* If there is no filter command set up, call the internal function that does
 the actual work, passing it the incoming fd, and return its result. */
@@ -1260,13 +1202,12 @@ if (  !transport_filter_argv
    || !*transport_filter_argv
    || !**transport_filter_argv
    )
-  return internal_transport_write_message(fd, tctx, size_limit);
+  return internal_transport_write_message(tctx, size_limit);
 
 /* Otherwise the message must be written to a filter process and read back
 before being written to the incoming fd. First set up the special processing to
 be done during the copying. */
 
-wck_flags = tctx->options & topt_use_crlf;
 nl_partial_match = -1;
 
 if (tctx->check_string && tctx->escape_string)
@@ -1290,10 +1231,13 @@ save_errno = 0;
 yield = FALSE;
 write_pid = (pid_t)(-1);
 
-(void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
-filter_pid = child_open(USS transport_filter_argv, NULL, 077,
- &fd_write, &fd_read, FALSE);
-(void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) & ~FD_CLOEXEC);
+  {
+  int bits = fcntl(tctx->u.fd, F_GETFD);
+  (void)fcntl(tctx->u.fd, F_SETFD, bits | FD_CLOEXEC);
+  filter_pid = child_open(USS transport_filter_argv, NULL, 077,
+   &fd_write, &fd_read, FALSE);
+  (void)fcntl(tctx->u.fd, F_SETFD, bits & ~FD_CLOEXEC);
+  }
 if (filter_pid < 0) goto TIDY_UP;      /* errno set */
 
 DEBUG(D_transport)
@@ -1312,10 +1256,11 @@ if ((write_pid = fork()) == 0)
   (void)close(pfd[pipe_read]);
   nl_check_length = nl_escape_length = 0;
 
+  tctx->u.fd = fd_write;
   tctx->check_string = tctx->escape_string = NULL;
   tctx->options &= ~(topt_use_crlf | topt_end_dot | topt_use_bdat);
 
-  rc = internal_transport_write_message(fd_write, tctx, size_limit);
+  rc = internal_transport_write_message(tctx, size_limit);
 
   save_errno = errno;
   if (  write(pfd[pipe_write], (void *)&rc, sizeof(BOOL))
@@ -1324,9 +1269,11 @@ if ((write_pid = fork()) == 0)
         != sizeof(int)
      || write(pfd[pipe_write], (void *)&tctx->addr->more_errno, sizeof(int))
         != sizeof(int)
+     || write(pfd[pipe_write], (void *)&tctx->addr->delivery_time, sizeof(struct timeval))
+        != sizeof(struct timeval)
      )
     rc = FALSE;        /* compiler quietening */
-  _exit(0);
+  exim_underbar_exit(0, US"tpt-filter");
   }
 save_errno = errno;
 
@@ -1346,7 +1293,7 @@ if (write_pid < 0)
 
 /* When testing, let the subprocess get going */
 
-if (running_in_test_harness) millisleep(250);
+testharness_pause_ms(250);
 
 DEBUG(D_transport)
   debug_printf("process %d writing to transport filter\n", (int)write_pid);
@@ -1360,20 +1307,22 @@ DEBUG(D_transport) debug_printf("copying from the filter\n");
 
 /* Copy the output of the filter, remembering if the last character was NL. If
 no data is returned, that counts as "ended with NL" (default setting of the
-variable is TRUE). */
+variable is TRUE).  The output should always be unix-format as we converted
+any wireformat source on writing input to the filter. */
 
+f.spool_file_wireformat = FALSE;
 chunk_ptr = deliver_out_buffer;
 
 for (;;)
   {
   sigalrm_seen = FALSE;
-  alarm(transport_filter_timeout);
+  ALARM(transport_filter_timeout);
   len = read(fd_read, deliver_in_buffer, DELIVER_IN_BUFFER_SIZE);
-  alarm(0);
+  ALARM_CLR(0);
   if (sigalrm_seen)
     {
     errno = ETIMEDOUT;
-    transport_filter_timed_out = TRUE;
+    f.transport_filter_timed_out = TRUE;
     goto TIDY_UP;
     }
 
@@ -1382,7 +1331,7 @@ for (;;)
 
   if (len > 0)
     {
-    if (!write_chunk(fd, tctx, deliver_in_buffer, len)) goto TIDY_UP;
+    if (!write_chunk(tctx, deliver_in_buffer, len)) goto TIDY_UP;
     last_filter_was_NL = (deliver_in_buffer[len-1] == '\n');
     }
 
@@ -1401,6 +1350,7 @@ there has been an error, kill the processes before waiting for them, just to be
 sure. Also apply a paranoia timeout. */
 
 TIDY_UP:
+f.spool_file_wireformat = save_spool_file_wireformat;
 save_errno = errno;
 
 (void)close(fd_read);
@@ -1432,15 +1382,22 @@ if (write_pid > 0)
   {
   rc = child_close(write_pid, 30);
   if (yield)
-    {
     if (rc == 0)
       {
       BOOL ok;
-      int dummy = read(pfd[pipe_read], (void *)&ok, sizeof(BOOL));
-      if (!ok)
+      if (read(pfd[pipe_read], (void *)&ok, sizeof(BOOL)) != sizeof(BOOL))
+       {
+       DEBUG(D_transport)
+         debug_printf("pipe read from writing process: %s\n", strerror(errno));
+       save_errno = ERRNO_FILTER_FAIL;
+        yield = FALSE;
+       }
+      else if (!ok)
         {
-        dummy = read(pfd[pipe_read], (void *)&save_errno, sizeof(int));
-        dummy = read(pfd[pipe_read], (void *)&(tctx->addr->more_errno), sizeof(int));
+       int dummy = read(pfd[pipe_read], (void *)&save_errno, sizeof(int));
+        dummy = read(pfd[pipe_read], (void *)&tctx->addr->more_errno, sizeof(int));
+        dummy = read(pfd[pipe_read], (void *)&tctx->addr->delivery_time, sizeof(struct timeval));
+       dummy = dummy;          /* compiler quietening */
         yield = FALSE;
         }
       }
@@ -1451,7 +1408,6 @@ if (write_pid > 0)
       tctx->addr->more_errno = rc;
       DEBUG(D_transport) debug_printf("writing process returned %d\n", rc);
       }
-    }
   }
 (void)close(pfd[pipe_read]);
 
@@ -1462,10 +1418,11 @@ filter was not NL, insert a NL to make the SMTP protocol work. */
 if (yield)
   {
   nl_check_length = nl_escape_length = 0;
+  f.spool_file_wireformat = FALSE;
   if (  tctx->options & topt_end_dot
      && ( last_filter_was_NL
-        ? !write_chunk(fd, tctx, US".\n", 2)
-       : !write_chunk(fd, tctx, US"\n.\n", 3)
+        ? !write_chunk(tctx, US".\n", 2)
+       : !write_chunk(tctx, US"\n.\n", 3)
      )  )
     yield = FALSE;
 
@@ -1473,7 +1430,7 @@ if (yield)
 
   else
     yield = (len = chunk_ptr - deliver_out_buffer) <= 0
-         || transport_write_block(fd, deliver_out_buffer, len);
+         || transport_write_block(tctx, deliver_out_buffer, len, FALSE);
   }
 else
   errno = save_errno;      /* From some earlier error */
@@ -1530,9 +1487,7 @@ Returns:    nothing
 void
 transport_update_waiting(host_item *hostlist, uschar *tpname)
 {
-uschar buffer[256];
 const uschar *prevname = US"";
-host_item *host;
 open_db dbblock;
 open_db *dbm_file;
 
@@ -1540,19 +1495,19 @@ DEBUG(D_transport) debug_printf("updating wait-%s database\n", tpname);
 
 /* Open the database for this transport */
 
-sprintf(CS buffer, "wait-%.200s", tpname);
-dbm_file = dbfn_open(buffer, O_RDWR, &dbblock, TRUE);
-if (dbm_file == NULL) return;
+if (!(dbm_file = dbfn_open(string_sprintf("wait-%.200s", tpname),
+                     O_RDWR, &dbblock, TRUE, TRUE)))
+  return;
 
 /* Scan the list of hosts for which this message is waiting, and ensure
 that the message id is in each host record. */
 
-for (host = hostlist; host!= NULL; host = host->next)
+for (host_item * host = hostlist; host; host = host->next)
   {
   BOOL already = FALSE;
   dbdata_wait *host_record;
-  uschar *s;
-  int i, host_length;
+  int host_length;
+  uschar buffer[256];
 
   /* Skip if this is the same host as we just processed; otherwise remember
   the name for next time. */
@@ -1562,10 +1517,9 @@ for (host = hostlist; host!= NULL; host = host->next)
 
   /* Look up the host record; if there isn't one, make an empty one. */
 
-  host_record = dbfn_read(dbm_file, host->name);
-  if (host_record == NULL)
+  if (!(host_record = dbfn_read(dbm_file, host->name)))
     {
-    host_record = store_get(sizeof(dbdata_wait) + MESSAGE_ID_LENGTH);
+    host_record = store_get(sizeof(dbdata_wait) + MESSAGE_ID_LENGTH, FALSE);
     host_record->count = host_record->sequence = 0;
     }
 
@@ -1575,29 +1529,24 @@ for (host = hostlist; host!= NULL; host = host->next)
 
   /* Search the record to see if the current message is already in it. */
 
-  for (s = host_record->text; s < host_record->text + host_length;
+  for (uschar * s = host_record->text; s < host_record->text + host_length;
        s += MESSAGE_ID_LENGTH)
-    {
     if (Ustrncmp(s, message_id, MESSAGE_ID_LENGTH) == 0)
       { already = TRUE; break; }
-    }
 
   /* If we haven't found this message in the main record, search any
   continuation records that exist. */
 
-  for (i = host_record->sequence - 1; i >= 0 && !already; i--)
+  for (int i = host_record->sequence - 1; i >= 0 && !already; i--)
     {
     dbdata_wait *cont;
     sprintf(CS buffer, "%.200s:%d", host->name, i);
-    cont = dbfn_read(dbm_file, buffer);
-    if (cont != NULL)
+    if ((cont = dbfn_read(dbm_file, buffer)))
       {
       int clen = cont->count * MESSAGE_ID_LENGTH;
-      for (s = cont->text; s < cont->text + clen; s += MESSAGE_ID_LENGTH)
-        {
+      for (uschar * s = cont->text; s < cont->text + clen; s += MESSAGE_ID_LENGTH)
         if (Ustrncmp(s, message_id, MESSAGE_ID_LENGTH) == 0)
           { already = TRUE; break; }
-        }
       }
     }
 
@@ -1612,12 +1561,17 @@ for (host = hostlist; host!= NULL; host = host->next)
 
   /* If this record is full, write it out with a new name constructed
   from the sequence number, increase the sequence number, and empty
-  the record. */
+  the record.  If we're doing a two-phase queue run initial phase, ping the
+  daemon to consider running a delivery on this host. */
 
   if (host_record->count >= WAIT_NAME_MAX)
     {
     sprintf(CS buffer, "%.200s:%d", host->name, host_record->sequence);
     dbfn_write(dbm_file, buffer, host_record, sizeof(dbdata_wait) + host_length);
+#ifdef EXPERIMENTAL_QUEUE_RAMP
+    if (f.queue_2stage && queue_fast_ramp && !queue_run_in_order)
+      queue_notify_daemon(message_id);
+#endif
     host_record->sequence++;
     host_record->count = 0;
     host_length = 0;
@@ -1629,7 +1583,7 @@ for (host = hostlist; host!= NULL; host = host->next)
   else
     {
     dbdata_wait *newr =
-      store_get(sizeof(dbdata_wait) + host_length + MESSAGE_ID_LENGTH);
+      store_get(sizeof(dbdata_wait) + host_length + MESSAGE_ID_LENGTH, FALSE);
     memcpy(newr, host_record, sizeof(dbdata_wait) + host_length);
     host_record = newr;
     }
@@ -1693,7 +1647,6 @@ dbdata_wait *host_record;
 int host_length;
 open_db dbblock;
 open_db *dbm_file;
-uschar buffer[256];
 
 int         i;
 struct stat statbuf;
@@ -1720,9 +1673,9 @@ if (local_message_max > 0 && continue_sequence >= local_message_max)
 
 /* Open the waiting information database. */
 
-sprintf(CS buffer, "wait-%.200s", transport_name);
-dbm_file = dbfn_open(buffer, O_RDWR, &dbblock, TRUE);
-if (dbm_file == NULL) return FALSE;
+if (!(dbm_file = dbfn_open(string_sprintf("wait-%.200s", transport_name),
+                         O_RDWR, &dbblock, TRUE, TRUE)))
+  return FALSE;
 
 /* See if there is a record for this host; if not, there's nothing to do. */
 
@@ -1765,7 +1718,7 @@ while (1)
 
   /* create an array to read entire message queue into memory for processing  */
 
-  msgq = (msgq_t*) malloc(sizeof(msgq_t) * host_record->count);
+  msgq = store_get(sizeof(msgq_t) * host_record->count, FALSE);
   msgq_count = host_record->count;
   msgq_actual = msgq_count;
 
@@ -1773,7 +1726,7 @@ while (1)
     {
     msgq[i].bKeep = TRUE;
 
-    Ustrncpy(msgq[i].message_id, host_record->text + (i * MESSAGE_ID_LENGTH),
+    Ustrncpy_nt(msgq[i].message_id, host_record->text + (i * MESSAGE_ID_LENGTH), 
       MESSAGE_ID_LENGTH);
     msgq[i].message_id[MESSAGE_ID_LENGTH] = 0;
     }
@@ -1792,16 +1745,14 @@ while (1)
   for (i = msgq_count - 1; i >= 0; --i) if (msgq[i].bKeep)
     {
     uschar subdir[2];
+    uschar * mid = msgq[i].message_id;
 
-    subdir[0] = split_spool_directory ? msgq[i].message_id[5] : 0;
-    subdir[1] = 0;
-
-    if (Ustat(spool_fname(US"input", subdir, msgq[i].message_id, US"-D"),
-             &statbuf) != 0)
+    set_subdir_str(subdir, mid, 0);
+    if (Ustat(spool_fname(US"input", subdir, mid, US"-D"), &statbuf) != 0)
       msgq[i].bKeep = FALSE;
-    else if (!oicf_func || oicf_func(msgq[i].message_id, oicf_data))
+    else if (!oicf_func || oicf_func(mid, oicf_data))
       {
-      Ustrcpy(new_message_id, msgq[i].message_id);
+      Ustrcpy_nt(new_message_id, mid);
       msgq[i].bKeep = FALSE;
       bFound = TRUE;
       break;
@@ -1838,17 +1789,16 @@ while (1)
       }
     }
 
-/* Jeremy: check for a continuation record, this code I do not know how to
-test but the code should work */
+  /* Check for a continuation record. */
 
   while (host_length <= 0)
     {
-    int i;
     dbdata_wait * newr = NULL;
+    uschar buffer[256];
 
     /* Search for a continuation */
 
-    for (i = host_record->sequence - 1; i >= 0 && !newr; i--)
+    for (int i = host_record->sequence - 1; i >= 0 && !newr; i--)
       {
       sprintf(CS buffer, "%.200s:%d", hostname, i);
       newr = dbfn_read(dbm_file, buffer);
@@ -1872,10 +1822,7 @@ test but the code should work */
     }
 
   if (bFound)          /* Usual exit from main loop */
-    {
-    free (msgq);
     break;
-    }
 
   /* If host_length <= 0 we have emptied a record and not found a good message,
   and there are no continuation records. Otherwise there is a continuation
@@ -1898,8 +1845,6 @@ test but the code should work */
     dbfn_close(dbm_file);
     return FALSE;
     }
-
-  free(msgq);
   }            /* we need to process a continuation record */
 
 /* Control gets here when an existing message has been encountered; its
@@ -1923,6 +1868,70 @@ return TRUE;
 *    Deliver waiting message down same socket    *
 *************************************************/
 
+/* Just the regain-root-privilege exec portion */
+void
+transport_do_pass_socket(const uschar *transport_name, const uschar *hostname,
+  const uschar *hostaddress, uschar *id, int socket_fd)
+{
+int i = 20;
+const uschar **argv;
+
+/* Set up the calling arguments; use the standard function for the basics,
+but we have a number of extras that may be added. */
+
+argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0);
+
+if (f.smtp_authenticated)                      argv[i++] = US"-MCA";
+if (smtp_peer_options & OPTION_CHUNKING)       argv[i++] = US"-MCK";
+if (smtp_peer_options & OPTION_DSN)            argv[i++] = US"-MCD";
+if (smtp_peer_options & OPTION_PIPE)           argv[i++] = US"-MCP";
+if (smtp_peer_options & OPTION_SIZE)           argv[i++] = US"-MCS";
+#ifndef DISABLE_TLS
+if (smtp_peer_options & OPTION_TLS)
+  if (tls_out.active.sock >= 0 || continue_proxy_cipher)
+    {
+    argv[i++] = US"-MCt";
+    argv[i++] = sending_ip_address;
+    argv[i++] = string_sprintf("%d", sending_port);
+    argv[i++] = tls_out.active.sock >= 0 ? tls_out.cipher : continue_proxy_cipher;
+    }
+  else
+    argv[i++] = US"-MCT";
+#endif
+
+if (queue_run_pid != (pid_t)0)
+  {
+  argv[i++] = US"-MCQ";
+  argv[i++] = string_sprintf("%d", queue_run_pid);
+  argv[i++] = string_sprintf("%d", queue_run_pipe);
+  }
+
+argv[i++] = US"-MC";
+argv[i++] = US transport_name;
+argv[i++] = US hostname;
+argv[i++] = US hostaddress;
+argv[i++] = string_sprintf("%d", continue_sequence + 1);
+argv[i++] = id;
+argv[i++] = NULL;
+
+/* Arrange for the channel to be on stdin. */
+
+if (socket_fd != 0)
+  {
+  (void)dup2(socket_fd, 0);
+  (void)close(socket_fd);
+  }
+
+DEBUG(D_exec) debug_print_argv(argv);
+exim_nullstd();                          /* Ensure std{out,err} exist */
+execv(CS argv[0], (char *const *)argv);
+
+DEBUG(D_any) debug_printf("execv failed: %s\n", strerror(errno));
+_exit(errno);         /* Note: must be _exit(), NOT exit() */
+}
+
+
+
 /* Fork a new exim process to deliver the message, and do a re-exec, both to
 get a clean delivery process, and to regain root privilege in cases where it
 has been given away.
@@ -1948,61 +1957,20 @@ DEBUG(D_transport) debug_printf("transport_pass_socket entered\n");
 
 if ((pid = fork()) == 0)
   {
-  int i = 17;
-  const uschar **argv;
-
   /* Disconnect entirely from the parent process. If we are running in the
   test harness, wait for a bit to allow the previous process time to finish,
   write the log, etc., so that the output is always in the same order for
   automatic comparison. */
 
-  if ((pid = fork()) != 0) _exit(EXIT_SUCCESS);
-  if (running_in_test_harness) sleep(1);
-
-  /* Set up the calling arguments; use the standard function for the basics,
-  but we have a number of extras that may be added. */
-
-  argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0);
-
-  if (smtp_authenticated) argv[i++] = US"-MCA";
-
-  if (smtp_peer_options & PEER_OFFERED_CHUNKING) argv[i++] = US"-MCK";
-  if (smtp_peer_options & PEER_OFFERED_DSN) argv[i++] = US"-MCD";
-  if (smtp_peer_options & PEER_OFFERED_PIPE) argv[i++] = US"-MCP";
-  if (smtp_peer_options & PEER_OFFERED_SIZE) argv[i++] = US"-MCS";
-#ifdef SUPPORT_TLS
-  if (smtp_peer_options & PEER_OFFERED_TLS) argv[i++] = US"-MCT";
-#endif
-
-  if (queue_run_pid != (pid_t)0)
-    {
-    argv[i++] = US"-MCQ";
-    argv[i++] = string_sprintf("%d", queue_run_pid);
-    argv[i++] = string_sprintf("%d", queue_run_pipe);
-    }
-
-  argv[i++] = US"-MC";
-  argv[i++] = US transport_name;
-  argv[i++] = US hostname;
-  argv[i++] = US hostaddress;
-  argv[i++] = string_sprintf("%d", continue_sequence + 1);
-  argv[i++] = id;
-  argv[i++] = NULL;
-
-  /* Arrange for the channel to be on stdin. */
-
-  if (socket_fd != 0)
+  if ((pid = fork()) != 0)
     {
-    (void)dup2(socket_fd, 0);
-    (void)close(socket_fd);
+    DEBUG(D_transport) debug_printf("transport_pass_socket succeeded (final-pid %d)\n", pid);
+    _exit(EXIT_SUCCESS);
     }
+  testharness_pause_ms(1000);
 
-  DEBUG(D_exec) debug_print_argv(argv);
-  exim_nullstd();                          /* Ensure std{out,err} exist */
-  execv(CS argv[0], (char *const *)argv);
-
-  DEBUG(D_any) debug_printf("execv failed: %s\n", strerror(errno));
-  _exit(errno);         /* Note: must be _exit(), NOT exit() */
+  transport_do_pass_socket(transport_name, hostname, hostaddress,
+    id, socket_fd);
   }
 
 /* If the process creation succeeded, wait for the first-level child, which
@@ -2013,7 +1981,7 @@ if (pid > 0)
   {
   int rc;
   while ((rc = wait(&status)) != pid && (rc >= 0 || errno != ECHILD));
-  DEBUG(D_transport) debug_printf("transport_pass_socket succeeded\n");
+  DEBUG(D_transport) debug_printf("transport_pass_socket succeeded (inter-pid %d)\n", pid);
   return TRUE;
   }
 else
@@ -2056,21 +2024,20 @@ transport_set_up_command(const uschar ***argvptr, uschar *cmd,
   BOOL expand_arguments, int expand_failed, address_item *addr,
   uschar *etext, uschar **errptr)
 {
-address_item *ad;
 const uschar **argv;
 uschar *s, *ss;
 int address_count = 0;
 int argcount = 0;
-int i, max_args;
+int max_args;
 
 /* Get store in which to build an argument list. Count the number of addresses
 supplied, and allow for that many arguments, plus an additional 60, which
 should be enough for anybody. Multiple addresses happen only when the local
 delivery batch option is set. */
 
-for (ad = addr; ad != NULL; ad = ad->next) address_count++;
+for (address_item * ad = addr; ad; ad = ad->next) address_count++;
 max_args = address_count + 60;
-*argvptr = argv = store_get((max_args+1)*sizeof(uschar *));
+*argvptr = argv = store_get((max_args+1)*sizeof(uschar *), FALSE);
 
 /* Split the command up into arguments terminated by white space. Lose
 trailing space at the start and end. Double-quoted arguments can contain \\ and
@@ -2080,22 +2047,23 @@ arguments are verbatim. Copy each argument into a new string. */
 s = cmd;
 while (isspace(*s)) s++;
 
-while (*s != 0 && argcount < max_args)
+for (; *s != 0 && argcount < max_args; argcount++)
   {
   if (*s == '\'')
     {
     ss = s + 1;
     while (*ss != 0 && *ss != '\'') ss++;
-    argv[argcount++] = ss = store_get(ss - s++);
+    argv[argcount] = ss = store_get(ss - s++, is_tainted(cmd));
     while (*s != 0 && *s != '\'') *ss++ = *s++;
     if (*s != 0) s++;
     *ss++ = 0;
     }
-  else argv[argcount++] = string_copy(string_dequote(CUSS &s));
+  else
+    argv[argcount] = string_dequote(CUSS &s);
   while (isspace(*s)) s++;
   }
 
-argv[argcount] = (uschar *)0;
+argv[argcount] = US 0;
 
 /* If *s != 0 we have run out of argument slots. */
 
@@ -2131,8 +2099,8 @@ $recipients. */
 DEBUG(D_transport)
   {
   debug_printf("direct command:\n");
-  for (i = 0; argv[i] != (uschar *)0; i++)
-    debug_printf("  argv[%d] = %s\n", i, string_printing(argv[i]));
+  for (int i = 0; argv[i]; i++)
+    debug_printf("  argv[%d] = '%s'\n", i, string_printing(argv[i]));
   }
 
 if (expand_arguments)
@@ -2141,7 +2109,7 @@ if (expand_arguments)
     addr->parent != NULL &&
     Ustrcmp(addr->parent->address, "system-filter") == 0;
 
-  for (i = 0; argv[i] != (uschar *)0; i++)
+  for (int i = 0; argv[i] != US 0; i++)
     {
 
     /* Handle special fudge for passing an address list */
@@ -2165,10 +2133,11 @@ if (expand_arguments)
         memmove(argv + i + 1 + additional, argv + i + 1,
           (argcount - i)*sizeof(uschar *));
 
-      for (ad = addr; ad != NULL; ad = ad->next) {
-          argv[i++] = ad->address;
-          argcount++;
-      }
+      for (address_item * ad = addr; ad; ad = ad->next)
+        {
+       argv[i++] = ad->address;
+       argcount++;
+       }
 
       /* Subtract one since we replace $pipe_addresses */
       argcount--;
@@ -2181,10 +2150,10 @@ if (expand_arguments)
         (Ustrcmp(argv[i], "$address_pipe") == 0 ||
          Ustrcmp(argv[i], "${address_pipe}") == 0))
       {
-      int address_pipe_i;
       int address_pipe_argcount = 0;
       int address_pipe_max_args;
       uschar **address_pipe_argv;
+      BOOL tainted;
 
       /* We can never have more then the argv we will be loading into */
       address_pipe_max_args = max_args - argcount + 1;
@@ -2193,10 +2162,11 @@ if (expand_arguments)
         debug_printf("address_pipe_max_args=%d\n", address_pipe_max_args);
 
       /* We allocate an additional for (uschar *)0 */
-      address_pipe_argv = store_get((address_pipe_max_args+1)*sizeof(uschar *));
+      address_pipe_argv = store_get((address_pipe_max_args+1)*sizeof(uschar *), FALSE);
 
       /* +1 because addr->local_part[0] == '|' since af_force_command is set */
       s = expand_string(addr->local_part + 1);
+      tainted = is_tainted(s);
 
       if (s == NULL || *s == '\0')
         {
@@ -2215,7 +2185,7 @@ if (expand_arguments)
           {
           ss = s + 1;
           while (*ss != 0 && *ss != '\'') ss++;
-          address_pipe_argv[address_pipe_argcount++] = ss = store_get(ss - s++);
+          address_pipe_argv[address_pipe_argcount++] = ss = store_get(ss - s++, tainted);
           while (*s != 0 && *s != '\'') *ss++ = *s++;
           if (*s != 0) s++;
           *ss++ = 0;
@@ -2225,7 +2195,7 @@ if (expand_arguments)
         while (isspace(*s)) s++; /* strip space after arg */
         }
 
-      address_pipe_argv[address_pipe_argcount] = (uschar *)0;
+      address_pipe_argv[address_pipe_argcount] = US 0;
 
       /* If *s != 0 we have run out of argument slots. */
       if (*s != 0)
@@ -2261,7 +2231,7 @@ if (expand_arguments)
        */
       if (address_pipe_argcount > 1)
         memmove(
-          /* current position + additonal args */
+          /* current position + additional args */
           argv + i + address_pipe_argcount,
           /* current position + 1 (for the (uschar *)0 at the end) */
           argv + i + 1,
@@ -2272,8 +2242,8 @@ if (expand_arguments)
       /* Now we fill in the slots we just moved argv out of
        * [argv 0][argv 1][argv 2=pipeargv[0]][argv 3=pipeargv[1]][old argv 3][0]
        */
-      for (address_pipe_i = 0;
-           address_pipe_argv[address_pipe_i] != (uschar *)0;
+      for (int address_pipe_i = 0;
+           address_pipe_argv[address_pipe_i] != US 0;
            address_pipe_i++)
         {
         argv[i++] = address_pipe_argv[address_pipe_i];
@@ -2290,16 +2260,16 @@ if (expand_arguments)
     else
       {
       const uschar *expanded_arg;
-      enable_dollar_recipients = allow_dollar_recipients;
+      f.enable_dollar_recipients = allow_dollar_recipients;
       expanded_arg = expand_cstring(argv[i]);
-      enable_dollar_recipients = FALSE;
+      f.enable_dollar_recipients = FALSE;
 
-      if (expanded_arg == NULL)
+      if (!expanded_arg)
         {
         uschar *msg = string_sprintf("Expansion of \"%s\" "
           "from command \"%s\" in %s failed: %s",
           argv[i], cmd, etext, expand_string_message);
-        if (addr != NULL)
+        if (addr)
           {
           addr->transport_return = expand_failed;
           addr->message = msg;
@@ -2314,7 +2284,7 @@ if (expand_arguments)
   DEBUG(D_transport)
     {
     debug_printf("direct command after expansion:\n");
-    for (i = 0; argv[i] != (uschar *)0; i++)
+    for (int i = 0; argv[i] != US 0; i++)
       debug_printf("  argv[%d] = %s\n", i, string_printing(argv[i]));
     }
   }
@@ -2322,6 +2292,7 @@ if (expand_arguments)
 return TRUE;
 }
 
+#endif /*!MACRO_PREDEF*/
 /* vi: aw ai sw=2
 */
 /* End of transport.c */