Fix spurious detection of timeout while writing to transport filter
[exim.git] / src / src / transport.c
index fb74dfdcde44919d4b99c736f025186dda4de57e..e4aaa7debd889e8e2789883ddafa5c80337485cf 100644 (file)
@@ -15,75 +15,76 @@ transports. */
 data blocks and which therefore have the opt_public flag set. Note that there
 are other options living inside this structure which can be set only from
 certain transports. */
+#define LOFF(field) OPT_OFF(transport_instance, field)
 
 optionlist optionlist_transports[] = {
   /*   name            type                                    value */
   { "*expand_group",    opt_stringptr|opt_hidden|opt_public,
-                 (void *)offsetof(transport_instance, expand_gid) },
+                 LOFF(expand_gid) },
   { "*expand_user",     opt_stringptr|opt_hidden|opt_public,
-                 (void *)offsetof(transport_instance, expand_uid) },
+                 LOFF(expand_uid) },
   { "*headers_rewrite_flags", opt_int|opt_public|opt_hidden,
-                 (void *)offsetof(transport_instance, rewrite_existflags) },
+                 LOFF(rewrite_existflags) },
   { "*headers_rewrite_rules", opt_void|opt_public|opt_hidden,
-                 (void *)offsetof(transport_instance, rewrite_rules) },
+                 LOFF(rewrite_rules) },
   { "*set_group",       opt_bool|opt_hidden|opt_public,
-                 (void *)offsetof(transport_instance, gid_set) },
+                 LOFF(gid_set) },
   { "*set_user",        opt_bool|opt_hidden|opt_public,
-                 (void *)offsetof(transport_instance, uid_set) },
+                 LOFF(uid_set) },
   { "body_only",        opt_bool|opt_public,
-                 (void *)offsetof(transport_instance, body_only) },
+                 LOFF(body_only) },
   { "current_directory", opt_stringptr|opt_public,
-                 (void *)offsetof(transport_instance, current_dir) },
+                 LOFF(current_dir) },
   { "debug_print",      opt_stringptr | opt_public,
-                 (void *)offsetof(transport_instance, debug_string) },
+                 LOFF(debug_string) },
   { "delivery_date_add", opt_bool|opt_public,
-                 (void *)(offsetof(transport_instance, delivery_date_add)) },
+                 LOFF(delivery_date_add) },
   { "disable_logging",  opt_bool|opt_public,
-                 (void *)(offsetof(transport_instance, disable_logging)) },
+                 LOFF(disable_logging) },
   { "driver",           opt_stringptr|opt_public,
-                 (void *)offsetof(transport_instance, driver_name) },
+                 LOFF(driver_name) },
   { "envelope_to_add",   opt_bool|opt_public,
-                 (void *)(offsetof(transport_instance, envelope_to_add)) },
+                 LOFF(envelope_to_add) },
 #ifndef DISABLE_EVENT
   { "event_action",     opt_stringptr | opt_public,
-                 (void *)offsetof(transport_instance, event_action) },
+                 LOFF(event_action) },
 #endif
   { "group",             opt_expand_gid|opt_public,
-                 (void *)offsetof(transport_instance, gid) },
+                 LOFF(gid) },
   { "headers_add",      opt_stringptr|opt_public|opt_rep_str,
-                 (void *)offsetof(transport_instance, add_headers) },
+                 LOFF(add_headers) },
   { "headers_only",     opt_bool|opt_public,
-                 (void *)offsetof(transport_instance, headers_only) },
+                 LOFF(headers_only) },
   { "headers_remove",   opt_stringptr|opt_public|opt_rep_str,
-                 (void *)offsetof(transport_instance, remove_headers) },
+                 LOFF(remove_headers) },
   { "headers_rewrite",  opt_rewrite|opt_public,
-                 (void *)offsetof(transport_instance, headers_rewrite) },
+                 LOFF(headers_rewrite) },
   { "home_directory",   opt_stringptr|opt_public,
-                 (void *)offsetof(transport_instance, home_dir) },
+                 LOFF(home_dir) },
   { "initgroups",       opt_bool|opt_public,
-                 (void *)offsetof(transport_instance, initgroups) },
+                 LOFF(initgroups) },
   { "max_parallel",     opt_stringptr|opt_public,
-                 (void *)offsetof(transport_instance, max_parallel) },
+                 LOFF(max_parallel) },
   { "message_size_limit", opt_stringptr|opt_public,
-                 (void *)offsetof(transport_instance, message_size_limit) },
+                 LOFF(message_size_limit) },
   { "rcpt_include_affixes", opt_bool|opt_public,
-                 (void *)offsetof(transport_instance, rcpt_include_affixes) },
+                 LOFF(rcpt_include_affixes) },
   { "retry_use_local_part", opt_bool|opt_public,
-                 (void *)offsetof(transport_instance, retry_use_local_part) },
+                 LOFF(retry_use_local_part) },
   { "return_path",      opt_stringptr|opt_public,
-                 (void *)(offsetof(transport_instance, return_path)) },
+                 LOFF(return_path) },
   { "return_path_add",   opt_bool|opt_public,
-                 (void *)(offsetof(transport_instance, return_path_add)) },
+                 LOFF(return_path_add) },
   { "shadow_condition", opt_stringptr|opt_public,
-                 (void *)offsetof(transport_instance, shadow_condition) },
+                 LOFF(shadow_condition) },
   { "shadow_transport", opt_stringptr|opt_public,
-                 (void *)offsetof(transport_instance, shadow) },
+                 LOFF(shadow) },
   { "transport_filter", opt_stringptr|opt_public,
-                 (void *)offsetof(transport_instance, filter_command) },
+                 LOFF(filter_command) },
   { "transport_filter_timeout", opt_time|opt_public,
-                 (void *)offsetof(transport_instance, filter_timeout) },
+                 LOFF(filter_timeout) },
   { "user",             opt_expand_uid|opt_public,
-                 (void *)offsetof(transport_instance, uid) }
+                 LOFF(uid) }
 };
 
 int optionlist_transports_size = nelem(optionlist_transports);
