General tidying
[exim.git] / src / src / transport.c
index a104f51b49d3ffa26f94409f777278c8228adda6..00b8fa9d8847d5fedec17ab26fec0583c1ab57da 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/transport.c,v 1.9 2005/05/24 14:56:27 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* General functions concerned with transportation, and generic options for all
@@ -13,6 +11,9 @@ 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. */
@@ -67,11 +68,11 @@ optionlist optionlist_transports[] = {
                  (void *)(offsetof(transport_instance, envelope_to_add)) },
   { "group",             opt_expand_gid|opt_public,
                  (void *)offsetof(transport_instance, gid) },
-  { "headers_add",      opt_stringptr|opt_public,
+  { "headers_add",      opt_stringptr|opt_public|opt_rep_str,
                  (void *)offsetof(transport_instance, add_headers) },
   { "headers_only",     opt_bool|opt_public,
                  (void *)offsetof(transport_instance, headers_only) },
-  { "headers_remove",   opt_stringptr|opt_public,
+  { "headers_remove",   opt_stringptr|opt_public|opt_rep_str,
                  (void *)offsetof(transport_instance, remove_headers) },
   { "headers_rewrite",  opt_rewrite|opt_public,
                  (void *)offsetof(transport_instance, headers_rewrite) },
@@ -93,6 +94,10 @@ optionlist optionlist_transports[] = {
                  (void *)offsetof(transport_instance, shadow_condition) },
   { "shadow_transport", opt_stringptr|opt_public,
                  (void *)offsetof(transport_instance, shadow) },
+#ifdef EXPERIMENTAL_TPDA
+  { "tpda_delivery_action",opt_stringptr | opt_public,
+                 (void *)offsetof(transport_instance, tpda_delivery_action) },
+#endif
   { "transport_filter", opt_stringptr|opt_public,
                  (void *)offsetof(transport_instance, filter_command) },
   { "transport_filter_timeout", opt_time|opt_public,
@@ -218,7 +223,7 @@ for (i = 0; i < 100; i++)
   if (transport_write_timeout <= 0)   /* No timeout wanted */
     {
     #ifdef SUPPORT_TLS
-    if (tls_active == fd) rc = tls_write(block, len); else
+    if (tls_out.active == fd) rc = tls_write(FALSE, block, len); else
     #endif
     rc = write(fd, block, len);
     save_errno = errno;
@@ -230,7 +235,7 @@ for (i = 0; i < 100; i++)
     {
     alarm(local_timeout);
     #ifdef SUPPORT_TLS
-    if (tls_active == fd) rc = tls_write(block, len); else
+    if (tls_out.active == fd) rc = tls_write(FALSE, block, len); else
     #endif
     rc = write(fd, block, len);
     save_errno = errno;
@@ -321,7 +326,7 @@ Returns:      the yield of transport_write_block()
 */
 
 BOOL
-transport_write_string(int fd, char *format, ...)
+transport_write_string(int fd, const char *format, ...)
 {
 va_list ap;
 va_start(ap, format);
@@ -423,6 +428,7 @@ for (ptr = start; ptr < end; ptr++)
 
     if (use_crlf) *chunk_ptr++ = '\r';
     *chunk_ptr++ = '\n';
+    transport_newlines++;
 
     /* The check_string test (formerly "from hack") replaces the specific
     string at the start of a line with an escape string (e.g. "From " becomes
@@ -594,6 +600,177 @@ return write_chunk(fd, pp->address, Ustrlen(pp->address), use_crlf);
 
 
 
+/* Add/remove/rewwrite headers, and send them plus the empty-line sparator.
+
+Globals:
+  header_list
+
+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
+
+Returns:                TRUE on success; FALSE on failure.
+*/
+BOOL
+transport_headers_send(address_item *addr, int fd, uschar *add_headers, uschar *remove_headers,
+  BOOL (*sendfn)(int fd, uschar * s, int len, BOOL use_crlf),
+  BOOL use_crlf, rewrite_rule *rewrite_rules, int rewrite_existflags)
+{
+header_line *h;
+
+/* 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
+were removed (e.g. Bcc). If remove_headers is not null, skip any headers that
+match any entries therein.  It is a colon-sep list; expand the items
+separately and squash any empty ones.
+Then check addr->p.remove_headers too, provided that addr is not NULL. */
+
+for (h = header_list; h != NULL; h = h->next) if (h->type != htype_old)
+  {
+  int i;
+  uschar *list = remove_headers;
+
+  BOOL include_header = TRUE;
+
+  for (i = 0; i < 2; i++)    /* For remove_headers && addr->p.remove_headers */
+    {
+    if (list)
+      {
+      int sep = ':';         /* This is specified as a colon-separated list */
+      uschar *s, *ss;
+      uschar buffer[128];
+      while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
+       {
+       int len;
+
+       if (i == 0)
+         if (!(s = expand_string(s)) && !expand_string_forcedfail)
+           {
+           errno = ERRNO_CHHEADER_FAIL;
+           return FALSE;
+           }
+       len = Ustrlen(s);
+       if (strncmpic(h->text, s, len) != 0) continue;
+       ss = h->text + len;
+       while (*ss == ' ' || *ss == '\t') ss++;
+       if (*ss == ':') break;
+       }
+      if (s != NULL) { include_header = FALSE; break; }
+      }
+    if (addr != NULL) list = addr->p.remove_headers;
+    }
+
+  /* If this header is to be output, try to rewrite it if there are rewriting
+  rules. */
+
+  if (include_header)
+    {
+    if (rewrite_rules)
+      {
+      void *reset_point = store_get(0);
+      header_line *hh;
+
+      if ((hh = rewrite_header(h, NULL, NULL, rewrite_rules, rewrite_existflags, FALSE)))
+       {
+       if (!sendfn(fd, hh->text, hh->slen, use_crlf)) return FALSE;
+       store_reset(reset_point);
+       continue;     /* With the next header line */
+       }
+      }
+
+    /* Either no rewriting rules, or it didn't get rewritten */
+
+    if (!sendfn(fd, h->text, h->slen, use_crlf)) 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,
+they will all have the same headers in order to be batched. The headers
+are chained in reverse order of adding (so several addresses from the
+same alias might share some of them) but we want to output them in the
+opposite order. This is a bit tedious, but there shouldn't be very many
+of them. We just walk the list twice, reversing the pointers each time,
+but on the second time, write out the items.
+
+Headers added to an address by a router are guaranteed to end with a newline.
+*/
+
+if (addr)
+  {
+  int i;
+  header_line *hprev = addr->p.extra_headers;
+  header_line *hnext;
+  for (i = 0; i < 2; i++)
+    {
+    for (h = hprev, hprev = NULL; h != NULL; h = hnext)
+      {
+      hnext = h->next;
+      h->next = hprev;
+      hprev = h;
+      if (i == 1)
+       {
+       if (!sendfn(fd, h->text, h->slen, use_crlf)) return FALSE;
+       DEBUG(D_transport)
+         debug_printf("added header line(s):\n%s---\n", h->text);
+       }
+      }
+    }
+  }
+
+/* If a string containing additional headers exists it is a newline-sep
+list.  Expand each item and write out the result.  This is done last so that
+if it (deliberately or accidentally) isn't in header format, it won't mess
+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 (add_headers)
+  {
+  int sep = '\n';
+  uschar * s;
+
+  while ((s = string_nextinlist(&add_headers, &sep, NULL, 0)))
+    if (!(s = expand_string(s)))
+      {
+      if (!expand_string_forcedfail)
+       { errno = ERRNO_CHHEADER_FAIL; return FALSE; }
+      }
+    else
+      {
+      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))
+         return FALSE;
+       DEBUG(D_transport)
+         {
+         debug_printf("added header line:\n%s", s);
+         if (s[len-1] != '\n') debug_printf("\n");
+         debug_printf("---\n");
+         }
+       }
+      }
+  }
+
+/* Separate headers from body with a blank line */
+
+return sendfn(fd, US"\n", 1, use_crlf);
+}
+
+
 /*************************************************
 *                Write the message               *
 *************************************************/
@@ -660,7 +837,6 @@ internal_transport_write_message(address_item *addr, int fd, int options,
 {
 int written = 0;
 int len;
-header_line *h;
 BOOL use_crlf  = (options & topt_use_crlf)  != 0;
 
 /* Initialize pointer in output buffer. */
@@ -741,154 +917,9 @@ if ((options & topt_no_headers) == 0)
   were removed (e.g. Bcc). If remove_headers is not null, skip any headers that
   match any entries therein. Then check addr->p.remove_headers too, provided that
   addr is not NULL. */
-
-  if (remove_headers != NULL)
-    {
-    uschar *s = expand_string(remove_headers);
-    if (s == NULL && !expand_string_forcedfail)
-      {
-      errno = ERRNO_CHHEADER_FAIL;
-      return FALSE;
-      }
-    remove_headers = s;
-    }
-
-  for (h = header_list; h != NULL; h = h->next)
-    {
-    int i;
-    uschar *list = NULL;
-    BOOL include_header;
-
-    if (h->type == htype_old) continue;
-
-    include_header = TRUE;
-    list = remove_headers;
-
-    for (i = 0; i < 2; i++)    /* For remove_headers && addr->p.remove_headers */
-      {
-      if (list != NULL)
-        {
-        int sep = ':';         /* This is specified as a colon-separated list */
-        uschar *s, *ss;
-        uschar buffer[128];
-        while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer)))
-                != NULL)
-          {
-          int len = Ustrlen(s);
-          if (strncmpic(h->text, s, len) != 0) continue;
-          ss = h->text + len;
-          while (*ss == ' ' || *ss == '\t') ss++;
-          if (*ss == ':') break;
-          }
-        if (s != NULL) { include_header = FALSE; break; }
-        }
-      if (addr != NULL) list = addr->p.remove_headers;
-      }
-
-    /* If this header is to be output, try to rewrite it if there are rewriting
-    rules. */
-
-    if (include_header)
-      {
-      if (rewrite_rules != NULL)
-        {
-        void *reset_point = store_get(0);
-        header_line *hh =
-          rewrite_header(h, NULL, NULL, rewrite_rules, rewrite_existflags,
-            FALSE);
-        if (hh != NULL)
-          {
-          if (!write_chunk(fd, hh->text, hh->slen, use_crlf)) return FALSE;
-          store_reset(reset_point);
-          continue;     /* With the next header line */
-          }
-        }
-
-      /* Either no rewriting rules, or it didn't get rewritten */
-
-      if (!write_chunk(fd, h->text, h->slen, use_crlf)) 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,
-  they will all have the same headers in order to be batched. The headers
-  are chained in reverse order of adding (so several addresses from the
-  same alias might share some of them) but we want to output them in the
-  opposite order. This is a bit tedious, but there shouldn't be very many
-  of them. We just walk the list twice, reversing the pointers each time,
-  but on the second time, write out the items.
-
-  Headers added to an address by a router are guaranteed to end with a newline.
-  */
-
-  if (addr != NULL)
-    {
-    int i;
-    header_line *hprev = addr->p.extra_headers;
-    header_line *hnext;
-    for (i = 0; i < 2; i++)
-      {
-      for (h = hprev, hprev = NULL; h != NULL; h = hnext)
-        {
-        hnext = h->next;
-        h->next = hprev;
-        hprev = h;
-        if (i == 1)
-          {
-          if (!write_chunk(fd, h->text, h->slen, use_crlf)) return FALSE;
-          DEBUG(D_transport)
-            debug_printf("added header line(s):\n%s---\n", h->text);
-          }
-        }
-      }
-    }
-
-  /* If a string containing additional headers exists, expand it and write
-  out the result. This is done last so that if it (deliberately or accidentally)
-  isn't in header format, it won't mess 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 (add_headers != NULL)
-    {
-    uschar *s = expand_string(add_headers);
-    if (s == NULL)
-      {
-      if (!expand_string_forcedfail)
-        {
-        errno = ERRNO_CHHEADER_FAIL;
-        return FALSE;
-        }
-      }
-    else
-      {
-      int len = Ustrlen(s);
-      if (len > 0)
-        {
-        if (!write_chunk(fd, s, len, use_crlf)) return FALSE;
-        if (s[len-1] != '\n' && !write_chunk(fd, US"\n", 1, use_crlf))
-          return FALSE;
-        DEBUG(D_transport)
-          {
-          debug_printf("added header line(s):\n%s", s);
-          if (s[len-1] != '\n') debug_printf("\n");
-          debug_printf("---\n");
-          }
-        }
-      }
-    }
-
-  /* Separate headers from body with a blank line */
-
-  if (!write_chunk(fd, US"\n", 1, use_crlf)) return FALSE;
+  if (!transport_headers_send(addr, fd, add_headers, remove_headers, &write_chunk,
+       use_crlf, rewrite_rules, rewrite_existflags))
+    return FALSE;
   }
 
 /* If the body is required, ensure that the data for check strings (formerly
@@ -917,19 +948,19 @@ if ((options & topt_no_body) == 0)
       }
     }
 
-  /* Finished with the check string */
-
-  nl_check_length = nl_escape_length = 0;
-
   /* A read error on the body will have left len == -1 and errno set. */
 
   if (len != 0) return FALSE;
+  }
 
-  /* If requested, add a terminating "." line (SMTP output). */
+/* Finished with the check string */
 
-  if ((options & topt_end_dot) != 0 && !write_chunk(fd, US".\n", 2, use_crlf))
-    return FALSE;
-  }
+nl_check_length = nl_escape_length = 0;
+
+/* If requested, add a terminating "." line (SMTP output). */
+
+if ((options & topt_end_dot) != 0 && !write_chunk(fd, US".\n", 2, use_crlf))
+  return FALSE;
 
 /* Write out any remaining data in the buffer before returning. */
 
@@ -938,16 +969,14 @@ return (len = chunk_ptr - deliver_out_buffer) <= 0 ||
 }
 
 
-#ifdef EXPERIMENTAL_DOMAINKEYS
+#ifndef DISABLE_DKIM
 
-/**********************************************************************************
-*    External interface to write the message, while signing it with domainkeys    *
-**********************************************************************************/
+/***************************************************************************************************
+*    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
-   (1) Domainkeys support is compiled in.
-   (2) The dk_private_key option on the smtp transport is set.
+   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
@@ -956,38 +985,48 @@ return (len = chunk_ptr - deliver_out_buffer) <= 0 ||
 
 Arguments:     as for internal_transport_write_message() above, with additional
                arguments:
-               uschar *dk_private_key         The private key to use (filename or plain data)
-               uschar *dk_domain              Override domain (normally NULL)
-               uschar *dk_selector            The selector to use.
-               uschar *dk_canon               The canonalization scheme to use, "simple" or "nofws"
-               uschar *dk_headers             Colon-separated header list to include in the signing
-                                              process.
-               uschar *dk_strict              What to do if signing fails: 1/true  => throw error
-                                                                           0/false => send anyway
+               uschar *dkim_private_key         DKIM: The private key to use (filename or plain data)
+               uschar *dkim_domain              DKIM: The domain to use
+               uschar *dkim_selector            DKIM: The selector to use.
+               uschar *dkim_canon               DKIM: The canonalization scheme to use, "simple" or "relaxed"
+               uschar *dkim_strict              DKIM: What to do if signing fails: 1/true  => throw error
+                                                                                   0/false => send anyway
+               uschar *dkim_sign_headers        DKIM: List of headers that should be included in signature
+                                                generation
 
 Returns:       TRUE on success; FALSE (with errno) for any failure
 */
 
 BOOL
-dk_transport_write_message(address_item *addr, int fd, int options,
+dkim_transport_write_message(address_item *addr, int fd, int options,
   int size_limit, uschar *add_headers, uschar *remove_headers,
   uschar *check_string, uschar *escape_string, rewrite_rule *rewrite_rules,
-  int rewrite_existflags, uschar *dk_private_key, uschar *dk_domain,
-  uschar *dk_selector, uschar *dk_canon, uschar *dk_headers, uschar *dk_strict)
+  int rewrite_existflags, uschar *dkim_private_key, uschar *dkim_domain,
+  uschar *dkim_selector, uschar *dkim_canon, uschar *dkim_strict, uschar *dkim_sign_headers
+  )
 {
-  int dk_fd;
+  int dkim_fd;
   int save_errno = 0;
   BOOL rc;
-  uschar dk_spool_name[256];
+  uschar dkim_spool_name[256];
   char sbuf[2048];
   int sread = 0;
   int wwritten = 0;
-  uschar *dk_signature = NULL;
+  uschar *dkim_signature = NULL;
+  off_t size = 0;
+
+  if (!( ((dkim_private_key != NULL) && (dkim_domain != NULL) && (dkim_selector != NULL)) )) {
+    /* If we can't sign, just call the original function. */
+    return transport_write_message(addr, fd, options,
+              size_limit, add_headers, remove_headers,
+              check_string, escape_string, rewrite_rules,
+              rewrite_existflags);
+  }
 
-  snprintf(CS dk_spool_name, 256, "%s/input/%s/%s-K",
-          spool_directory, message_subdir, message_id);
-  dk_fd = Uopen(dk_spool_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE);
-  if (dk_fd < 0)
+  (void)string_format(dkim_spool_name, 256, "%s/input/%s/%s-%d-K",
+          spool_directory, message_subdir, message_id, (int)getpid());
+  dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE);
+  if (dkim_fd < 0)
     {
     /* Can't create spool file. Ugh. */
     rc = FALSE;
@@ -996,7 +1035,7 @@ dk_transport_write_message(address_item *addr, int fd, int options,
     }
 
   /* Call original function */
-  rc = transport_write_message(addr, dk_fd, options,
+  rc = transport_write_message(addr, dkim_fd, options,
     size_limit, add_headers, remove_headers,
     check_string, escape_string, rewrite_rules,
     rewrite_existflags);
@@ -1008,60 +1047,85 @@ dk_transport_write_message(address_item *addr, int fd, int options,
     goto CLEANUP;
     }
 
-  /* Rewind file and feed it to the goats^W DK lib */
-  lseek(dk_fd, 0, SEEK_SET);
-  dk_signature = dk_exim_sign(dk_fd,
-                              dk_private_key,
-                              dk_domain,
-                              dk_selector,
-                              dk_canon);
-
-  if (dk_signature != NULL)
-    {
-    /* Send the signature first */
-    int siglen = Ustrlen(dk_signature);
-    while(siglen > 0)
-      {
-      #ifdef SUPPORT_TLS
-      if (tls_active == fd) wwritten = tls_write(dk_signature, siglen); else
-      #endif
-      wwritten = write(fd,dk_signature,siglen);
-      if (wwritten == -1)
-        {
-        /* error, bail out */
-        save_errno = errno;
-        rc = FALSE;
-        goto CLEANUP;
+  if ( (dkim_private_key != NULL) && (dkim_domain != NULL) && (dkim_selector != NULL) ) {
+    /* 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_private_key,
+                                    dkim_domain,
+                                    dkim_selector,
+                                    dkim_canon,
+                                    dkim_sign_headers);
+    if (dkim_signature == NULL) {
+      if (dkim_strict != NULL) {
+        uschar *dkim_strict_result = expand_string(dkim_strict);
+        if (dkim_strict_result != NULL) {
+          if ( (strcmpic(dkim_strict,US"1") == 0) ||
+               (strcmpic(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;
+          }
         }
-      siglen -= wwritten;
-      dk_signature += wwritten;
       }
     }
-  else if (dk_strict != NULL)
-    {
-    uschar *dk_strict_result = expand_string(dk_strict);
-    if (dk_strict_result != NULL)
-      {
-      if ( (strcmpic(dk_strict,"1") == 0) ||
-           (strcmpic(dk_strict,"true") == 0) )
-        {
-        save_errno = errno;
-        rc = FALSE;
-        goto CLEANUP;
+    else {
+      int siglen = Ustrlen(dkim_signature);
+      while(siglen > 0) {
+        #ifdef SUPPORT_TLS
+        if (tls_out.active == fd) wwritten = tls_write(FALSE, dkim_signature, siglen); else
+        #endif
+        wwritten = write(fd,dkim_signature,siglen);
+        if (wwritten == -1) {
+          /* error, bail out */
+          save_errno = errno;
+          rc = FALSE;
+          goto CLEANUP;
         }
+        siglen -= wwritten;
+        dkim_signature += wwritten;
       }
     }
+  }
 
-  /* Rewind file and send it down the original fd. */
-  lseek(dk_fd, 0, SEEK_SET);
+  /* Fetch file positition (the size) */
+  size = lseek(dkim_fd,0,SEEK_CUR);
 
-  while((sread = read(dk_fd,sbuf,2048)) > 0)
+  /* Rewind file */
+  lseek(dkim_fd, 0, SEEK_SET);
+
+#ifdef HAVE_LINUX_SENDFILE
+  /* We can use sendfile() to shove the file contents
+     to the socket. However only if we don't use TLS,
+     in which case theres another layer of indirection
+     before the data finally hits the socket. */
+  if (tls_out.active != fd)
+    {
+    ssize_t copied = 0;
+    off_t offset = 0;
+    while((copied >= 0) && (offset<size))
+      {
+      copied = sendfile(fd, dkim_fd, &offset, (size - offset));
+      }
+    if (copied < 0)
+      {
+      save_errno = errno;
+      rc = FALSE;
+      }
+    goto CLEANUP;
+    }
+#endif
+
+  /* Send file down the original fd */
+  while((sread = read(dkim_fd,sbuf,2048)) > 0)
     {
     char *p = sbuf;
     /* write the chunk */
-    DK_WRITE:
+    DKIM_WRITE:
     #ifdef SUPPORT_TLS
-    if (tls_active == fd) wwritten = tls_write(p, sread); else
+    if (tls_out.active == fd) wwritten = tls_write(FALSE, US p, sread); else
     #endif
     wwritten = write(fd,p,sread);
     if (wwritten == -1)
@@ -1076,7 +1140,7 @@ dk_transport_write_message(address_item *addr, int fd, int options,
       /* short write, try again */
       p += wwritten;
       sread -= wwritten;
-      goto DK_WRITE;
+      goto DKIM_WRITE;
       }
     }
 
@@ -1087,17 +1151,18 @@ dk_transport_write_message(address_item *addr, int fd, int options,
     goto CLEANUP;
     }
 
-
   CLEANUP:
   /* unlink -K file */
-  close(dk_fd);
-  Uunlink(dk_spool_name);
+  (void)close(dkim_fd);
+  Uunlink(dkim_spool_name);
   errno = save_errno;
   return rc;
 }
+
 #endif
 
 
+
 /*************************************************
 *    External interface to write the message     *
 *************************************************/
@@ -1164,10 +1229,10 @@ save_errno = 0;
 yield = FALSE;
 write_pid = (pid_t)(-1);
 
-fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+(void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
 filter_pid = child_open(transport_filter_argv, NULL, 077, &fd_write, &fd_read,
   FALSE);
-fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) & ~FD_CLOEXEC);
+(void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) & ~FD_CLOEXEC);
 if (filter_pid < 0) goto TIDY_UP;      /* errno set */
 
 DEBUG(D_transport)
@@ -1182,25 +1247,30 @@ if (pipe(pfd) != 0) goto TIDY_UP;      /* errno set */
 if ((write_pid = fork()) == 0)
   {
   BOOL rc;
-  close(fd_read);
-  close(pfd[pipe_read]);
+  (void)close(fd_read);
+  (void)close(pfd[pipe_read]);
   nl_check_length = nl_escape_length = 0;
   rc = internal_transport_write_message(addr, fd_write,
     (options & ~(topt_use_crlf | topt_end_dot)),
     size_limit, add_headers, remove_headers, NULL, NULL,
     rewrite_rules, rewrite_existflags);
   save_errno = errno;
-  write(pfd[pipe_write], (void *)&rc, sizeof(BOOL));
-  write(pfd[pipe_write], (void *)&save_errno, sizeof(int));
-  write(pfd[pipe_write], (void *)&(addr->more_errno), sizeof(int));
+  if (  write(pfd[pipe_write], (void *)&rc, sizeof(BOOL))
+        != sizeof(BOOL)
+     || write(pfd[pipe_write], (void *)&save_errno, sizeof(int))
+        != sizeof(int)
+     || write(pfd[pipe_write], (void *)&(addr->more_errno), sizeof(int))
+        != sizeof(int)
+     )
+    rc = FALSE;        /* compiler quietening */
   _exit(0);
   }
 save_errno = errno;
 
 /* Parent process: close our copy of the writing subprocess' pipes. */
 
-close(pfd[pipe_write]);
-close(fd_write);
+(void)close(pfd[pipe_write]);
+(void)close(fd_write);
 fd_write = -1;
 
 /* Writing process creation failed */
@@ -1270,8 +1340,8 @@ sure. Also apply a paranoia timeout. */
 TIDY_UP:
 save_errno = errno;
 
-close(fd_read);
-if (fd_write > 0) close(fd_write);
+(void)close(fd_read);
+if (fd_write > 0) (void)close(fd_write);
 
 if (!yield)
   {
@@ -1303,11 +1373,11 @@ if (write_pid > 0)
     if (rc == 0)
       {
       BOOL ok;
-      read(pfd[pipe_read], (void *)&ok, sizeof(BOOL));
+      int dummy = read(pfd[pipe_read], (void *)&ok, sizeof(BOOL));
       if (!ok)
         {
-        read(pfd[pipe_read], (void *)&save_errno, sizeof(int));
-        read(pfd[pipe_read], (void *)&(addr->more_errno), sizeof(int));
+        dummy = read(pfd[pipe_read], (void *)&save_errno, sizeof(int));
+        dummy = read(pfd[pipe_read], (void *)&(addr->more_errno), sizeof(int));
         yield = FALSE;
         }
       }
@@ -1320,7 +1390,7 @@ if (write_pid > 0)
       }
     }
   }
-close(pfd[pipe_read]);
+(void)close(pfd[pipe_read]);
 
 /* If there have been no problems we can now add the terminating "." if this is
 SMTP output, turning off escaping beforehand. If the last character from the
@@ -1389,8 +1459,7 @@ better.
 Old records should eventually get swept up by the exim_tidydb utility.
 
 Arguments:
-  hostlist  list of hosts that this message could be sent to;
-              the update_waiting flag is set if a host is to be noted
+  hostlist  list of hosts that this message could be sent to
   tpname    name of the transport
 
 Returns:    nothing
@@ -1405,6 +1474,8 @@ host_item *host;
 open_db dbblock;
 open_db *dbm_file;
 
+DEBUG(D_transport) debug_printf("updating wait-%s database\n", tpname);
+
 /* Open the database for this transport */
 
 sprintf(CS buffer, "wait-%.200s", tpname);
@@ -1412,8 +1483,7 @@ dbm_file = dbfn_open(buffer, O_RDWR, &dbblock, TRUE);
 if (dbm_file == NULL) return;
 
 /* Scan the list of hosts for which this message is waiting, and ensure
-that the message id is in each host record for those that have the
-update_waiting flag set. */
+that the message id is in each host record. */
 
 for (host = hostlist; host!= NULL; host = host->next)
   {
@@ -1422,10 +1492,6 @@ for (host = hostlist; host!= NULL; host = host->next)
   uschar *s;
   int i, host_length;
 
-  /* Skip if the update_waiting flag is not set. */
-
-  if (!host->update_waiting) continue;
-
   /* Skip if this is the same host as we just processed; otherwise remember
   the name for next time. */
 
@@ -1475,7 +1541,11 @@ for (host = hostlist; host!= NULL; host = host->next)
 
   /* If this message is already in a record, no need to update. */
 
-  if (already) continue;
+  if (already)
+    {
+    DEBUG(D_transport) debug_printf("already listed for %s\n", host->name);
+    continue;
+    }
 
 
   /* If this record is full, write it out with a new name constructed
@@ -1511,6 +1581,7 @@ for (host = hostlist; host!= NULL; host = host->next)
   /* Update the database */
 
   dbfn_write(dbm_file, host->name, host_record, sizeof(dbdata_wait) + host_length);
+  DEBUG(D_transport) debug_printf("added to list for %s\n", host->name);
   }
 
 /* All now done */
@@ -1749,7 +1820,7 @@ if ((pid = fork()) == 0)
   automatic comparison. */
 
   if ((pid = fork()) != 0) _exit(EXIT_SUCCESS);
-  if (running_in_test_harness) millisleep(500);
+  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. */
@@ -1784,8 +1855,8 @@ if ((pid = fork()) == 0)
 
   if (socket_fd != 0)
     {
-    dup2(socket_fd, 0);
-    close(socket_fd);
+    (void)dup2(socket_fd, 0);
+    (void)close(socket_fd);
     }
 
   DEBUG(D_exec) debug_print_argv(argv);
@@ -1955,7 +2026,122 @@ 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;
+      for (ad = addr; ad != NULL; ad = ad->next) {
+          argv[i++] = ad->address;
+          argcount++;
+      }
+
+      /* Subtract one since we replace $pipe_addresses */
+      argcount--;
+      i--;
+      }
+
+      /* Handle special case of $address_pipe when af_force_command is set */
+
+    else if (addr != NULL && testflag(addr,af_force_command) &&
+        (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;
+
+      /* We can never have more then the argv we will be loading into */
+      address_pipe_max_args = max_args - argcount + 1;
+
+      DEBUG(D_transport)
+        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 *));
+
+      /* +1 because addr->local_part[0] == '|' since af_force_command is set */
+      s = expand_string(addr->local_part + 1);
+
+      if (s == NULL || *s == '\0')
+        {
+        addr->transport_return = FAIL;
+        addr->message = string_sprintf("Expansion of \"%s\" "
+           "from command \"%s\" in %s failed: %s",
+           (addr->local_part + 1), cmd, etext, expand_string_message);
+        return FALSE;
+        }
+
+      while (isspace(*s)) s++; /* strip leading space */
+
+      while (*s != 0 && address_pipe_argcount < address_pipe_max_args)
+        {
+        if (*s == '\'')
+          {
+          ss = s + 1;
+          while (*ss != 0 && *ss != '\'') ss++;
+          address_pipe_argv[address_pipe_argcount++] = ss = store_get(ss - s++);
+          while (*s != 0 && *s != '\'') *ss++ = *s++;
+          if (*s != 0) s++;
+          *ss++ = 0;
+          }
+        else address_pipe_argv[address_pipe_argcount++] = string_dequote(&s);
+        while (isspace(*s)) s++; /* strip space after arg */
+        }
+
+      address_pipe_argv[address_pipe_argcount] = (uschar *)0;
+
+      /* If *s != 0 we have run out of argument slots. */
+      if (*s != 0)
+        {
+        uschar *msg = string_sprintf("Too many arguments in $address_pipe "
+          "\"%s\" in %s", addr->local_part + 1, etext);
+        if (addr != NULL)
+          {
+          addr->transport_return = FAIL;
+          addr->message = msg;
+          }
+        else *errptr = msg;
+        return FALSE;
+        }
+
+      /* address_pipe_argcount - 1
+       * because we are replacing $address_pipe in the argument list
+       * with the first thing it expands to */
+      if (argcount + address_pipe_argcount - 1 > max_args)
+        {
+        addr->transport_return = FAIL;
+        addr->message = string_sprintf("Too many arguments to command "
+          "\"%s\" after expanding $address_pipe in %s", cmd, etext);
+        return FALSE;
+        }
+
+      /* If we are not just able to replace the slot that contained
+       * $address_pipe (address_pipe_argcount == 1)
+       * We have to move the existing argv by address_pipe_argcount - 1
+       * Visually if address_pipe_argcount == 2:
+       * [argv 0][argv 1][argv 2($address_pipe)][argv 3][0]
+       * [argv 0][argv 1][ap_arg0][ap_arg1][old argv 3][0]
+       */
+      if (address_pipe_argcount > 1)
+        memmove(
+          /* current position + additonal args */
+          argv + i + address_pipe_argcount,
+          /* current position + 1 (for the (uschar *)0 at the end) */
+          argv + i + 1,
+          /* -1 for the (uschar *)0 at the end)*/
+          (argcount - i)*sizeof(uschar *)
+        );
+
+      /* 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;
+           address_pipe_i++)
+        {
+        argv[i++] = address_pipe_argv[address_pipe_i];
+        argcount++;
+        }
+
+      /* Subtract one since we replace $address_pipe */
+      argcount--;
       i--;
       }
 
@@ -1996,4 +2182,6 @@ if (expand_arguments)
 return TRUE;
 }
 
+/* vi: aw ai sw=2
+*/
 /* End of transport.c */