tidying
[exim.git] / src / src / transport.c
index 4f7538d2462660fad3c21e9660148908ab4203a7..ee51206638bfb27360e722d575596a6e6c91d585 100644 (file)
@@ -108,10 +108,23 @@ optionlist optionlist_transports[] = {
                  (void *)offsetof(transport_instance, uid) }
 };
 
-int optionlist_transports_size =
-  sizeof(optionlist_transports)/sizeof(optionlist);
+int optionlist_transports_size = nelem(optionlist_transports);
 
 
+void
+readconf_options_transports(void)
+{
+struct transport_info * ti;
+
+readconf_options_from_list(optionlist_transports, nelem(optionlist_transports), US"TRANSPORTS", NULL);
+
+for (ti = transports_available; ti->driver_name[0]; ti++)
+  {
+  macro_create(string_sprintf("_DRIVER_TRANSPORT_%T", ti->driver_name), US"y", FALSE, TRUE);
+  readconf_options_from_list(ti->options, (unsigned)*ti->options_count, US"TRANSPORT", ti->driver_name);
+  }
+}
+
 /*************************************************
 *             Initialize transport list           *
 *************************************************/
@@ -139,14 +152,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 (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,
@@ -236,10 +246,12 @@ for (i = 0; i < 100; i++)
   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);
+#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)
@@ -357,7 +369,7 @@ Arguments:
   fd         file descript to write to
   chunk      pointer to data to write
   len        length of data to write
-  usr_crlf   TRUE if CR LF is wanted at the end of each line
+  tctx       transport context - processing to be done during output
 
 In addition, the static nl_xxx variables must be set as required.
 
@@ -365,7 +377,7 @@ Returns:     TRUE on success, FALSE on failure (with errno preserved)
 */
 
 static BOOL
-write_chunk(int fd, uschar *chunk, int len, BOOL use_crlf)
+write_chunk(int fd, transport_ctx * tctx, uschar *chunk, int len)
 {
 uschar *start = chunk;
 uschar *end = chunk + len;
@@ -408,17 +420,30 @@ possible. */
 
 for (ptr = start; ptr < end; ptr++)
   {
-  int ch;
+  int ch, len;
 
   /* Flush the buffer if it has reached the threshold - we want to leave enough
   room for the next uschar, plus a possible extra CR for an LF, plus the escape
   string. */
 
-  if (chunk_ptr - deliver_out_buffer > mlen)
+  if ((len = chunk_ptr - deliver_out_buffer) > mlen)
     {
-    if (!transport_write_block(fd, deliver_out_buffer,
-          chunk_ptr - deliver_out_buffer))
-      return FALSE;
+    DEBUG(D_transport) debug_printf("flushing headers buffer\n");
+
+    /* 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->chunk_cb(tctx, (unsigned)len, 0) != OK
+        || !transport_write_block(fd, deliver_out_buffer, len)
+        || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
+        )
+       return FALSE;
+      }
+    else
+      if (!transport_write_block(fd, deliver_out_buffer, len))
+       return FALSE;
     chunk_ptr = deliver_out_buffer;
     }
 
@@ -428,7 +453,7 @@ for (ptr = start; ptr < end; ptr++)
 
     /* Insert CR before NL if required */
 
-    if (use_crlf) *chunk_ptr++ = '\r';
+    if (tctx  &&  tctx->options & topt_use_crlf) *chunk_ptr++ = '\r';
     *chunk_ptr++ = '\n';
     transport_newlines++;
 
@@ -545,14 +570,14 @@ Arguments:
   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
-  use_crlf  to be passed on to write_chunk()
+  tctx      transport context - processing to be done during output
 
 Returns:    FALSE if writing failed
 */
 
 static BOOL
 write_env_to(address_item *p, struct aci **pplist, struct aci **pdlist,
-  BOOL *first, int fd, BOOL use_crlf)
+  BOOL *first, int fd, transport_ctx * tctx)
 {
 address_item *pp;
 struct aci *ppp;
@@ -574,7 +599,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, use_crlf))
+      if (!write_env_to(dup, pplist, pdlist, first, fd, tctx))
        return FALSE;
   if (!pp->parent) break;
   }
@@ -591,15 +616,15 @@ ppp->next = *pplist;
 *pplist = ppp;
 ppp->ptr = pp;
 
-if (!(*first) && !write_chunk(fd, US",\n ", 3, use_crlf)) return FALSE;
+if (!*first && !write_chunk(fd, tctx, US",\n ", 3)) return FALSE;
 *first = FALSE;
