max_parallel transport option
[users/jgh/exim.git] / src / src / transport.c
index cc6103df5f52d9bcaf27f15499f5dc43ab0526dc..c258bfd9d0800062422d14edf185d89118eb621d 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/transport.c,v 1.14 2005/12/14 10:00:05 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* General functions concerned with transportation, and generic options for all
 /* 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"
 
 
 #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. */
 
 /* Structure for keeping list of addresses that have been added to
 Envelope-To:, in order to avoid duplication. */
@@ -65,13 +66,17 @@ optionlist optionlist_transports[] = {
                  (void *)offsetof(transport_instance, driver_name) },
   { "envelope_to_add",   opt_bool|opt_public,
                  (void *)(offsetof(transport_instance, envelope_to_add)) },
                  (void *)offsetof(transport_instance, driver_name) },
   { "envelope_to_add",   opt_bool|opt_public,
                  (void *)(offsetof(transport_instance, envelope_to_add)) },
+#ifdef EXPERIMENTAL_EVENT
+  { "event_action",     opt_stringptr | opt_public,
+                 (void *)offsetof(transport_instance, event_action) },
+#endif
   { "group",             opt_expand_gid|opt_public,
                  (void *)offsetof(transport_instance, gid) },
   { "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) },
                  (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) },
                  (void *)offsetof(transport_instance, remove_headers) },
   { "headers_rewrite",  opt_rewrite|opt_public,
                  (void *)offsetof(transport_instance, headers_rewrite) },
@@ -79,6 +84,8 @@ optionlist optionlist_transports[] = {
                  (void *)offsetof(transport_instance, home_dir) },
   { "initgroups",       opt_bool|opt_public,
                  (void *)offsetof(transport_instance, initgroups) },
                  (void *)offsetof(transport_instance, home_dir) },
   { "initgroups",       opt_bool|opt_public,
                  (void *)offsetof(transport_instance, initgroups) },