@@ -172,6 +173,20 @@ for (transport_instance * t = transports; t; t = t->next)
 *             Write block of data                *
 *************************************************/
 
+static int
+tpt_write(int fd, uschar * block, int len, BOOL more, int options)
+{
+return
+#ifndef DISABLE_TLS
+  tls_out.active.sock == fd
+    ? tls_write(tls_out.active.tls_ctx, block, len, more) :
+#endif
+#ifdef MSG_MORE
+  more && !(options & topt_not_socket) ? send(fd, block, len, MSG_MORE) :
+#endif
+  write(fd, block, len);
+}
+
 /* Subroutine called by write_chunk() and at the end of the message actually
 to write a data block. Also called directly by some transports to write
 additional data to the file descriptor (e.g. prefix, suffix).
@@ -215,10 +230,11 @@ Returns:    TRUE on success, FALSE on failure (with errno preserved);
 */
 
 static BOOL
-transport_write_block_fd(transport_ctx * tctx, uschar *block, int len, BOOL more)
+transport_write_block_fd(transport_ctx * tctx, uschar * block, int len, BOOL more)
 {
 int rc, save_errno;
 int local_timeout = transport_write_timeout;
+int connretry = 1;
 int fd = tctx->u.fd;
 
 /* This loop is for handling incomplete writes and other retries. In most
@@ -230,48 +246,43 @@ for (int i = 0; i < 100; i++)
     debug_printf("writing data block fd=%d size=%d timeout=%d%s\n",
       fd, len, local_timeout, more ? " (more expected)" : "");
 
-  /* This code makes use of alarm() in order to implement the timeout. This
-  isn't a very tidy way of doing things. Using non-blocking I/O with select()
-  provides a neater approach. However, I don't know how to do this when TLS is
-  in use. */
+  /* When doing TCP Fast Open we may get this far before the 3-way handshake
+  is complete, and write returns ENOTCONN.  Detect that, wait for the socket
+  to become writable, and retry once only. */
 
-  if (transport_write_timeout <= 0)   /* No timeout wanted */
+  for(;;)
     {
-    rc =
-#ifdef SUPPORT_TLS
-       tls_out.active.sock == fd ? tls_write(tls_out.active.tls_ctx, block, len, more) :
-#endif
-#ifdef MSG_MORE
-       more && !(tctx->options & topt_not_socket)
-         ? send(fd, block, len, MSG_MORE) :
-#endif
-       write(fd, block, len);
-    save_errno = errno;
-    }
-
-  /* Timeout wanted. */
+    fd_set fds;
+    /* This code makes use of alarm() in order to implement the timeout. This
+    isn't a very tidy way of doing things. Using non-blocking I/O with select()
+    provides a neater approach. However, I don't know how to do this when TLS is
+    in use. */
 
-  else
-    {
-    ALARM(local_timeout);
-
-    rc =
-#ifdef SUPPORT_TLS
-       tls_out.active.sock == fd ? tls_write(tls_out.active.tls_ctx, block, len, more) :
-#endif
-#ifdef MSG_MORE
-       more && !(tctx->options & topt_not_socket)
-         ? send(fd, block, len, MSG_MORE) :
-#endif
-       write(fd, block, len);
-
-    save_errno = errno;
-    local_timeout = ALARM_CLR(0);
-    if (sigalrm_seen)
+    if (transport_write_timeout <= 0)   /* No timeout wanted */
       {
-      errno = ETIMEDOUT;
-      return FALSE;
+      rc = tpt_write(fd, block, len, more, tctx->options);
+      save_errno = errno;
+      }
+    else                               /* Timeout wanted. */
+      {
+      sigalrm_seen = FALSE;
+      ALARM(local_timeout);
+       rc = tpt_write(fd, block, len, more, tctx->options);
+       save_errno = errno;
+      local_timeout = ALARM_CLR(0);
+      if (sigalrm_seen)
+       {
+       errno = ETIMEDOUT;
+       return FALSE;
+       }
       }
+
+    if (rc >= 0 || errno != ENOTCONN || connretry <= 0)
+      break;
+
+    FD_ZERO(&fds); FD_SET(fd, &fds);
+    select(fd+1, NULL, &fds, NULL, NULL);      /* could set timout? */
+    connretry--;
     }
 
   /* Hopefully, the most common case is success, so test that first. */
@@ -375,8 +386,11 @@ transport_ctx tctx = {{0}};
 gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
 va_list ap;
 
+/* Use taint-unchecked routines for writing into big_buffer, trusting
+that the result will never be expanded. */
+
 va_start(ap, format);
-if (!string_vformat(&gs, FALSE, format, ap))
+if (!string_vformat(&gs, SVFMT_TAINT_NOCHK, format, ap))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong formatted string in transport");
 va_end(ap);
 tctx.u.fd = fd;
@@ -578,14 +592,14 @@ if (include_affixes)
   return addr->address;
   }
 