-return write_chunk(fd, pp->address, Ustrlen(pp->address), use_crlf);
+return write_chunk(fd, 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
@@ -608,20 +633,19 @@ Arguments:
   addr                  (chain of) addresses (for extra headers), or NULL;
                           only the first address is used
   fd                    file descriptor to write the message to
-  sendfn               function for output
-  use_crlf             turn NL into CR LF
-  rewrite_rules         chain of header rewriting rules
-  rewrite_existflags    flags for the rewriting rules
+  tctx                  transport context
+  sendfn               function for output (transport or verify)
 
 Returns:                TRUE on success; FALSE on failure.
 */
 BOOL
-transport_headers_send(address_item *addr, int fd, transport_instance * tblock,
-  BOOL (*sendfn)(int fd, uschar * s, int len, BOOL use_crlf),
-  BOOL use_crlf)
+transport_headers_send(int fd, transport_ctx * tctx,
+  BOOL (*sendfn)(int fd, 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;
 
 /* Then the message's headers. Don't write any that are flagged as "old";
 that means they were rewritten, or are a record of envelope rewriting, or
@@ -635,7 +659,7 @@ for (h = header_list; h; h = h->next) if (h->type != htype_old)
   int i;
   BOOL include_header = TRUE;
 
-  list = tblock->remove_headers;
+  list = tblock ? tblock->remove_headers : NULL;
   for (i = 0; i < 2; i++)    /* For remove_headers && addr->prop.remove_headers */
     {
     if (list)
@@ -668,7 +692,7 @@ for (h = header_list; h; h = h->next) if (h->type != htype_old)
 
   if (include_header)
     {
-    if (tblock->rewrite_rules)
+    if (tblock && tblock->rewrite_rules)
       {
       void *reset_point = store_get(0);
       header_line *hh;
@@ -676,7 +700,7 @@ for (h = header_list; h; h = h->next) if (h->type != htype_old)
       if ((hh = rewrite_header(h, NULL, NULL, tblock->rewrite_rules,
                  tblock->rewrite_existflags, FALSE)))
        {
-       if (!sendfn(fd, hh->text, hh->slen, use_crlf)) return FALSE;
+       if (!sendfn(fd, tctx, hh->text, hh->slen)) return FALSE;
        store_reset(reset_point);
        continue;     /* With the next header line */
        }
@@ -684,7 +708,7 @@ 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, h->text, h->slen, use_crlf)) return FALSE;
+    if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE;
     }
 
   /* Header removed */
@@ -719,7 +743,7 @@ if (addr)
       hprev = h;
       if (i == 1)
        {
-       if (!sendfn(fd, h->text, h->slen, use_crlf)) return FALSE;
+       if (!sendfn(fd, tctx, h->text, h->slen)) return FALSE;
        DEBUG(D_transport)
          debug_printf("added header line(s):\n%s---\n", h->text);
        }
@@ -733,7 +757,7 @@ up any other headers. An empty string or a forced expansion failure are
 noops. An added header string from a transport may not end with a newline;
 add one if it does not. */
 
-if ((list = CUS tblock->add_headers))
+if (tblock && (list = CUS tblock->add_headers))
   {
   int sep = '\n';
   uschar * s;
@@ -744,8 +768,8 @@ if ((list = CUS tblock->add_headers))
       int len = Ustrlen(s);
       if (len > 0)
        {
-       if (!sendfn(fd, s, len, use_crlf)) return FALSE;
-       if (s[len-1] != '\n' && !sendfn(fd, US"\n", 1, use_crlf))
+       if (!sendfn(fd, tctx, s, len)) return FALSE;
+       if (s[len-1] != '\n' && !sendfn(fd, tctx, US"\n", 1))
          return FALSE;
        DEBUG(D_transport)
          {
@@ -761,7 +785,7 @@ if ((list = CUS tblock->add_headers))
 
 /* Separate headers from body with a blank line */
 
-return sendfn(fd, US"\n", 1, use_crlf);
+return sendfn(fd, tctx, US"\n", 1);
 }
 
 
@@ -814,12 +838,12 @@ Arguments:
       end_dot               if TRUE, send a terminating "." line at the end
       no_headers            if TRUE, omit the headers
       no_body               if TRUE, omit the body
-    size_limit            if > 0, this is a limit to the size of message written;
+    check_string          a string to check for at the start of lines, or NULL
+    escape_string         a string to insert in front of any check string
+  size_limit              if > 0, this is a limit to the size of message written;
                             it is used when returning messages to their senders,
                             and is approximate rather than exact, owing to chunk
                             buffering
-    check_string          a string to check for at the start of lines, or NULL
-    escape_string         a string to insert in front of any check string
 
 Returns:                TRUE on success; FALSE (with errno) on failure.
                         In addition, the global variable transport_count
@@ -829,9 +853,7 @@ Returns:                TRUE on success; FALSE (with errno) on failure.
 static BOOL
 internal_transport_write_message(int fd, transport_ctx * tctx, int size_limit)
 {
-int written = 0;
 int len;
-BOOL use_crlf = (tctx->options & topt_use_crlf) != 0;
 
 /* Initialize pointer in output buffer. */
 
@@ -869,7 +891,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, buffer, n, use_crlf)) return FALSE;
+    if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
     }
 
   /* Add envelope-to: if requested */
@@ -882,19 +904,19 @@ if (!(tctx->options & topt_no_headers))
     struct aci *dlist = NULL;
     void *reset_point = store_get(0);
 
-    if (!write_chunk(fd, US"Envelope-to: ", 13, use_crlf)) return FALSE;
+    if (!write_chunk(fd, tctx, US"Envelope-to: ", 13)) return FALSE;
 
     /* 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, use_crlf))
+      if (!write_env_to(p, &plist, &dlist, &first, fd, tctx))
        return FALSE;
 
     /* Add a final newline and reset the store used for tracking duplicates */
 
-    if (!write_chunk(fd, US"\n", 1, use_crlf)) return FALSE;
+    if (!write_chunk(fd, tctx, US"\n", 1)) return FALSE;
     store_reset(reset_point);
     }
 
@@ -904,7 +926,7 @@ if (!(tctx->options & topt_no_headers))
     {
     uschar buffer[100];
     int n = sprintf(CS buffer, "Delivery-date: %s\n", tod_stamp(tod_full));
-    if (!write_chunk(fd, buffer, n, use_crlf)) return FALSE;
+    if (!write_chunk(fd, tctx, buffer, n)) return FALSE;
     }
 
   /* Then the message's headers. Don't write any that are flagged as "old";
@@ -913,10 +935,67 @@ 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(tctx->addr, fd, tctx->tblock, &write_chunk, use_crlf))
+  if (!transport_headers_send(fd, tctx, &write_chunk))
     return FALSE;
   }
 
+/* When doing RFC3030 CHUNKING output, work out how much data would be in a
+last-BDAT, consisting of the current write_chunk() output buffer fill
+(optimally, all of the headers - but it does not matter if we already had to
+flush that buffer with non-last BDAT prependix) plus the amount of body data
+(as expanded for CRLF lines).  Then create and write BDAT(s), and ensure
+that further use of write_chunk() will not prepend BDATs.
+The first BDAT written will also first flush any outstanding MAIL and RCPT
+commands which were buffered thans to PIPELINING.
+Commands go out (using a send()) from a different buffer to data (using a
+write()).  They might not end up in the same TCP segment, which is
+suboptimal. */
+
+if (tctx->options & topt_use_bdat)
+  {
+  off_t fsize;
+  int hsize, size = 0;
+
+  if ((hsize = chunk_ptr - deliver_out_buffer) < 0)
+    hsize = 0;
+  if (!(tctx->options & topt_no_body))
+    {
+    if ((fsize = lseek(deliver_datafile, 0, SEEK_END)) < 0) return FALSE;
+    fsize -= SPOOL_DATA_START_OFFSET;
+    if (size_limit > 0  &&  fsize > size_limit)
+      fsize = size_limit;
+    size = hsize + fsize;
+    if (tctx->options & topt_use_crlf)
+      size += body_linecount;  /* account for CRLF-expansion */
+    }
+
+  /* If the message is large, emit first a non-LAST chunk with just the
+  headers, and reap the command responses.  This lets us error out early
+  on RCPT rejects rather than sending megabytes of data.  Include headers
+  on the assumption they are cheap enough and some clever implementations
+  might errorcheck them too, on-the-fly, and reject that chunk. */
+
+  if (size > DELIVER_OUT_BUFFER_SIZE && hsize > 0)
+    {
+    DEBUG(D_transport)
+      debug_printf("sending small initial BDAT; hsize=%d\n", hsize);
+    if (  tctx->chunk_cb(tctx, hsize, 0) != OK
+       || !transport_write_block(fd, deliver_out_buffer, hsize)
+       || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
+       )
+      return FALSE;
+    chunk_ptr = deliver_out_buffer;
+    size -= hsize;
+    }
+
+  /* Emit a LAST datachunk command, and unmark the context for further
+  BDAT commands. */
+
+  if (tctx->chunk_cb(tctx, size, tc_chunk_last) != OK)
+    return FALSE;
+  tctx->options &= ~topt_use_bdat;
+  }
+
 /* If the body is required, ensure that the data for check strings (formerly
 the "from hack") is enabled by negating the length if necessary. (It will be
 negative in cases where it isn't to apply to the headers). Then ensure the body
@@ -925,23 +1004,18 @@ it, applying the size limit if required. */
 
 if (!(tctx->options & topt_no_body))
   {
+  int size = size_limit;
+
   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 = read(deliver_datafile, deliver_in_buffer,
-           DELIVER_IN_BUFFER_SIZE)) > 0)
+  while (  (len = MAX(DELIVER_IN_BUFFER_SIZE, size)) > 0
+       && (len = read(deliver_datafile, deliver_in_buffer, len)) > 0)
     {
-    if (!write_chunk(fd, deliver_in_buffer, len, use_crlf)) return FALSE;
-    if (size_limit > 0)
-      {
-      written += len;
-      if (written > size_limit)
-        {
-        len = 0;    /* Pretend EOF */
-        break;
-        }
-      }
+    if (!write_chunk(fd, tctx, deliver_in_buffer, len))
+      return FALSE;
+    size -= len;
     }
 
   /* A read error on the body will have left len == -1 and errno set. */
@@ -955,7 +1029,7 @@ nl_check_length = nl_escape_length = 0;
 
 /* If requested, add a terminating "." line (SMTP output). */
 
-if (tctx->options & topt_end_dot && !write_chunk(fd, US".\n", 2, use_crlf))
+if (tctx->options & topt_end_dot && !write_chunk(fd, tctx, US".\n", 2))
   return FALSE;
 
 /* Write out any remaining data in the buffer before returning. */
@@ -988,16 +1062,16 @@ 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)
+  struct ob_dkim * dkim, const uschar ** err)
 {
 int dkim_fd;
 int save_errno = 0;
 BOOL rc;
 uschar * dkim_spool_name;
-int sread = 0;
-int wwritten = 0;
-uschar *dkim_signature = NULL;
+uschar * dkim_signature = NULL;
+int sread = 0, wwritten = 0, siglen = 0, options;
 off_t k_file_size;
+const uschar * errstr;
 
 /* If we can't sign, just call the original function. */
 
@@ -1012,12 +1086,17 @@ 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;
+  *err = string_sprintf("dkim spoolfile create: %s", strerror(errno));
   goto CLEANUP;
   }
 
