Support transport-added headers under cutthrough delivery. Bug 1431
[users/jgh/exim.git] / src / src / transport.c
index 504ef9e06aaa2f8fbd994d46dd1764655b192f58..549da469423d3c84fed4642c6677db18cfd4e113 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/transport.c,v 1.26 2009/12/15 08:23:15 tom Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2013 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* General functions concerned with transportation, and generic options for all
@@ -70,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) },
@@ -96,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,
@@ -221,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;
@@ -233,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;
@@ -324,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);
@@ -598,6 +600,175 @@ 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. Then check addr->p.remove_headers too, provided that
+addr is not NULL. */
+
+if (remove_headers)
+  {
+  uschar *s = expand_string(remove_headers);
+  if (!s && !expand_string_forcedfail)
+    {
+    errno = ERRNO_CHHEADER_FAIL;
+    return FALSE;
+    }
+  remove_headers = s;
+  }
+
+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 = 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, 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)
+  {
+  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 (!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(s):\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               *
 *************************************************/
@@ -745,154 +916,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
@@ -921,19 +947,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. */
 
@@ -1048,7 +1074,7 @@ dkim_transport_write_message(address_item *addr, int fd, int options,
       int siglen = Ustrlen(dkim_signature);
       while(siglen > 0) {
         #ifdef SUPPORT_TLS
-        if (tls_active == fd) wwritten = tls_write(dkim_signature, siglen); else
+        if (tls_out.active == fd) wwritten = tls_write(FALSE, dkim_signature, siglen); else
         #endif
         wwritten = write(fd,dkim_signature,siglen);
         if (wwritten == -1) {
@@ -1074,7 +1100,7 @@ dkim_transport_write_message(address_item *addr, int fd, int options,
      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_active != fd)
+  if (tls_out.active != fd)
     {
     ssize_t copied = 0;
     off_t offset = 0;
@@ -1098,7 +1124,7 @@ dkim_transport_write_message(address_item *addr, int fd, int options,
     /* write the chunk */
     DKIM_WRITE:
     #ifdef SUPPORT_TLS
-    if (tls_active == fd) wwritten = tls_write(US 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)
@@ -1228,9 +1254,14 @@ if ((write_pid = fork()) == 0)
     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;
@@ -1341,11 +1372,11 @@ if (write_pid > 0)
     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)
         {
-        (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;
         }
       }
@@ -1994,7 +2025,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--;
       }
 
@@ -2035,4 +2181,6 @@ if (expand_arguments)
 return TRUE;
 }
 
+/* vi: aw ai sw=2
+*/
 /* End of transport.c */