-if (addr->suffix == NULL)
+if (!addr->suffix)
   {
-  if (addr->prefix == NULL) return addr->address;
+  if (!addr->prefix) return addr->address;
   return addr->address + Ustrlen(addr->prefix);
   }
 
 at = Ustrrchr(addr->address, '@');
-plen = (addr->prefix == NULL)? 0 : Ustrlen(addr->prefix);
+plen = addr->prefix ? Ustrlen(addr->prefix) : 0;
 slen = Ustrlen(addr->suffix);
 
 return string_sprintf("%.*s@%s", (int)(at - addr->address - plen - slen),
@@ -638,7 +652,7 @@ so that we don't handle it again. */
 
 for (ppp = *pdlist; ppp; ppp = ppp->next) if (p == ppp->ptr) return TRUE;
 
-ppp = store_get(sizeof(struct aci));
+ppp = store_get(sizeof(struct aci), FALSE);
 ppp->next = *pdlist;
 *pdlist = ppp;
 ppp->ptr = p;
@@ -662,7 +676,7 @@ if (ppp) return TRUE;
 
 /* Remember what we have output, and output it. */
 
-ppp = store_get(sizeof(struct aci));
+ppp = store_get(sizeof(struct aci), FALSE);
 ppp->next = *pplist;
 *pplist = ppp;
 ppp->ptr = pp;
@@ -725,10 +739,17 @@ for (header_line * h = header_list; h; h = h->next) if (h->type != htype_old)
            return FALSE;
            }
        len = s ? Ustrlen(s) : 0;