-/* Call original function to write the -K file; does the CRLF expansion */
+/* Call original function to write the -K file; does the CRLF expansion
+(but, in the CHUNKING case, not dot-stuffing and dot-termination). */
 
+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)
@@ -1026,60 +1105,60 @@ if (!rc)
   goto CLEANUP;
   }
 
-if (dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)
+/* Rewind file and feed it to the goats^W DKIM lib */
+dkim->dot_stuffed = !!(options & topt_end_dot);
+lseek(dkim_fd, 0, SEEK_SET);
+if ((dkim_signature = dkim_exim_sign(dkim_fd, dkim, &errstr)))
+  siglen = Ustrlen(dkim_signature);
+else if (dkim->dkim_strict)
   {
-  /* 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)
-    {
-    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) )
       {
-      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;
-         }
+      /* 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.");
+      *err = errstr;
+      rc = FALSE;
+      goto CLEANUP;
       }
-    }
+  }
 
-  if (dkim_signature)
-    {
-    int siglen = Ustrlen(dkim_signature);
-    while(siglen > 0)
-      {
-#ifdef SUPPORT_TLS
-       wwritten = tls_out.active == out_fd
-         ? tls_write(FALSE, dkim_signature, siglen)
-         : write(out_fd, dkim_signature, siglen);
-#else
-       wwritten = write(out_fd, dkim_signature, siglen);
+#ifndef HAVE_LINUX_SENDFILE
+if (options & topt_use_bdat)
 #endif
-      if (wwritten == -1)
-       {
-       /* error, bail out */
-       save_errno = errno;
-       rc = FALSE;
-       goto CLEANUP;
-       }
-      siglen -= wwritten;
-      dkim_signature += wwritten;
-      }
+  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(tctx, siglen, 0) != OK
+       || !transport_write_block(out_fd, dkim_signature, siglen)
+       || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
+       )
+      goto err;
+    siglen = 0;
     }