+  { "max_parallel",     opt_stringptr|opt_public,
+                 (void *)offsetof(transport_instance, max_parallel) },
   { "message_size_limit", opt_stringptr|opt_public,
                  (void *)offsetof(transport_instance, message_size_limit) },
   { "rcpt_include_affixes", opt_bool|opt_public,
   { "message_size_limit", opt_stringptr|opt_public,
                  (void *)offsetof(transport_instance, message_size_limit) },
   { "rcpt_include_affixes", opt_bool|opt_public,
@@ -218,7 +225,7 @@ for (i = 0; i < 100; i++)
   if (transport_write_timeout <= 0)   /* No timeout wanted */
     {
     #ifdef SUPPORT_TLS
   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;
     #endif
     rc = write(fd, block, len);
     save_errno = errno;
@@ -230,7 +237,7 @@ for (i = 0; i < 100; i++)
     {
     alarm(local_timeout);
     #ifdef SUPPORT_TLS
     {
     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;
     #endif
     rc = write(fd, block, len);
     save_errno = errno;
@@ -321,7 +328,7 @@ Returns:      the yield of transport_write_block()
 */
 
 BOOL
 */
 
 BOOL
-transport_write_string(int fd, char *format, ...)
+transport_write_string(int fd, const char *format, ...)
 {
 va_list ap;
 va_start(ap, format);
 {
 va_list ap;
 va_start(ap, format);
@@ -423,6 +430,7 @@ for (ptr = start; ptr < end; ptr++)
 
     if (use_crlf) *chunk_ptr++ = '\r';
     *chunk_ptr++ = '\n';
 
     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
 
     /* 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 +602,176 @@ 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->prop.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;
+  const uschar *list = remove_headers;
+
+  BOOL include_header = TRUE;
+
+  for (i = 0; i < 2; i++)    /* For remove_headers && addr->prop.remove_headers */
+    {
+    if (list)
+      {
+      int sep = ':';         /* This is specified as a colon-separated list */
+      uschar *s, *ss;
+      while ((s = string_nextinlist(&list, &sep, NULL, 0)))
+       {
+       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->prop.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->prop.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(CUSS &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               *
 *************************************************/
 /*************************************************
 *                Write the message               *
 *************************************************/
@@ -660,7 +838,6 @@ internal_transport_write_message(address_item *addr, int fd, int options,
 {
 int written = 0;
 int len;
 {
 int written = 0;
 int len;
-header_line *h;
 BOOL use_crlf  = (options & topt_use_crlf)  != 0;
 
 /* Initialize pointer in output buffer. */
 BOOL use_crlf  = (options & topt_use_crlf)  != 0;
 
 /* Initialize pointer in output buffer. */
@@ -739,156 +916,11 @@ if ((options & topt_no_headers) == 0)
   /* 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
   /* 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. Then check addr->p.remove_headers too, provided that
+  match any entries therein. Then check addr->prop.remove_headers too, provided that
   addr is not NULL. */
   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
   }
 
 /* If the body is required, ensure that the data for check strings (formerly
@@ -917,19 +949,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;
   /* 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. */
 
 
 /* Write out any remaining data in the buffer before returning. */
 
@@ -938,145 +970,194 @@ return (len = chunk_ptr - deliver_out_buffer) <= 0 ||
 }
 
 
 }
 
 
-#ifdef EXPERIMENTAL_DOMAINKEYS
-
-/**********************************************************************************
-*    External interface to write the message, while signing it with 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.
-   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:
-               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
+#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:
+   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
 
 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 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 save_errno = 0;
-  BOOL rc;
-  uschar dk_spool_name[256];
-  char sbuf[2048];
-  int sread = 0;
-  int wwritten = 0;
-  uschar *dk_signature = NULL;
-
-  (void)string_format(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)
-    {
-    /* Can't create spool file. Ugh. */
-    rc = FALSE;
-    save_errno = errno;
-    goto CLEANUP;
-    }
+int dkim_fd;
+int save_errno = 0;
+BOOL rc;
+uschar dkim_spool_name[256];
+char sbuf[2048];
+int sread = 0;
+int wwritten = 0;
+uschar *dkim_signature = NULL;
+
+/* If we can't sign, just call the original function. */
+
+if (!(dkim_private_key && dkim_domain && dkim_selector))
+  return transport_write_message(addr, fd, options,
+           size_limit, add_headers, remove_headers,
+           check_string, escape_string, rewrite_rules,
+           rewrite_existflags);
+
+(void)string_format(dkim_spool_name, 256, "%s/input/%s/%s-%d-K",
+       spool_directory, message_subdir, message_id, (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 */
-  rc = transport_write_message(addr, dk_fd, options,
-    size_limit, add_headers, remove_headers,
-    check_string, escape_string, rewrite_rules,
-    rewrite_existflags);
+/* Call original function to write the -K file */
 
 
-  /* Save error state. We must clean up before returning. */
-  if (!rc)
-    {
-    save_errno = errno;
-    goto CLEANUP;
-    }
+rc = transport_write_message(addr, dkim_fd, options,
+  size_limit, add_headers, remove_headers,
+  check_string, escape_string, rewrite_rules,
+  rewrite_existflags);
 
 
-  /* 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);
+/* Save error state. We must clean up before returning. */
+if (!rc)
+  {
+  save_errno = errno;
+  goto CLEANUP;
+  }
 
 
-  if (dk_signature != NULL)
+if (dkim_private_key && dkim_domain && dkim_selector)
+  {
+  /* 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)
+    {
+    if (dkim_strict)
+      {
+      uschar *dkim_strict_result = expand_string(dkim_strict);
+      if (dkim_strict_result)
+       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;
+         }
+      }
+    }
+  else
     {
     {
-    /* Send the signature first */
-    int siglen = Ustrlen(dk_signature);
+    int siglen = Ustrlen(dkim_signature);
     while(siglen > 0)
       {
     while(siglen > 0)
       {
-      #ifdef SUPPORT_TLS
-      if (tls_active == fd) wwritten = tls_write(dk_signature, siglen); else
-      #endif
-      wwritten = write(fd,dk_signature,siglen);
+#ifdef SUPPORT_TLS
+      wwritten = tls_out.active == fd
+       ? tls_write(FALSE, dkim_signature, siglen)
+       : write(fd, dkim_signature, siglen);
+#else
+      wwritten = write(fd, dkim_signature, siglen);
+#endif
       if (wwritten == -1)
         {
       if (wwritten == -1)
         {
-        /* error, bail out */
-        save_errno = errno;
-        rc = FALSE;
-        goto CLEANUP;
-        }
+       /* error, bail out */
+       save_errno = errno;
+       rc = FALSE;
+       goto CLEANUP;
+       }
       siglen -= wwritten;
       siglen -= wwritten;
-      dk_signature += wwritten;
+      dkim_signature += wwritten;
       }
     }
       }
     }
-  else if (dk_strict != NULL)
+  }
+
+#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 != fd)
+  {
+  off_t size = lseek(dkim_fd, 0, SEEK_END); /* Fetch file size */
+  ssize_t copied = 0;
+  off_t offset = 0;
+
+  /* Rewind file */
+  lseek(dkim_fd, 0, SEEK_SET);
+
+  while(copied >= 0 && offset < size)
+    copied = sendfile(fd, dkim_fd, &offset, size - offset);
+  if (copied < 0)
     {
     {
-    uschar *dk_strict_result = expand_string(dk_strict);
-    if (dk_strict_result != NULL)
-      {
-      if ( (strcmpic(dk_strict,US"1") == 0) ||
-           (strcmpic(dk_strict,US"true") == 0) )
-        {
-        save_errno = errno;
-        rc = FALSE;
-        goto CLEANUP;
-        }
-      }
+    save_errno = errno;
+    rc = FALSE;
     }
     }
+  }
+else
 
 
-  /* Rewind file and send it down the original fd. */
-  lseek(dk_fd, 0, SEEK_SET);
+#endif
+
+  {
+  /* Rewind file */
+  lseek(dkim_fd, 0, SEEK_SET);
 
 
-  while((sread = read(dk_fd,sbuf,2048)) > 0)
+  /* Send file down the original fd */
+  while((sread = read(dkim_fd, sbuf, 2048)) > 0)
     {
     char *p = sbuf;
     /* write the chunk */
     {
     char *p = sbuf;
     /* write the chunk */
-    DK_WRITE:
-    #ifdef SUPPORT_TLS
-    if (tls_active == fd) wwritten = tls_write(US p, sread); else
-    #endif
-    wwritten = write(fd,p,sread);
-    if (wwritten == -1)
-      {
-      /* error, bail out */
-      save_errno = errno;
-      rc = FALSE;
-      goto CLEANUP;
-      }
-    if (wwritten < sread)
+
+    while (sread)
       {
       {
-      /* short write, try again */
+#ifdef SUPPORT_TLS
+      wwritten = tls_out.active == fd
+       ? tls_write(FALSE, US p, sread)
+       : write(fd, p, sread);
+#else
+      wwritten = write(fd, p, sread);
+#endif
+      if (wwritten == -1)
+       {
+       /* error, bail out */
+       save_errno = errno;
+       rc = FALSE;
+       goto CLEANUP;
+       }
       p += wwritten;
       sread -= wwritten;
       p += wwritten;
       sread -= wwritten;
-      goto DK_WRITE;
       }
     }
 
       }
     }
 
@@ -1084,20 +1165,21 @@ dk_transport_write_message(address_item *addr, int fd, int options,
     {
     save_errno = errno;
     rc = FALSE;
     {
     save_errno = errno;
     rc = FALSE;
-    goto CLEANUP;
     }
     }
+  }
 
 
-
-  CLEANUP:
-  /* unlink -K file */
-  (void)close(dk_fd);
-  Uunlink(dk_spool_name);
-  errno = save_errno;
-  return rc;
+CLEANUP:
+/* unlink -K file */
+(void)close(dkim_fd);
+Uunlink(dkim_spool_name);
+errno = save_errno;
+return rc;
 }
 }
+
 #endif
 
 
 #endif
 
 
+
 /*************************************************
 *    External interface to write the message     *
 *************************************************/
 /*************************************************
 *    External interface to write the message     *
 *************************************************/
@@ -1165,8 +1247,8 @@ yield = FALSE;
 write_pid = (pid_t)(-1);
 
 (void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
 write_pid = (pid_t)(-1);
 
 (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);
+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);
 if (filter_pid < 0) goto TIDY_UP;      /* errno set */
 
 (void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) & ~FD_CLOEXEC);
 if (filter_pid < 0) goto TIDY_UP;      /* errno set */
 
@@ -1190,9 +1272,14 @@ if ((write_pid = fork()) == 0)
     size_limit, add_headers, remove_headers, NULL, NULL,
     rewrite_rules, rewrite_existflags);
   save_errno = errno;
     size_limit, add_headers, remove_headers, NULL, NULL,
     rewrite_rules, rewrite_existflags);
   save_errno = errno;
-  (void)write(pfd[pipe_write], (void *)&rc, sizeof(BOOL));
-  (void)write(pfd[pipe_write], (void *)&save_errno, sizeof(int));
-  (void)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;
   _exit(0);
   }
 save_errno = errno;
@@ -1303,11 +1390,11 @@ if (write_pid > 0)
     if (rc == 0)
       {
       BOOL ok;
     if (rc == 0)
       {
       BOOL ok;
-      (void)read(pfd[pipe_read], (void *)&ok, sizeof(BOOL));
+      int dummy = read(pfd[pipe_read], (void *)&ok, sizeof(BOOL));
       if (!ok)
         {
       if (!ok)
         {
-        (void)read(pfd[pipe_read], (void *)&save_errno, sizeof(int));
-        (void)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;
         }
       }
         yield = FALSE;
         }
       }
@@ -1389,8 +1476,7 @@ better.
 Old records should eventually get swept up by the exim_tidydb utility.
 
 Arguments:
 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
   tpname    name of the transport
 
 Returns:    nothing
@@ -1400,11 +1486,13 @@ void
 transport_update_waiting(host_item *hostlist, uschar *tpname)
 {
 uschar buffer[256];
 transport_update_waiting(host_item *hostlist, uschar *tpname)
 {
 uschar buffer[256];
-uschar *prevname = US"";
+const uschar *prevname = US"";
 host_item *host;
 open_db dbblock;
 open_db *dbm_file;
 
 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);
 /* Open the database for this transport */
 
 sprintf(CS buffer, "wait-%.200s", tpname);
@@ -1412,8 +1500,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
 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)
   {
 
 for (host = hostlist; host!= NULL; host = host->next)
   {
@@ -1422,10 +1509,6 @@ for (host = hostlist; host!= NULL; host = host->next)
   uschar *s;
   int i, host_length;
 
   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. */
 
   /* Skip if this is the same host as we just processed; otherwise remember
   the name for next time. */
 
@@ -1475,7 +1558,11 @@ for (host = hostlist; host!= NULL; host = host->next)
 
   /* If this message is already in a record, no need to update. */
 
 
   /* 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
 
 
   /* If this record is full, write it out with a new name constructed
@@ -1511,6 +1598,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);
   /* 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 */
   }
 
 /* All now done */
@@ -1539,20 +1627,39 @@ Arguments:
                        as set by the caller transport
   new_message_id     set to the message id of a waiting message
   more               set TRUE if there are yet more messages waiting
                        as set by the caller transport
   new_message_id     set to the message id of a waiting message
   more               set TRUE if there are yet more messages waiting
+  oicf_func          function to call to validate if it is ok to send
+                     to this message_id from the current instance.
+  oicf_data          opaque data for oicf_func
 
 Returns:             TRUE if new_message_id set; FALSE otherwise
 */
 
 
 Returns:             TRUE if new_message_id set; FALSE otherwise
 */
 
+typedef struct msgq_s
+{
+    uschar  message_id [MESSAGE_ID_LENGTH + 1];
+    BOOL    bKeep;
+} msgq_t;
+
 BOOL
 BOOL
-transport_check_waiting(uschar *transport_name, uschar *hostname,
-  int local_message_max, uschar *new_message_id, BOOL *more)
+transport_check_waiting(const uschar *transport_name, const uschar *hostname,
+  int local_message_max, uschar *new_message_id, BOOL *more, oicf oicf_func, void *oicf_data)
 {
 dbdata_wait *host_record;
 {
 dbdata_wait *host_record;
-int host_length, path_len;
+int host_length;
 open_db dbblock;
 open_db *dbm_file;
 uschar buffer[256];
 
 open_db dbblock;
 open_db *dbm_file;
 uschar buffer[256];
 
+msgq_t      *msgq = NULL;
+int         msgq_count = 0;
+int         msgq_actual = 0;
+int         i;
+BOOL        bFound = FALSE;
+uschar      spool_dir [PATH_MAX];
+uschar      spool_file [PATH_MAX];
+struct stat statbuf;
+BOOL        bContinuation = FALSE;
+
 *more = FALSE;
 
 DEBUG(D_transport)
 *more = FALSE;
 
 DEBUG(D_transport)
@@ -1605,58 +1712,107 @@ until one is found for which a spool file actually exists. If the record gets
 emptied, delete it and continue with any continuation records that may exist.
 */
 
 emptied, delete it and continue with any continuation records that may exist.
 */
 
-host_length = host_record->count * MESSAGE_ID_LENGTH;
+/* For Bug 1141, I refactored this major portion of the routine, it is risky
+but the 1 off will remain without it.  This code now allows me to SKIP over
+a message I do not want to send out on this run.  */
 
 
-/* Loop to handle continuation host records in the database */
+sprintf(CS spool_dir, "%s/input/", spool_directory);
 
 
-for (;;)
+host_length = host_record->count * MESSAGE_ID_LENGTH;
+
+while (1)
   {
   {
-  BOOL found = FALSE;
+  /* create an array to read entire message queue into memory for processing  */
 
 
-  sprintf(CS buffer, "%s/input/", spool_directory);
-  path_len = Ustrlen(buffer);
+  msgq = (msgq_t*) malloc(sizeof(msgq_t) * host_record->count);
+  msgq_count = host_record->count;
+  msgq_actual = msgq_count;
 
 
-  for (host_length -= MESSAGE_ID_LENGTH; host_length >= 0;
-       host_length -= MESSAGE_ID_LENGTH)
+  for (i = 0; i < host_record->count; ++i)
     {
     {
-    struct stat statbuf;
-    Ustrncpy(new_message_id, host_record->text + host_length,
+    msgq[i].bKeep = TRUE;
+
+    Ustrncpy(msgq[i].message_id, host_record->text + (i * MESSAGE_ID_LENGTH),
       MESSAGE_ID_LENGTH);
       MESSAGE_ID_LENGTH);
-    new_message_id[MESSAGE_ID_LENGTH] = 0;
+    msgq[i].message_id[MESSAGE_ID_LENGTH] = 0;
+    }
+
+  /* first thing remove current message id if it exists */
+
+  for (i = 0; i < msgq_count; ++i)
+    if (Ustrcmp(msgq[i].message_id, message_id) == 0)
+      {
+      msgq[i].bKeep = FALSE;
+      break;
+      }
+
+  /* now find the next acceptable message_id */
+
+  bFound = FALSE;
 
 
+  for (i = msgq_count - 1; i >= 0; --i) if (msgq[i].bKeep)
+    {
     if (split_spool_directory)
     if (split_spool_directory)
-      sprintf(CS(buffer + path_len), "%c/%s-D", new_message_id[5], new_message_id);
+       sprintf(CS spool_file, "%s%c/%s-D",
+                     spool_dir, msgq[i].message_id[5], msgq[i].message_id);
     else
     else
-      sprintf(CS(buffer + path_len), "%s-D", new_message_id);
-
-    /* The listed message may be the one we are currently processing. If
-    so, we want to remove it from the list without doing anything else.
-    If not, do a stat to see if it is an existing message. If it is, break
-    the loop to handle it. No need to bother about locks; as this is all
-    "hint" processing, it won't matter if it doesn't exist by the time exim
-    actually tries to deliver it. */
+       sprintf(CS spool_file, "%s%s-D", spool_dir, msgq[i].message_id);
 
 
-    if (Ustrcmp(new_message_id, message_id) != 0 &&
-        Ustat(buffer, &statbuf) == 0)
+    if (Ustat(spool_file, &statbuf) != 0)
+      msgq[i].bKeep = FALSE;
+    else if (!oicf_func || oicf_func(msgq[i].message_id, oicf_data))
       {
       {
-      found = TRUE;
+      Ustrcpy(new_message_id, msgq[i].message_id);
+      msgq[i].bKeep = FALSE;
+      bFound = TRUE;
       break;
       }
     }
 
       break;
       }
     }
 
-  /* If we have removed all the message ids from the record delete the record.
-  If there is a continuation record, fetch it and remove it from the file,
-  as it will be rewritten as the main record. Repeat in the case of an
-  empty continuation. */
+  /* re-count */
+  for (msgq_actual = 0, i = 0; i < msgq_count; ++i)
+    if (msgq[i].bKeep)
+      msgq_actual++;
+
+  /* reassemble the host record, based on removed message ids, from in
+   * memory queue.
+   */
+
+  if (msgq_actual <= 0)
+    {
+    host_length = 0;
+    host_record->count = 0;
+    }
+  else
+    {
+    host_length = msgq_actual * MESSAGE_ID_LENGTH;
+    host_record->count = msgq_actual;
+
+    if (msgq_actual < msgq_count)
+      {
+      int new_count;
+      for (new_count = 0, i = 0; i < msgq_count; ++i)
+       if (msgq[i].bKeep)
+         Ustrncpy(&host_record->text[new_count++ * MESSAGE_ID_LENGTH],
+           msgq[i].message_id, MESSAGE_ID_LENGTH);
+
+      host_record->text[new_count * MESSAGE_ID_LENGTH] = 0;
+      }
+    }
+
+/* Jeremy: check for a continuation record, this code I do not know how to
+test but the code should work */
+
+  bContinuation = FALSE;
 
   while (host_length <= 0)
     {
     int i;
 
   while (host_length <= 0)
     {
     int i;
-    dbdata_wait *newr = NULL;
+    dbdata_wait * newr = NULL;
 
     /* Search for a continuation */
 
 
     /* Search for a continuation */
 
-    for (i = host_record->sequence - 1; i >= 0 && newr == NULL; i--)
+    for (i = host_record->sequence - 1; i >= 0 && !newr; i--)
       {
       sprintf(CS buffer, "%.200s:%d", hostname, i);
       newr = dbfn_read(dbm_file, buffer);
       {
       sprintf(CS buffer, "%.200s:%d", hostname, i);
       newr = dbfn_read(dbm_file, buffer);
@@ -1664,7 +1820,7 @@ for (;;)
 
     /* If no continuation, delete the current and break the loop */
 
 
     /* If no continuation, delete the current and break the loop */
 
-    if (newr == NULL)
+    if (!newr)
       {
       dbfn_delete(dbm_file, hostname);
       break;
       {
       dbfn_delete(dbm_file, hostname);
       break;
@@ -1675,11 +1831,12 @@ for (;;)
     dbfn_delete(dbm_file, buffer);
     host_record = newr;
     host_length = host_record->count * MESSAGE_ID_LENGTH;
     dbfn_delete(dbm_file, buffer);
     host_record = newr;
     host_length = host_record->count * MESSAGE_ID_LENGTH;
-    }
 
 
-  /* If we found an existing message, break the continuation loop. */
+    bContinuation = TRUE;
+    }
 
 
-  if (found) break;
+  if (bFound)
+    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
 
   /* 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
@@ -1691,6 +1848,26 @@ for (;;)
     DEBUG(D_transport) debug_printf("waiting messages already delivered\n");
     return FALSE;
     }
     DEBUG(D_transport) debug_printf("waiting messages already delivered\n");
     return FALSE;
     }
+
+  /* we were not able to find an acceptable message, nor was there a
+   * continuation record.  So bug out, outer logic will clean this up.
+   */
+
+  if (!bContinuation)
+    {
+    Ustrcpy (new_message_id, message_id); 
+    dbfn_close(dbm_file);
+    return FALSE;
+    }
+  }            /* we need to process a continuation record */
+
+/* clean up in memory queue */
+if (msgq)
+  {
+  free (msgq);
+  msgq = NULL;
+  msgq_count = 0;
+  msgq_actual = 0;
   }
 
 /* Control gets here when an existing message has been encountered; its
   }
 
 /* Control gets here when an existing message has been encountered; its
@@ -1700,7 +1877,19 @@ record if required, close the database, and return TRUE. */
 
 if (host_length > 0)
   {
 
 if (host_length > 0)
   {
+  uschar  msg [MESSAGE_ID_LENGTH + 1];
+  int i;
+
   host_record->count = host_length/MESSAGE_ID_LENGTH;
   host_record->count = host_length/MESSAGE_ID_LENGTH;
+
+  /* rebuild the host_record->text */
+
+  for (i = 0; i < host_record->count; ++i)
+    {
+    Ustrncpy(msg, host_record->text + (i*MESSAGE_ID_LENGTH), MESSAGE_ID_LENGTH);
+    msg[MESSAGE_ID_LENGTH] = 0;
+    }
+
   dbfn_write(dbm_file, hostname, host_record, (int)sizeof(dbdata_wait) + host_length);
   *more = TRUE;
   }
   dbfn_write(dbm_file, hostname, host_record, (int)sizeof(dbdata_wait) + host_length);
   *more = TRUE;
   }
@@ -1709,8 +1898,6 @@ dbfn_close(dbm_file);
 return TRUE;
 }
 
 return TRUE;
 }
 
-
-
 /*************************************************
 *    Deliver waiting message down same socket    *
 *************************************************/
 /*************************************************
 *    Deliver waiting message down same socket    *
 *************************************************/
@@ -1730,8 +1917,8 @@ Returns:          FALSE if fork fails; TRUE otherwise
 */
 
 BOOL
 */
 
 BOOL
-transport_pass_socket(uschar *transport_name, uschar *hostname,
-  uschar *hostaddress, uschar *id, int socket_fd)
+transport_pass_socket(const uschar *transport_name, const uschar *hostname,
+  const uschar *hostaddress, uschar *id, int socket_fd)
 {
 pid_t pid;
 int status;
 {
 pid_t pid;
 int status;
@@ -1741,7 +1928,7 @@ DEBUG(D_transport) debug_printf("transport_pass_socket entered\n");
 if ((pid = fork()) == 0)
   {
   int i = 16;
 if ((pid = fork()) == 0)
   {
   int i = 16;
-  uschar **argv;
+  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,
 
   /* 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,
@@ -1754,7 +1941,10 @@ if ((pid = fork()) == 0)
   /* Set up the calling arguments; use the standard function for the basics,
   but we have a number of extras that may be added. */
 
   /* Set up the calling arguments; use the standard function for the basics,
   but we have a number of extras that may be added. */
 
-  argv = child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0);
+  argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, &i, FALSE, 0);
+
+  /* Call with the dsn flag */
+  if (smtp_use_dsn) argv[i++] = US"-MCD";
 
   if (smtp_authenticated) argv[i++] = US"-MCA";
 
 
   if (smtp_authenticated) argv[i++] = US"-MCA";
 
@@ -1773,9 +1963,9 @@ if ((pid = fork()) == 0)
     }
 
   argv[i++] = US"-MC";
     }
 
   argv[i++] = US"-MC";
-  argv[i++] = transport_name;
-  argv[i++] = hostname;
-  argv[i++] = hostaddress;
+  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;
   argv[i++] = string_sprintf("%d", continue_sequence + 1);
   argv[i++] = id;
   argv[i++] = NULL;
@@ -1829,7 +2019,7 @@ case, no addresses are passed.
 
 Arguments:
   argvptr            pointer to anchor for argv vector
 
 Arguments:
   argvptr            pointer to anchor for argv vector
-  cmd                points to the command string
+  cmd                points to the command string (modified IN PLACE)
   expand_arguments   true if expansion is to occur
   expand_failed      error value to set if expansion fails; not relevant if
                      addr == NULL
   expand_arguments   true if expansion is to occur
   expand_failed      error value to set if expansion fails; not relevant if
                      addr == NULL
@@ -1843,11 +2033,12 @@ Returns:             TRUE if all went well; otherwise an error will be
 */
 
 BOOL
 */
 
 BOOL
-transport_set_up_command(uschar ***argvptr, uschar *cmd, BOOL expand_arguments,
-  int expand_failed, address_item *addr, uschar *etext, uschar **errptr)
+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;
 {
 address_item *ad;
-uschar **argv;
+const uschar **argv;
 uschar *s, *ss;
 int address_count = 0;
 int argcount = 0;
 uschar *s, *ss;
 int address_count = 0;
 int argcount = 0;
@@ -1881,7 +2072,7 @@ while (*s != 0 && argcount < max_args)
     if (*s != 0) s++;
     *ss++ = 0;
     }
     if (*s != 0) s++;
     *ss++ = 0;
     }
-  else argv[argcount++] = string_dequote(&s);
+  else argv[argcount++] = string_copy(string_dequote(CUSS &s));
   while (isspace(*s)) s++;
   }
 
   while (isspace(*s)) s++;
   }
 
@@ -1955,7 +2146,123 @@ if (expand_arguments)
         memmove(argv + i + 1 + additional, argv + i + 1,
           (argcount - i)*sizeof(uschar *));
 
         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_copy(string_dequote(CUSS &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--;
       }
 
       i--;
       }
 
@@ -1963,9 +2270,9 @@ if (expand_arguments)
 
     else
       {
 
     else
       {
-      uschar *expanded_arg;
+      const uschar *expanded_arg;
       enable_dollar_recipients = allow_dollar_recipients;
       enable_dollar_recipients = allow_dollar_recipients;
-      expanded_arg = expand_string(argv[i]);
+      expanded_arg = expand_cstring(argv[i]);
       enable_dollar_recipients = FALSE;
 
       if (expanded_arg == NULL)
       enable_dollar_recipients = FALSE;
 
       if (expanded_arg == NULL)
@@ -1996,4 +2303,6 @@ if (expand_arguments)
 return TRUE;
 }
 
 return TRUE;
 }
 
+/* vi: aw ai sw=2
+*/
 /* End of transport.c */
 /* End of transport.c */