-       if (strncmpic(h->text, s, len) != 0) continue;
-       ss = h->text + len;
-       while (*ss == ' ' || *ss == '\t') ss++;
-       if (*ss == ':') break;
+       if (len && s[len-1] == '*')     /* trailing glob */
+         {
+         if (strncmpic(h->text, s, len-1) == 0) break;
+         }
+       else
+         {
+         if (strncmpic(h->text, s, len) != 0) continue;
+         ss = h->text + len;
+         while (*ss == ' ' || *ss == '\t') ss++;
+         if (*ss == ':') break;
+         }
        }
       if (s) { include_header = FALSE; break; }
       }
@@ -742,7 +763,7 @@ for (header_line * h = header_list; h; h = h->next) if (h->type != htype_old)
     {
     if (tblock && tblock->rewrite_rules)
       {
-      void *reset_point = store_get(0);
+      rmark reset_point = store_mark();
       header_line *hh;
 
       if ((hh = rewrite_header(h, NULL, NULL, tblock->rewrite_rules,
@@ -949,7 +970,7 @@ if (!(tctx->options & topt_no_headers))
     BOOL first = TRUE;
     struct aci *plist = NULL;
     struct aci *dlist = NULL;
-    void *reset_point = store_get(0);
+    rmark reset_point = store_mark();
 
     if (!write_chunk(tctx, US"Envelope-to: ", 13)) goto bad;
 
@@ -1248,11 +1269,11 @@ if ((write_pid = fork()) == 0)
         != sizeof(int)
      || write(pfd[pipe_write], (void *)&tctx->addr->more_errno, sizeof(int))
         != sizeof(int)
-     || write(pfd[pipe_write], (void *)&tctx->addr->delivery_usec, sizeof(int))
-        != sizeof(int)
+     || write(pfd[pipe_write], (void *)&tctx->addr->delivery_time, sizeof(struct timeval))
+        != sizeof(struct timeval)
      )
     rc = FALSE;        /* compiler quietening */
-  _exit(0);
+  exim_underbar_exit(0, US"tpt-filter");
   }
 save_errno = errno;
 
@@ -1272,7 +1293,7 @@ if (write_pid < 0)
 
 /* When testing, let the subprocess get going */
 
-if (f.running_in_test_harness) millisleep(250);
+testharness_pause_ms(250);
 
 DEBUG(D_transport)
   debug_printf("process %d writing to transport filter\n", (int)write_pid);
@@ -1375,7 +1396,7 @@ if (write_pid > 0)
         {
        int dummy = read(pfd[pipe_read], (void *)&save_errno, sizeof(int));
         dummy = read(pfd[pipe_read], (void *)&tctx->addr->more_errno, sizeof(int));
-        dummy = read(pfd[pipe_read], (void *)&tctx->addr->delivery_usec, sizeof(int));
+        dummy = read(pfd[pipe_read], (void *)&tctx->addr->delivery_time, sizeof(struct timeval));
        dummy = dummy;          /* compiler quietening */
         yield = FALSE;
         }
@@ -1498,7 +1519,7 @@ for (host_item * host = hostlist; host; host = host->next)
 
   if (!(host_record = dbfn_read(dbm_file, host->name)))
     {
-    host_record = store_get(sizeof(dbdata_wait) + MESSAGE_ID_LENGTH);
+    host_record = store_get(sizeof(dbdata_wait) + MESSAGE_ID_LENGTH, FALSE);
     host_record->count = host_record->sequence = 0;
     }
 
@@ -1540,12 +1561,17 @@ for (host_item * host = hostlist; host; host = host->next)
 
   /* If this record is full, write it out with a new name constructed
   from the sequence number, increase the sequence number, and empty
-  the record. */
+  the record.  If we're doing a two-phase queue run initial phase, ping the
+  daemon to consider running a delivery on this host. */
 
   if (host_record->count >= WAIT_NAME_MAX)
     {
     sprintf(CS buffer, "%.200s:%d", host->name, host_record->sequence);
     dbfn_write(dbm_file, buffer, host_record, sizeof(dbdata_wait) + host_length);
+#ifdef EXPERIMENTAL_QUEUE_RAMP
+    if (f.queue_2stage && queue_fast_ramp && !queue_run_in_order)
+      queue_notify_daemon(message_id);
+#endif
     host_record->sequence++;
     host_record->count = 0;
     host_length = 0;
@@ -1557,7 +1583,7 @@ for (host_item * host = hostlist; host; host = host->next)
   else
     {
     dbdata_wait *newr =
-      store_get(sizeof(dbdata_wait) + host_length + MESSAGE_ID_LENGTH);
+      store_get(sizeof(dbdata_wait) + host_length + MESSAGE_ID_LENGTH, FALSE);
     memcpy(newr, host_record, sizeof(dbdata_wait) + host_length);
     host_record = newr;
     }
@@ -1692,7 +1718,7 @@ while (1)
 
   /* create an array to read entire message queue into memory for processing  */
 
-  msgq = store_malloc(sizeof(msgq_t) * host_record->count);
+  msgq = store_get(sizeof(msgq_t) * host_record->count, FALSE);
   msgq_count = host_record->count;
   msgq_actual = msgq_count;
 
@@ -1700,7 +1726,7 @@ while (1)
     {
     msgq[i].bKeep = TRUE;
 
-    Ustrncpy(msgq[i].message_id, host_record->text + (i * MESSAGE_ID_LENGTH),
+    Ustrncpy_nt(msgq[i].message_id, host_record->text + (i * MESSAGE_ID_LENGTH), 
       MESSAGE_ID_LENGTH);
     msgq[i].message_id[MESSAGE_ID_LENGTH] = 0;
     }
@@ -1719,16 +1745,14 @@ while (1)
   for (i = msgq_count - 1; i >= 0; --i) if (msgq[i].bKeep)
     {
     uschar subdir[2];
+    uschar * mid = msgq[i].message_id;
 
-    subdir[0] = split_spool_directory ? msgq[i].message_id[5] : 0;
-    subdir[1] = 0;
-
-    if (Ustat(spool_fname(US"input", subdir, msgq[i].message_id, US"-D"),
-             &statbuf) != 0)
+    set_subdir_str(subdir, mid, 0);
+    if (Ustat(spool_fname(US"input", subdir, mid, US"-D"), &statbuf) != 0)
       msgq[i].bKeep = FALSE;
-    else if (!oicf_func || oicf_func(msgq[i].message_id, oicf_data))
+    else if (!oicf_func || oicf_func(mid, oicf_data))
       {
-      Ustrcpy(new_message_id, msgq[i].message_id);
+      Ustrcpy_nt(new_message_id, mid);
       msgq[i].bKeep = FALSE;
       bFound = TRUE;
       break;
@@ -1798,10 +1822,7 @@ while (1)
     }
 
   if (bFound)          /* Usual exit from main loop */
-    {
-    store_free (msgq);
     break;
-    }
 
   /* If host_length <= 0 we have emptied a record and not found a good message,
   and there are no continuation records. Otherwise there is a continuation
@@ -1824,8 +1845,6 @@ while (1)
     dbfn_close(dbm_file);
     return FALSE;
     }
-
-  store_free(msgq);
   }            /* we need to process a continuation record */
 
 /* Control gets here when an existing message has been encountered; its
@@ -1867,7 +1886,7 @@ if (smtp_peer_options & OPTION_CHUNKING)  argv[i++] = US"-MCK";
 if (smtp_peer_options & OPTION_DSN)            argv[i++] = US"-MCD";
 if (smtp_peer_options & OPTION_PIPE)           argv[i++] = US"-MCP";
 if (smtp_peer_options & OPTION_SIZE)           argv[i++] = US"-MCS";
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 if (smtp_peer_options & OPTION_TLS)
   if (tls_out.active.sock >= 0 || continue_proxy_cipher)
     {
@@ -1948,7 +1967,7 @@ if ((pid = fork()) == 0)
     DEBUG(D_transport) debug_printf("transport_pass_socket succeeded (final-pid %d)\n", pid);
     _exit(EXIT_SUCCESS);
     }
-  if (f.running_in_test_harness) sleep(1);
+  testharness_pause_ms(1000);
 
   transport_do_pass_socket(transport_name, hostname, hostaddress,
     id, socket_fd);
@@ -2018,7 +2037,7 @@ delivery batch option is set. */
 
 for (address_item * ad = addr; ad; ad = ad->next) address_count++;
 max_args = address_count + 60;
-*argvptr = argv = store_get((max_args+1)*sizeof(uschar *));
+*argvptr = argv = store_get((max_args+1)*sizeof(uschar *), FALSE);
 
 /* Split the command up into arguments terminated by white space. Lose
 trailing space at the start and end. Double-quoted arguments can contain \\ and
@@ -2028,18 +2047,19 @@ arguments are verbatim. Copy each argument into a new string. */
 s = cmd;
 while (isspace(*s)) s++;
 
-while (*s != 0 && argcount < max_args)
+for (; *s != 0 && argcount < max_args; argcount++)
   {
   if (*s == '\'')
     {
     ss = s + 1;
     while (*ss != 0 && *ss != '\'') ss++;
-    argv[argcount++] = ss = store_get(ss - s++);
+    argv[argcount] = ss = store_get(ss - s++, is_tainted(cmd));
     while (*s != 0 && *s != '\'') *ss++ = *s++;
     if (*s != 0) s++;
     *ss++ = 0;
     }
-  else argv[argcount++] = string_copy(string_dequote(CUSS &s));
+  else
+    argv[argcount] = string_dequote(CUSS &s);
   while (isspace(*s)) s++;
   }
 
@@ -2079,8 +2099,8 @@ $recipients. */
 DEBUG(D_transport)
   {
   debug_printf("direct command:\n");
-  for (int i = 0; argv[i] != US 0; i++)
-    debug_printf("  argv[%d] = %s\n", i, string_printing(argv[i]));
+  for (int i = 0; argv[i]; i++)
+    debug_printf("  argv[%d] = '%s'\n", i, string_printing(argv[i]));
   }
 
 if (expand_arguments)
@@ -2133,6 +2153,7 @@ if (expand_arguments)
       int address_pipe_argcount = 0;
       int address_pipe_max_args;
       uschar **address_pipe_argv;
+      BOOL tainted;
 
       /* We can never have more then the argv we will be loading into */
       address_pipe_max_args = max_args - argcount + 1;
@@ -2141,10 +2162,11 @@ if (expand_arguments)
         debug_printf("address_pipe_max_args=%d\n", address_pipe_max_args);
 
       /* We allocate an additional for (uschar *)0 */
-      address_pipe_argv = store_get((address_pipe_max_args+1)*sizeof(uschar *));
+      address_pipe_argv = store_get((address_pipe_max_args+1)*sizeof(uschar *), FALSE);
 
       /* +1 because addr->local_part[0] == '|' since af_force_command is set */
       s = expand_string(addr->local_part + 1);
+      tainted = is_tainted(s);
 
       if (s == NULL || *s == '\0')
         {
@@ -2163,7 +2185,7 @@ if (expand_arguments)
           {
           ss = s + 1;
           while (*ss != 0 && *ss != '\'') ss++;
-          address_pipe_argv[address_pipe_argcount++] = ss = store_get(ss - s++);
+          address_pipe_argv[address_pipe_argcount++] = ss = store_get(ss - s++, tainted);
           while (*s != 0 && *s != '\'') *ss++ = *s++;
           if (*s != 0) s++;
           *ss++ = 0;
@@ -2242,12 +2264,12 @@ if (expand_arguments)
       expanded_arg = expand_cstring(argv[i]);
       f.enable_dollar_recipients = FALSE;
 
-      if (expanded_arg == NULL)
+      if (!expanded_arg)
         {
         uschar *msg = string_sprintf("Expansion of \"%s\" "
           "from command \"%s\" in %s failed: %s",
           argv[i], cmd, etext, expand_string_message);
-        if (addr != NULL)
+        if (addr)
           {
           addr->transport_return = expand_failed;
           addr->message = msg;