+
+  /* Send the BDAT command for the entire message, as a single LAST-marked
+  chunk. */
+
+  if (tctx->chunk_cb(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,
@@ -1090,18 +1169,13 @@ if (tls_out.active != out_fd)
   ssize_t copied = 0;
   off_t offset = 0;
 
-  k_file_size = lseek(dkim_fd, 0, SEEK_END); /* Fetch file size */
-
   /* 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)
-    {
-    save_errno = errno;
-    rc = FALSE;
-    }
+    goto err;
   }
 else
 
@@ -1114,25 +1188,20 @@ else
   /* Send file down the original fd */
   while((sread = read(dkim_fd, deliver_out_buffer, DELIVER_OUT_BUFFER_SIZE)) >0)
     {
-    char *p = deliver_out_buffer;
+    uschar * 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);
+       ? tls_write(FALSE, p, sread)
+       : write(out_fd, CS p, sread);
 #else
-      wwritten = write(out_fd, p, sread);
+      wwritten = write(out_fd, CS p, sread);
 #endif
       if (wwritten == -1)
-       {
-       /* error, bail out */
-       save_errno = errno;
-       rc = FALSE;
-       goto CLEANUP;
-       }
+       goto err;
       p += wwritten;
       sread -= wwritten;
       }
@@ -1146,11 +1215,16 @@ else
   }
 
 CLEANUP:
-/* unlink -K file */
-(void)close(dkim_fd);
-Uunlink(dkim_spool_name);
-errno = save_errno;
-return rc;
+  /* unlink -K file */
+  (void)close(dkim_fd);
+  Uunlink(dkim_spool_name);
+  errno = save_errno;
+  return rc;
+
+err:
+  save_errno = errno;
+  rc = FALSE;
+  goto CLEANUP;
 }
 
 #endif
@@ -1167,7 +1241,6 @@ 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.
 
-XXX
 Arguments:     as for internal_transport_write_message() above
 
 Returns:       TRUE on success; FALSE (with errno) for any failure
@@ -1177,12 +1250,11 @@ Returns:       TRUE on success; FALSE (with errno) for any failure
 BOOL
 transport_write_message(int fd, transport_ctx * tctx, int size_limit)
 {
-BOOL use_crlf;
 BOOL last_filter_was_NL = TRUE;
 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 = { NULL, NULL, NULL, NULL, 0 };
+static transport_ctx dummy_tctx = {0};
 
 if (!tctx) tctx = &dummy_tctx;
 
@@ -1201,7 +1273,6 @@ if (  !transport_filter_argv
 before being written to the incoming fd. First set up the special processing to
 be done during the copying. */
 
-use_crlf  = (tctx->options & topt_use_crlf) != 0;
 nl_partial_match = -1;
 
 if (tctx->check_string && tctx->escape_string)
@@ -1225,14 +1296,17 @@ 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(fd, F_GETFD);
+  (void)fcntl(fd, F_SETFD, bits | FD_CLOEXEC);
+  filter_pid = child_open(USS transport_filter_argv, NULL, 077,
+   &fd_write, &fd_read, FALSE);
+  (void)fcntl(fd, F_SETFD, bits & ~FD_CLOEXEC);
+  }
 if (filter_pid < 0) goto TIDY_UP;      /* errno set */
 
 DEBUG(D_transport)
-  debug_printf("process %d running as transport filter: write=%d read=%d\n",
+  debug_printf("process %d running as transport filter: fd_write=%d fd_read=%d\n",
     (int)filter_pid, fd_write, fd_read);
 
 /* Fork subprocess to write the message to the filter, and return the result
@@ -1247,7 +1321,8 @@ if ((write_pid = fork()) == 0)
   (void)close(pfd[pipe_read]);
   nl_check_length = nl_escape_length = 0;
 
-  tctx->options &= ~(topt_use_crlf | topt_end_dot);
+  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);
 
@@ -1316,7 +1391,7 @@ for (;;)
 
   if (len > 0)
     {
-    if (!write_chunk(fd, deliver_in_buffer, len, use_crlf)) goto TIDY_UP;
+    if (!write_chunk(fd, tctx, deliver_in_buffer, len)) goto TIDY_UP;
     last_filter_was_NL = (deliver_in_buffer[len-1] == '\n');
     }
 
@@ -1366,14 +1441,19 @@ 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));
+       int dummy = read(pfd[pipe_read], (void *)&save_errno, sizeof(int));
         dummy = read(pfd[pipe_read], (void *)&(tctx->addr->more_errno), sizeof(int));
         yield = FALSE;
         }
@@ -1385,7 +1465,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]);
 
@@ -1398,8 +1477,8 @@ if (yield)
   nl_check_length = nl_escape_length = 0;
   if (  tctx->options & topt_end_dot
      && ( last_filter_was_NL
-        ? !write_chunk(fd, US".\n", 2, tctx->options)
-       : !write_chunk(fd, US"\n.\n", 3, tctx->options)
+        ? !write_chunk(fd, tctx, US".\n", 2)
+       : !write_chunk(fd, tctx, US"\n.\n", 3)
      )  )
     yield = FALSE;
 
@@ -1464,7 +1543,6 @@ Returns:    nothing
 void
 transport_update_waiting(host_item *hostlist, uschar *tpname)
 {
-uschar buffer[256];
 const uschar *prevname = US"";
 host_item *host;
 open_db dbblock;
@@ -1474,19 +1552,20 @@ 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)))
+  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 = hostlist; host; host = host->next)
   {
   BOOL already = FALSE;
   dbdata_wait *host_record;
   uschar *s;
   int i, host_length;
+  uschar buffer[256];
 
   /* Skip if this is the same host as we just processed; otherwise remember
   the name for next time. */
@@ -1496,8 +1575,7 @@ 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->count = host_record->sequence = 0;
@@ -1511,10 +1589,8 @@ for (host = hostlist; host!= NULL; host = host->next)
 
   for (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. */
@@ -1523,15 +1599,12 @@ for (host = hostlist; host!= NULL; host = host->next)
     {
     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)
-        {
         if (Ustrncmp(s, message_id, MESSAGE_ID_LENGTH) == 0)
           { already = TRUE; break; }
-        }
       }
     }
 
@@ -1627,7 +1700,6 @@ dbdata_wait *host_record;
 int host_length;
 open_db dbblock;
 open_db *dbm_file;
-uschar buffer[256];
 
 int         i;
 struct stat statbuf;
@@ -1654,9 +1726,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)))
+  return FALSE;
 
 /* See if there is a record for this host; if not, there's nothing to do. */
 
@@ -1699,7 +1771,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_malloc(sizeof(msgq_t) * host_record->count);
   msgq_count = host_record->count;
   msgq_actual = msgq_count;
 
@@ -1772,13 +1844,13 @@ 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 */
 
@@ -1807,7 +1879,7 @@ test but the code should work */
 
   if (bFound)          /* Usual exit from main loop */
     {
-    free (msgq);
+    store_free (msgq);
     break;
     }
 
@@ -1833,7 +1905,7 @@ test but the code should work */
     return FALSE;
     }
 
-  free(msgq);
+  store_free(msgq);
   }            /* we need to process a continuation record */
 
 /* Control gets here when an existing message has been encountered; its
@@ -1857,6 +1929,72 @@ 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)
+{
+pid_t pid;
+int status;
+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 (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)
+  if (tls_out.active >= 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 >= 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.
@@ -1882,9 +2020,6 @@ DEBUG(D_transport) debug_printf("transport_pass_socket entered\n");
 
 if ((pid = fork()) == 0)
   {
-  int i = 16;
-  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
@@ -1893,51 +2028,8 @@ if ((pid = fork()) == 0)
   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_use_dsn) argv[i++] = US"-MCD";
-
-  if (smtp_authenticated) argv[i++] = US"-MCA";
-
-  #ifdef SUPPORT_TLS
-  if (tls_offered) argv[i++] = US"-MCT";
-  #endif
-
-  if (smtp_use_size) argv[i++] = US"-MCS";
-  if (smtp_use_pipelining) argv[i++] = US"-MCP";
-
-  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() */
+  transport_do_pass_socket(transport_name, hostname, hostaddress,
+    id, socket_fd);
   }
 
 /* If the process creation succeeded, wait for the first-level child, which
@@ -2196,7 +2288,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,