Fix bad use of library, copying string over itself
[exim.git] / src / src / deliver.c
index b5469908fb3ffb0e916ac615d2a1f9bdb3e6faa7..4624719f853ed84c9ae462dddd123bd3fe452c95 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* The main code for delivering a message. */
@@ -817,7 +817,7 @@ if (LOGGING(tls_certificate_verified) && addr->cipher)
   s = string_append(s, 2, US" CV=",
     testflag(addr, af_cert_verified)
     ?
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
       testflag(addr, af_dane_verified)
     ? "dane"
     :
@@ -851,7 +851,7 @@ if (action)
   if (!(s = expand_string(action)) && *expand_string_message)
     log_write(0, LOG_MAIN|LOG_PANIC,
       "failed to expand event_action %s in %s: %s\n",
-      event, transport_name, expand_string_message);
+      event, transport_name ? transport_name : US"main", expand_string_message);
 
   event_name = event_data = NULL;
 
@@ -877,20 +877,33 @@ const uschar * save_host = deliver_host;
 const uschar * save_address = deliver_host_address;
 const int      save_port =   deliver_host_port;
 
-if (!addr->transport)
-  return;
-
 router_name =    addr->router ? addr->router->name : NULL;
-transport_name = addr->transport->name;
 deliver_domain = addr->domain;
 deliver_localpart = addr->local_part;
 deliver_host =   addr->host_used ? addr->host_used->name : NULL;
 
-(void) event_raise(addr->transport->event_action, event,
-         addr->host_used
-          || Ustrcmp(addr->transport->driver_name, "smtp") == 0
-         || Ustrcmp(addr->transport->driver_name, "lmtp") == 0
-        ? addr->message : NULL);
+if (!addr->transport)
+  {
+  if (Ustrcmp(event, "msg:fail:delivery") == 0)
+    {
+     /* An address failed with no transport involved. This happens when
+     a filter was used which triggered a fail command (in such a case
+     a transport isn't needed).  Convert it to an internal fail event. */
+
+    (void) event_raise(event_action, US"msg:fail:internal", addr->message);
+    }
+  }
+else
+  {
+  transport_name = addr->transport->name;
+
+  (void) event_raise(addr->transport->event_action, event,
+           addr->host_used
+           || Ustrcmp(addr->transport->driver_name, "smtp") == 0
+           || Ustrcmp(addr->transport->driver_name, "lmtp") == 0
+           || Ustrcmp(addr->transport->driver_name, "autoreply") == 0
+          ? addr->message : NULL);
+  }
 
 deliver_host_port =    save_port;
 deliver_host_address = save_address;
@@ -1095,7 +1108,7 @@ static uschar buf[sizeof("0.000s")];
 if (diff->tv_sec >= 5 || !LOGGING(millisec))
   return readconf_printtime((int)diff->tv_sec);
 
-sprintf(CS buf, "%d.%03ds", (int)diff->tv_sec, (int)diff->tv_usec/1000);
+sprintf(CS buf, "%u.%03us", (uint)diff->tv_sec, (uint)diff->tv_usec/1000);
 return buf;
 }
 
@@ -1234,6 +1247,9 @@ else
       }
     }
 
+  if (LOGGING(pipelining) && testflag(addr, af_pipelining))
+    g = string_catn(g, US" L", 2);
+
 #ifndef DISABLE_PRDR
   if (testflag(addr, af_prdr_used))
     g = string_catn(g, US" PRDR", 5);
@@ -1355,7 +1371,7 @@ if (addr->message)
 /* Log the deferment in the message log, but don't clutter it
 up with retry-time defers after the first delivery attempt. */
 
-if (deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
+if (f.deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
   deliver_msglog("%s %s\n", now, g->s);
 
 /* Write the main log and reset the store.
@@ -1380,6 +1396,16 @@ failure_log(address_item * addr, uschar * driver_kind, uschar * now)
 void * reset_point;
 gstring * g = reset_point = string_get(256);
 
+#ifndef DISABLE_EVENT
+/* Message failures for which we will send a DSN get their event raised
+later so avoid doing it here. */
+
+if (  !addr->prop.ignore_error
+   && !(addr->dsn_flags & (rf_dsnflags & ~rf_notify_failure))
+   )
+  msg_event_raise(US"msg:fail:delivery", addr);
+#endif
+
 /* Build up the log line for the message and main logs */
 
 /* Create the address string for logging. Must not do this earlier, because
@@ -1428,10 +1454,6 @@ else
 
 log_write(0, LOG_MAIN, "** %s", g->s);
 
-#ifndef DISABLE_EVENT
-msg_event_raise(US"msg:fail:delivery", addr);
-#endif
-
 store_reset(reset_point);
 return;
 }
@@ -1475,7 +1497,7 @@ if (driver_type == EXIM_DTYPE_TRANSPORT)
     {
     driver_name = addr->transport->name;
     driver_kind = US" transport";
-    disable_logging = addr->transport->disable_logging;
+    f.disable_logging = addr->transport->disable_logging;
     }
   else driver_kind = US"transporting";
   }
@@ -1485,7 +1507,7 @@ else if (driver_type == EXIM_DTYPE_ROUTER)
     {
     driver_name = addr->router->name;
     driver_kind = US" router";
-    disable_logging = addr->router->disable_logging;
+    f.disable_logging = addr->router->disable_logging;
     }
   else driver_kind = US"routing";
   }
@@ -1553,7 +1575,7 @@ if (addr->return_file >= 0 && addr->return_filename)
           log_write(0, LOG_MAIN, "<%s>: %s transport output: %s",
             addr->address, tb->name, sp);
           }
-        (void)fclose(f);
+      (void)fclose(f);
       }
 
     /* Handle returning options, but only if there is an address to return
@@ -1619,7 +1641,7 @@ if (result == OK)
   tls_out.cipher = addr->cipher;
   tls_out.peerdn = addr->peerdn;
   tls_out.ocsp = addr->ocsp;
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
   tls_out.dane_verified = testflag(addr, af_dane_verified);
 # endif
 #endif
@@ -1632,7 +1654,7 @@ if (result == OK)
   tls_out.cipher = NULL;
   tls_out.peerdn = NULL;
   tls_out.ocsp = OCSP_NOT_REQ;
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
   tls_out.dane_verified = FALSE;
 # endif
 #endif
@@ -1659,7 +1681,7 @@ else if (result == DEFER || result == PANIC)
 
   if (addr->special_action == SPECIAL_FREEZE)
     {
-    deliver_freeze = TRUE;
+    f.deliver_freeze = TRUE;
     deliver_frozen_at = time(NULL);
     update_spool = TRUE;
     }
@@ -1667,7 +1689,7 @@ else if (result == DEFER || result == PANIC)
   /* If doing a 2-stage queue run, we skip writing to either the message
   log or the main log for SMTP defers. */
 
-  if (!queue_2stage || addr->basic_errno != 0)
+  if (!f.queue_2stage || addr->basic_errno != 0)
     deferral_log(addr, now, logflags, driver_name, driver_kind);
   }
 
@@ -1700,10 +1722,10 @@ else
     {
     frozen_info = addr->special_action == SPECIAL_FREEZE
       ? US""
-      : sender_local && !local_error_message
+      : f.sender_local && !f.local_error_message
       ? US" (message created with -f <>)"
       : US" (delivery error message)";
-    deliver_freeze = TRUE;
+    f.deliver_freeze = TRUE;
     deliver_frozen_at = time(NULL);
     update_spool = TRUE;
 
@@ -1728,7 +1750,7 @@ else
 
 /* Ensure logging is turned on again in all cases */
 
-disable_logging = FALSE;
+f.disable_logging = FALSE;
 }
 
 
@@ -2154,7 +2176,7 @@ if (tp->return_path)
   uschar *new_return_path = expand_string(tp->return_path);
   if (!new_return_path)
     {
-    if (!expand_string_forcedfail)
+    if (!f.expand_string_forcedfail)
       {
       common_error(TRUE, addr, ERRNO_EXPANDFAIL,
         US"Failed to expand return path \"%s\" in %s transport: %s",
@@ -2553,7 +2575,7 @@ if (!shadowing)
       /* In the test harness, wait just a bit to let the subprocess finish off
       any debug output etc first. */
 
-      if (running_in_test_harness) millisleep(300);
+      if (f.running_in_test_harness) millisleep(300);
 
       DEBUG(D_deliver) debug_printf("journalling %s", big_buffer);
       len = Ustrlen(big_buffer);
@@ -2713,7 +2735,7 @@ while (addr_local)
   struct timeval deliver_time;
   address_item *addr2, *addr3, *nextaddr;
   int logflags = LOG_MAIN;
-  int logchar = dont_deliver? '*' : '=';
+  int logchar = f.dont_deliver? '*' : '=';
   transport_instance *tp;
   uschar * serialize_key = NULL;
 
@@ -2731,7 +2753,7 @@ while (addr_local)
   if (!(tp = addr->transport))
     {
     logflags |= LOG_PANIC;
-    disable_logging = FALSE;  /* Jic */
+    f.disable_logging = FALSE;  /* Jic */
     addr->message = addr->router
       ? string_sprintf("No transport set by %s router", addr->router->name)
       : string_sprintf("No transport set by system filter");
@@ -2749,7 +2771,7 @@ while (addr_local)
 
   /* There are weird cases where logging is disabled */
 
-  disable_logging = tp->disable_logging;
+  f.disable_logging = tp->disable_logging;
 
   /* Check for batched addresses and possible amalgamation. Skip all the work
   if either batch_max <= 1 or there aren't any other addresses for local
@@ -2936,7 +2958,7 @@ while (addr_local)
             retry_record->expired);
           }
 
-        if (queue_running && !deliver_force)
+        if (f.queue_running && !f.deliver_force)
           {
           ok = (now - retry_record->time_stamp > retry_data_expire)
            || (now >= retry_record->next_try)
@@ -3293,8 +3315,8 @@ small items (less than PIPE_BUF, which seems to be at least 512 in any Unix and
 often bigger) so even if we are reading while the subprocess is still going, we
 should never have only a partial item in the buffer.
 
-hs12: This assumption is not true anymore, since we got quit large items (certificate
-information and such)
+hs12: This assumption is not true anymore, since we get quite large items (certificate
+information and such).
 
 Argument:
   poffset     the offset of the parlist item
@@ -3551,6 +3573,13 @@ while (!done)
       break;
 #endif
 
+    case 'L':
+      switch (*subid)
+       {
+       case 1: setflag(addr, af_pipelining); break;
+       }
+      break;
+
     case 'K':
       setflag(addr, af_chunking_used);
       break;
@@ -4251,7 +4280,7 @@ for (delivery_count = 0; addr_remote; delivery_count++)
 
   if (!(tp = addr->transport))
     {
-    disable_logging = FALSE;  /* Jic */
+    f.disable_logging = FALSE;  /* Jic */
     panicmsg = US"No transport set by router";
     goto panic_continue;
     }
@@ -4446,7 +4475,7 @@ for (delivery_count = 0; addr_remote; delivery_count++)
     uschar *new_return_path = expand_string(tp->return_path);
     if (new_return_path)
       return_path = new_return_path;
-    else if (!expand_string_forcedfail)
+    else if (!f.expand_string_forcedfail)
       {
       panicmsg = string_sprintf("Failed to expand return path \"%s\": %s",
        tp->return_path, expand_string_message);
@@ -4478,7 +4507,7 @@ for (delivery_count = 0; addr_remote; delivery_count++)
   treat it as if it is a continued connection (apart from the counter used
   for the log line mark). */
 
-  if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
+  if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
     {
     DEBUG(D_deliver)
       debug_printf("lazy-callout-close: have conn still open from verification\n");
@@ -4497,7 +4526,7 @@ for (delivery_count = 0; addr_remote; delivery_count++)
   we must check that the continue host is on the list. Otherwise, the
   host is set in the transport. */
 
-  continue_more = FALSE;           /* In case got set for the last lot */
+  f.continue_more = FALSE;           /* In case got set for the last lot */
   if (continue_transport)
     {
     BOOL ok = Ustrcmp(continue_transport, tp->name) == 0;
@@ -4572,12 +4601,12 @@ for (delivery_count = 0; addr_remote; delivery_count++)
     connected to is too hard to manage.  Perhaps we need a finer-grain
     interface to the transport. */
 
-    for (next = addr_remote; next && !continue_more; next = next->next)
+    for (next = addr_remote; next && !f.continue_more; next = next->next)
       {
       host_item *h;
       for (h = next->host_list; h; h = h->next)
         if (Ustrcmp(h->name, continue_hostname) == 0)
-          { continue_more = TRUE; break; }
+          { f.continue_more = TRUE; break; }
       }
     }
 
@@ -4656,18 +4685,16 @@ all pipes, so I do not see a reason to use non-blocking IO here
 
   search_tidyup();
 
-
   if ((pid = fork()) == 0)
     {
     int fd = pfd[pipe_write];
     host_item *h;
-    DEBUG(D_deliver) debug_selector |= D_pid;  // hs12
 
     /* Setting this global in the subprocess means we need never clear it */
     transport_name = tp->name;
 
     /* There are weird circumstances in which logging is disabled */
-    disable_logging = tp->disable_logging;
+    f.disable_logging = tp->disable_logging;
 
     /* Show pids on debug output if parallelism possible */
 
@@ -4682,7 +4709,7 @@ all pipes, so I do not see a reason to use non-blocking IO here
     predictable settings for each delivery process, so do something explicit
     here rather they rely on the fixed reset in the random number function. */
 
-    random_seed = running_in_test_harness ? 42 + 2*delivery_count : 0;
+    random_seed = f.running_in_test_harness ? 42 + 2*delivery_count : 0;
 
     /* Set close-on-exec on the pipe so that it doesn't get passed on to
     a new process that may be forked to do another delivery down the same
@@ -4785,7 +4812,7 @@ all pipes, so I do not see a reason to use non-blocking IO here
 
       /* The certificate verification status goes into the flags */
       if (tls_out.certificate_verified) setflag(addr, af_cert_verified);
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
       if (tls_out.dane_verified)        setflag(addr, af_dane_verified);
 #endif
 
@@ -4856,6 +4883,9 @@ all pipes, so I do not see a reason to use non-blocking IO here
        rmt_dlv_checked_write(fd, 'P', '0', NULL, 0);
 #endif
 
+      if (testflag(addr, af_pipelining))
+         rmt_dlv_checked_write(fd, 'L', '1', NULL, 0);
+
       if (testflag(addr, af_chunking_used))
        rmt_dlv_checked_write(fd, 'K', '0', NULL, 0);
 
@@ -4985,12 +5015,13 @@ all pipes, so I do not see a reason to use non-blocking IO here
   release its TLS library context (if any) as responsibility was passed to
   the delivery child process. */
 
-  if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
+  if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
     {
 #ifdef SUPPORT_TLS
-    tls_close(FALSE, FALSE);
+    if (cutthrough.is_tls)
+      tls_close(cutthrough.cctx.tls_ctx, TLS_NO_SHUTDOWN);
 #endif
-    (void) close(cutthrough.fd);
+    (void) close(cutthrough.cctx.sock);
     release_cutthrough_connection(US"passed to transport proc");
     }
 
@@ -5032,7 +5063,7 @@ all pipes, so I do not see a reason to use non-blocking IO here
   newly created process get going before we create another process. This should
   ensure repeatability in the tests. We only need to wait a tad. */
 
-  else if (running_in_test_harness) millisleep(500);
+  else if (f.running_in_test_harness) millisleep(500);
 
   continue;
 
@@ -5555,8 +5586,9 @@ message size. This use of strcpy() is OK because the length id is checked when
 it is obtained from a command line (the -M or -q options), and otherwise it is
 known to be a valid message id. */
 
-Ustrcpy(message_id, id);
-deliver_force = forced;
+if (id != message_id)
+  Ustrcpy(message_id, id);
+f.deliver_force = forced;
 return_count = 0;
 message_size = 0;
 
@@ -5704,7 +5736,7 @@ Otherwise it might be needed again. */
 can happen, but in the default situation, unless forced, no delivery is
 attempted. */
 
-if (deliver_freeze)
+if (f.deliver_freeze)
   {
 #ifdef SUPPORT_MOVE_FROZEN_MESSAGES
   /* Moving to another directory removes the message from Exim's view. Other
@@ -5747,8 +5779,8 @@ if (deliver_freeze)
          || auto_thaw <= 0
          || now <= deliver_frozen_at + auto_thaw
           )
-       && (  !forced || !deliver_force_thaw
-         || !admin_user || continue_hostname
+       && (  !forced || !f.deliver_force_thaw
+         || !f.admin_user || continue_hostname
        )  )
       {
       (void)close(deliver_datafile);
@@ -5762,7 +5794,7 @@ if (deliver_freeze)
 
     if (forced)
       {
-      deliver_manual_thaw = TRUE;
+      f.deliver_manual_thaw = TRUE;
       log_write(0, LOG_MAIN, "Unfrozen by forced delivery");
       }
     else log_write(0, LOG_MAIN, "Unfrozen by auto-thaw");
@@ -5770,7 +5802,7 @@ if (deliver_freeze)
 
   /* We get here if any of the rules for unfreezing have triggered. */
 
-  deliver_freeze = FALSE;
+  f.deliver_freeze = FALSE;
   update_spool = TRUE;
   }
 
@@ -5845,8 +5877,8 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
     }
 
   return_path = sender_address;
-  enable_dollar_recipients = TRUE;   /* Permit $recipients in system filter */
-  system_filtering = TRUE;
+  f.enable_dollar_recipients = TRUE;   /* Permit $recipients in system filter */
+  f.system_filtering = TRUE;
 
   /* Any error in the filter file causes a delivery to be abandoned. */
 
@@ -5894,8 +5926,8 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
   /* Reset things. If the filter message is an empty string, which can happen
   for a filter "fail" or "freeze" command with no text, reset it to NULL. */
 
-  system_filtering = FALSE;
-  enable_dollar_recipients = FALSE;
+  f.system_filtering = FALSE;
+  f.enable_dollar_recipients = FALSE;
   if (filter_message && filter_message[0] == 0) filter_message = NULL;
 
   /* Save the values of the system filter variables so that user filters
@@ -5918,9 +5950,9 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
   unset "delivered", which is forced by the "freeze" command to make -bF
   work properly. */
 
-  else if (rc == FF_FREEZE && !deliver_manual_thaw)
+  else if (rc == FF_FREEZE && !f.deliver_manual_thaw)
     {
-    deliver_freeze = TRUE;
+    f.deliver_freeze = TRUE;
     deliver_frozen_at = time(NULL);
     process_recipients = RECIP_DEFER;
     frozen_info = string_sprintf(" by the system filter%s%s",
@@ -6157,19 +6189,19 @@ if (process_recipients != RECIP_IGNORE)
         /* RECIP_DEFER is set when a system filter freezes a message. */
 
         case RECIP_DEFER:
-        new->next = addr_defer;
-        addr_defer = new;
-        break;
+         new->next = addr_defer;
+         addr_defer = new;
+         break;
 
 
         /* RECIP_FAIL_FILTER is set when a system filter has obeyed a "fail"
         command. */
 
         case RECIP_FAIL_FILTER:
-        new->message =
-          filter_message ? filter_message : US"delivery cancelled";
-        setflag(new, af_pass_message);
-        goto RECIP_QUEUE_FAILED;   /* below */
+         new->message =
+           filter_message ? filter_message : US"delivery cancelled";
+         setflag(new, af_pass_message);
+         goto RECIP_QUEUE_FAILED;   /* below */
 
 
         /* RECIP_FAIL_TIMEOUT is set when a message is frozen, but is older
@@ -6179,15 +6211,15 @@ if (process_recipients != RECIP_IGNORE)
         been logged. */
 
         case RECIP_FAIL_TIMEOUT:
-        new->message  = US"delivery cancelled; message timed out";
-        goto RECIP_QUEUE_FAILED;   /* below */
+         new->message  = US"delivery cancelled; message timed out";
+         goto RECIP_QUEUE_FAILED;   /* below */
 
 
         /* RECIP_FAIL is set when -Mg has been used. */
 
         case RECIP_FAIL:
-        new->message  = US"delivery cancelled by administrator";
-        /* Fall through */
+         new->message  = US"delivery cancelled by administrator";
+         /* Fall through */
 
         /* Common code for the failure cases above. If this is not a bounce
         message, put the address on the failed list so that it is used to
@@ -6195,11 +6227,11 @@ if (process_recipients != RECIP_IGNORE)
         The incident has already been logged. */
 
         RECIP_QUEUE_FAILED:
-        if (sender_address[0] != 0)
-          {
-          new->next = addr_failed;
-          addr_failed = new;
-          }
+         if (sender_address[0])
+           {
+           new->next = addr_failed;
+           addr_failed = new;
+           }
         break;
 
 
@@ -6208,17 +6240,17 @@ if (process_recipients != RECIP_IGNORE)
         is a bounce message, it will get frozen. */
 
         case RECIP_FAIL_LOOP:
-        new->message = US"Too many \"Received\" headers - suspected mail loop";
-        post_process_one(new, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
-        break;
+         new->message = US"Too many \"Received\" headers - suspected mail loop";
+         post_process_one(new, FAIL, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+         break;
 
 
         /* Value should be RECIP_ACCEPT; take this as the safe default. */
 
         default:
-        if (!addr_new) addr_new = new; else addr_last->next = new;
-        addr_last = new;
-        break;
+         if (!addr_new) addr_new = new; else addr_last->next = new;
+         addr_last = new;
+         break;
         }
 
 #ifndef DISABLE_EVENT
@@ -6226,17 +6258,23 @@ if (process_recipients != RECIP_IGNORE)
        {
        uschar * save_local =  deliver_localpart;
        const uschar * save_domain = deliver_domain;
+       uschar * addr = new->address, * errmsg = NULL;
+       int start, end, dom;
 
-       deliver_localpart = expand_string(
-                     string_sprintf("${local_part:%s}", new->address));
-       deliver_domain =    expand_string(
-                     string_sprintf("${domain:%s}", new->address));
+       if (!parse_extract_address(addr, &errmsg, &start, &end, &dom, TRUE))
+         log_write(0, LOG_MAIN|LOG_PANIC,
+                "failed to parse address '%.100s': %s\n", addr, errmsg);
+       else
+         {
+         deliver_localpart =
+           string_copyn(addr+start, dom ? (dom-1) - start : end - start);
+         deliver_domain = dom ? CUS string_copyn(addr+dom, end - dom) : CUS"";
 
-       (void) event_raise(event_action,
-                     US"msg:fail:internal", new->message);
+         event_raise(event_action, US"msg:fail:internal", new->message);
 
-       deliver_localpart = save_local;
-       deliver_domain =    save_domain;
+         deliver_localpart = save_local;
+         deliver_domain = save_domain;
+         }
        }
 #endif
       }
@@ -6295,7 +6333,7 @@ deliver_out_buffer = store_malloc(DELIVER_OUT_BUFFER_SIZE);
  . If new addresses have been generated by the routers, da capo.
 */
 
-header_rewritten = FALSE;          /* No headers rewritten yet */
+f.header_rewritten = FALSE;          /* No headers rewritten yet */
 while (addr_new)           /* Loop until all addresses dealt with */
   {
   address_item *addr, *parent;
@@ -6641,7 +6679,7 @@ while (addr_new)           /* Loop until all addresses dealt with */
     which keep the retry record fresh, which can lead to us perpetually
     deferring messages. */
 
-    else if (  (  queue_running && !deliver_force
+    else if (  (  f.queue_running && !f.deliver_force
               || continue_hostname
               )
             && (  (  domain_retry_record
@@ -6685,7 +6723,7 @@ while (addr_new)           /* Loop until all addresses dealt with */
   those domains. During queue runs, queue_domains is forced to be unset.
   Optimize by skipping this pass through the addresses if nothing is set. */
 
-  if (!deliver_force && queue_domains)
+  if (!f.deliver_force && queue_domains)
     {
     address_item *okaddr = NULL;
     while (addr_route)
@@ -6979,14 +7017,14 @@ remember them for all subsequent deliveries. This can be delayed till later if
 there is only address to be delivered - if it succeeds the spool write need not
 happen. */
 
-if (  header_rewritten
+if (  f.header_rewritten
    && (  addr_local && (addr_local->next || addr_remote)
       || addr_remote && addr_remote->next
    )  )
   {
   /* Panic-dies on error */
   (void)spool_write_header(message_id, SW_DELIVERING, NULL);
-  header_rewritten = FALSE;
+  f.header_rewritten = FALSE;
   }
 
 
@@ -7066,13 +7104,13 @@ if (addr_local)
   DEBUG(D_deliver|D_transport)
     debug_printf(">>>>>>>>>>>>>>>> Local deliveries >>>>>>>>>>>>>>>>\n");
   do_local_deliveries();
-  disable_logging = FALSE;
+  f.disable_logging = FALSE;
   }
 
 /* If queue_run_local is set, we do not want to attempt any remote deliveries,
 so just queue them all. */
 
-if (queue_run_local)
+if (f.queue_run_local)
   while (addr_remote)
     {
     address_item *addr = addr_remote;
@@ -7124,7 +7162,7 @@ if (addr_remote)
     if (remote_sort_domains) sort_remote_deliveries();
     do_remote_deliveries(TRUE);
     }
-  disable_logging = FALSE;
+  f.disable_logging = FALSE;
   }
 
 
@@ -7203,7 +7241,7 @@ retry cutoff time has expired for all alternative destinations. Bypass the
 updating of the database if the -N flag is set, which is a debugging thing that
 prevents actual delivery. */
 
-else if (!dont_deliver)
+else if (!f.dont_deliver)
   retry_update(&addr_defer, &addr_failed, &addr_succeed);
 
 /* Send DSN for successful messages if requested */
@@ -7234,7 +7272,6 @@ for (addr_dsntmp = addr_succeed; addr_dsntmp; addr_dsntmp = addr_dsntmp->next)
   if (  (  addr_dsntmp->dsn_aware != dsn_support_yes
        || addr_dsntmp->dsn_flags & rf_dsnlasthop
         )
-     && addr_dsntmp->dsn_flags & rf_dsnflags
      && addr_dsntmp->dsn_flags & rf_notify_success
      )
     {
@@ -7301,11 +7338,9 @@ if (addr_senddsn)
         addr_dsntmp = addr_dsntmp->next)
       fprintf(f, "<%s> (relayed %s)\n\n",
        addr_dsntmp->address,
-       (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1
-         ? "via non DSN router"
-         : addr_dsntmp->dsn_aware == dsn_support_no
-         ? "to non-DSN-aware mailer"
-         : "via non \"Remote SMTP\" router"
+       addr_dsntmp->dsn_flags & rf_dsnlasthop ? "via non DSN router"
+       : addr_dsntmp->dsn_aware == dsn_support_no ? "to non-DSN-aware mailer"
+       : "via non \"Remote SMTP\" router"
        );
 
     fprintf(f, "--%s\n"
@@ -7340,7 +7375,7 @@ if (addr_senddsn)
          addr_dsntmp->host_used->name);
       else
        fprintf(f, "Diagnostic-Code: X-Exim; relayed via non %s router\n\n",
-         (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1 ? "DSN" : "SMTP");
+         addr_dsntmp->dsn_flags & rf_dsnlasthop ? "DSN" : "SMTP");
       }
 
     fprintf(f, "--%s\nContent-type: text/rfc822-headers\n\n", bound);
@@ -7383,9 +7418,9 @@ while (addr_failed)
   /* There are weird cases when logging is disabled in the transport. However,
   there may not be a transport (address failed by a router). */
 
-  disable_logging = FALSE;
+  f.disable_logging = FALSE;
   if (addr_failed->transport)
-    disable_logging = addr_failed->transport->disable_logging;
+    f.disable_logging = addr_failed->transport->disable_logging;
 
   DEBUG(D_deliver)
     debug_printf("processing failed address %s\n", addr_failed->address);
@@ -7421,14 +7456,16 @@ while (addr_failed)
   mark the recipient done. */
 
   if (  addr_failed->prop.ignore_error
-     || (  addr_failed->dsn_flags & rf_dsnflags
-        && (addr_failed->dsn_flags & rf_notify_failure) != rf_notify_failure
-     )  )
+     || addr_failed->dsn_flags & (rf_dsnflags & ~rf_notify_failure)
+     )
     {
     addr = addr_failed;
     addr_failed = addr->next;
     if (addr->return_filename) Uunlink(addr->return_filename);
 
+#ifndef DISABLE_EVENT
+    msg_event_raise(US"msg:fail:delivery", addr);
+#endif
     log_write(0, LOG_MAIN, "%s%s%s%s: error ignored",
       addr->address,
       !addr->parent ? US"" : US" <",
@@ -7467,8 +7504,8 @@ while (addr_failed)
       int filecount = 0;
       int rcount = 0;
       uschar *bcc, *emf_text;
-      FILE *f = fdopen(fd, "wb");
-      FILE *emf = NULL;
+      FILE * fp = fdopen(fd, "wb");
+      FILE * emf = NULL;
       BOOL to_sender = strcmpic(sender_address, bounce_recipient) == 0;
       int max = (bounce_return_size_limit/DELIVER_IN_BUFFER_SIZE + 1) *
         DELIVER_IN_BUFFER_SIZE;
@@ -7506,10 +7543,10 @@ while (addr_failed)
         if (testflag(addr, af_hide_child)) continue;
         if (rcount >= 50)
           {
-          fprintf(f, "\n");
+          fprintf(fp, "\n");
           rcount = 0;
           }
-        fprintf(f, "%s%s",
+        fprintf(fp, "%s%s",
           rcount++ == 0
          ? "X-Failed-Recipients: "
          : ",\n  ",
@@ -7517,20 +7554,20 @@ while (addr_failed)
          ? string_printing(addr->parent->address)
          : string_printing(addr->address));
         }
-      if (rcount > 0) fprintf(f, "\n");
+      if (rcount > 0) fprintf(fp, "\n");
 
       /* Output the standard headers */
 
       if (errors_reply_to)
-        fprintf(f, "Reply-To: %s\n", errors_reply_to);
-      fprintf(f, "Auto-Submitted: auto-replied\n");
-      moan_write_from(f);
-      fprintf(f, "To: %s\n", bounce_recipient);
+        fprintf(fp, "Reply-To: %s\n", errors_reply_to);
+      fprintf(fp, "Auto-Submitted: auto-replied\n");
+      moan_write_from(fp);
+      fprintf(fp, "To: %s\n", bounce_recipient);
 
       /* generate boundary string and output MIME-Headers */
       bound = string_sprintf(TIME_T_FMT "-eximdsn-%d", time(NULL), rand());
 
-      fprintf(f, "Content-Type: multipart/report;"
+      fprintf(fp, "Content-Type: multipart/report;"
            " report-type=delivery-status; boundary=%s\n"
          "MIME-Version: 1.0\n",
        bound);
@@ -7546,46 +7583,46 @@ while (addr_failed)
       /* Quietly copy to configured additional addresses if required. */
 
       if ((bcc = moan_check_errorcopy(bounce_recipient)))
-       fprintf(f, "Bcc: %s\n", bcc);
+       fprintf(fp, "Bcc: %s\n", bcc);
 
       /* The texts for the message can be read from a template file; if there
       isn't one, or if it is too short, built-in texts are used. The first
       emf text is a Subject: and any other headers. */
 
       if ((emf_text = next_emf(emf, US"header")))
-       fprintf(f, "%s\n", emf_text);
+       fprintf(fp, "%s\n", emf_text);
       else
-        fprintf(f, "Subject: Mail delivery failed%s\n\n",
+        fprintf(fp, "Subject: Mail delivery failed%s\n\n",
           to_sender? ": returning message to sender" : "");
 
       /* output human readable part as text/plain section */
-      fprintf(f, "--%s\n"
+      fprintf(fp, "--%s\n"
          "Content-type: text/plain; charset=us-ascii\n\n",
        bound);
 
       if ((emf_text = next_emf(emf, US"intro")))
-       fprintf(f, "%s", CS emf_text);
+       fprintf(fp, "%s", CS emf_text);
       else
         {
-        fprintf(f,
+        fprintf(fp,
 /* This message has been reworded several times. It seems to be confusing to
 somebody, however it is worded. I have retreated to the original, simple
 wording. */
 "This message was created automatically by mail delivery software.\n");
 
         if (bounce_message_text)
-         fprintf(f, "%s", CS bounce_message_text);
+         fprintf(fp, "%s", CS bounce_message_text);
         if (to_sender)
-          fprintf(f,
+          fprintf(fp,
 "\nA message that you sent could not be delivered to one or more of its\n"
 "recipients. This is a permanent error. The following address(es) failed:\n");
         else
-          fprintf(f,
+          fprintf(fp,
 "\nA message sent by\n\n  <%s>\n\n"
 "could not be delivered to one or more of its recipients. The following\n"
 "address(es) failed:\n", sender_address);
         }
-      fputc('\n', f);
+      fputc('\n', fp);
 
       /* Process the addresses, leaving them on the msgchain if they have a
       file name for a return message. (There has already been a check in
@@ -7596,12 +7633,12 @@ wording. */
       paddr = &msgchain;
       for (addr = msgchain; addr; addr = *paddr)
         {
-        if (print_address_information(addr, f, US"  ", US"\n    ", US""))
-          print_address_error(addr, f, US"");
+        if (print_address_information(addr, fp, US"  ", US"\n    ", US""))
+          print_address_error(addr, fp, US"");
 
         /* End the final line for the address */
 
-        fputc('\n', f);
+        fputc('\n', fp);
 
         /* Leave on msgchain if there's a return file. */
 
@@ -7622,7 +7659,7 @@ wording. */
           }
         }
 
-      fputc('\n', f);
+      fputc('\n', fp);
 
       /* Get the next text, whether we need it or not, so as to be
       positioned for the one after. */
@@ -7641,9 +7678,9 @@ wording. */
         address_item *nextaddr;
 
         if (emf_text)
-         fprintf(f, "%s", CS emf_text);
+         fprintf(fp, "%s", CS emf_text);
        else
-          fprintf(f,
+          fprintf(fp,
             "The following text was generated during the delivery "
             "attempt%s:\n", (filecount > 1)? "s" : "");
 
@@ -7654,24 +7691,24 @@ wording. */
 
           /* List all the addresses that relate to this file */
 
-         fputc('\n', f);
+         fputc('\n', fp);
           while(addr)                   /* Insurance */
             {
-            print_address_information(addr, f, US"------ ",  US"\n       ",
+            print_address_information(addr, fp, US"------ ",  US"\n       ",
               US" ------\n");
             if (addr->return_filename) break;
             addr = addr->next;
             }
-         fputc('\n', f);
+         fputc('\n', fp);
 
           /* Now copy the file */
 
           if (!(fm = Ufopen(addr->return_filename, "rb")))
-            fprintf(f, "    +++ Exim error... failed to open text file: %s\n",
+            fprintf(fp, "    +++ Exim error... failed to open text file: %s\n",
               strerror(errno));
           else
             {
-            while ((ch = fgetc(fm)) != EOF) fputc(ch, f);
+            while ((ch = fgetc(fm)) != EOF) fputc(ch, fp);
             (void)fclose(fm);
             }
           Uunlink(addr->return_filename);
@@ -7683,19 +7720,19 @@ wording. */
           addr->next = handled_addr;
           handled_addr = topaddr;
           }
-       fputc('\n', f);
+       fputc('\n', fp);
         }
 
       /* output machine readable part */
 #ifdef SUPPORT_I18N
       if (message_smtputf8)
-       fprintf(f, "--%s\n"
+       fprintf(fp, "--%s\n"
            "Content-type: message/global-delivery-status\n\n"
            "Reporting-MTA: dns; %s\n",
          bound, smtp_active_hostname);
       else
 #endif
-       fprintf(f, "--%s\n"
+       fprintf(fp, "--%s\n"
            "Content-type: message/delivery-status\n\n"
            "Reporting-MTA: dns; %s\n",
          bound, smtp_active_hostname);
@@ -7705,40 +7742,42 @@ wording. */
         /* must be decoded from xtext: see RFC 3461:6.3a */
         uschar *xdec_envid;
         if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0)
-          fprintf(f, "Original-Envelope-ID: %s\n", dsn_envid);
+          fprintf(fp, "Original-Envelope-ID: %s\n", dsn_envid);
         else
-          fprintf(f, "X-Original-Envelope-ID: error decoding xtext formatted ENVID\n");
+          fprintf(fp, "X-Original-Envelope-ID: error decoding xtext formatted ENVID\n");
         }
-      fputc('\n', f);
+      fputc('\n', fp);
 
       for (addr = handled_addr; addr; addr = addr->next)
         {
        host_item * hu;
-        fprintf(f, "Action: failed\n"
+        fprintf(fp, "Action: failed\n"
            "Final-Recipient: rfc822;%s\n"
            "Status: 5.0.0\n",
            addr->address);
         if ((hu = addr->host_used) && hu->name)
          {
-         const uschar * s;
-         fprintf(f, "Remote-MTA: dns; %s\n", hu->name);
+         fprintf(fp, "Remote-MTA: dns; %s\n", hu->name);
 #ifdef EXPERIMENTAL_DSN_INFO
+         {
+         const uschar * s;
          if (hu->address)
            {
            uschar * p = hu->port == 25
              ? US"" : string_sprintf(":%d", hu->port);
-           fprintf(f, "Remote-MTA: X-ip; [%s]%s\n", hu->address, p);
+           fprintf(fp, "Remote-MTA: X-ip; [%s]%s\n", hu->address, p);
            }
          if ((s = addr->smtp_greeting) && *s)
-           fprintf(f, "X-Remote-MTA-smtp-greeting: X-str; %s\n", s);
+           fprintf(fp, "X-Remote-MTA-smtp-greeting: X-str; %s\n", s);
          if ((s = addr->helo_response) && *s)
-           fprintf(f, "X-Remote-MTA-helo-response: X-str; %s\n", s);
+           fprintf(fp, "X-Remote-MTA-helo-response: X-str; %s\n", s);
          if ((s = addr->message) && *s)
-           fprintf(f, "X-Exim-Diagnostic: X-str; %s\n", s);
+           fprintf(fp, "X-Exim-Diagnostic: X-str; %s\n", s);
+         }
 #endif
-         print_dsn_diagnostic_code(addr, f);
+         print_dsn_diagnostic_code(addr, fp);
          }
-       fputc('\n', f);
+       fputc('\n', fp);
         }
 
       /* Now copy the message, trying to give an intelligible comment if
@@ -7759,7 +7798,7 @@ wording. */
          bounce_return_size_limit is always honored.
       */
 
-      fprintf(f, "--%s\n", bound);
+      fprintf(fp, "--%s\n", bound);
 
       dsnlimitmsg = US"X-Exim-DSN-Information: Due to administrative limits only headers are returned";
       dsnnotifyhdr = NULL;
@@ -7797,44 +7836,44 @@ wording. */
       if (message_smtputf8)
        fputs(topt & topt_no_body ? "Content-type: message/global-headers\n\n"
                                  : "Content-type: message/global\n\n",
-             f);
+             fp);
       else
 #endif
        fputs(topt & topt_no_body ? "Content-type: text/rfc822-headers\n\n"
                                  : "Content-type: message/rfc822\n\n",
-             f);
+             fp);
 
-      fflush(f);
+      fflush(fp);
       transport_filter_argv = NULL;   /* Just in case */
       return_path = sender_address;   /* In case not previously set */
        {                             /* Dummy transport for headers add */
        transport_ctx tctx = {{0}};
        transport_instance tb = {0};
 
-       tctx.u.fd = fileno(f);
+       tctx.u.fd = fileno(fp);
        tctx.tblock = &tb;
        tctx.options = topt;
        tb.add_headers = dsnnotifyhdr;
 
        transport_write_message(&tctx, 0);
        }
-      fflush(f);
+      fflush(fp);
 
       /* we never add the final text. close the file */
       if (emf)
         (void)fclose(emf);
 
-      fprintf(f, "\n--%s--\n", bound);
+      fprintf(fp, "\n--%s--\n", bound);
 
       /* Close the file, which should send an EOF to the child process
       that is receiving the message. Wait for it to finish. */
 
-      (void)fclose(f);
+      (void)fclose(fp);
       rc = child_close(pid, 0);     /* Waits for child to close, no timeout */
 
       /* In the test harness, let the child do it's thing first. */
 
-      if (running_in_test_harness) millisleep(500);
+      if (f.running_in_test_harness) millisleep(500);
 
       /* If the process failed, there was some disaster in setting up the
       error message. Unless the message is very old, ensure that addr_defer
@@ -7851,7 +7890,7 @@ wording. */
         if (now - received_time.tv_sec < retry_maximum_timeout && !addr_defer)
           {
           addr_defer = (address_item *)(+1);
-          deliver_freeze = TRUE;
+          f.deliver_freeze = TRUE;
           deliver_frozen_at = time(NULL);
           /* Panic-dies on error */
           (void)spool_write_header(message_id, SW_DELIVERING, NULL);
@@ -7880,7 +7919,7 @@ wording. */
     }
   }
 
-disable_logging = FALSE;  /* In case left set */
+f.disable_logging = FALSE;  /* In case left set */
 
 /* Come here from the mua_wrapper case if routing goes wrong */
 
@@ -7938,7 +7977,7 @@ if (!addr_defer)
     log_write(0, LOG_MAIN, "Completed");
 
   /* Unset deliver_freeze so that we won't try to move the spool files further down */
-  deliver_freeze = FALSE;
+  f.deliver_freeze = FALSE;
 
 #ifndef DISABLE_EVENT
   (void) event_raise(event_action, US"msg:complete", NULL);
@@ -8056,10 +8095,10 @@ else if (addr_defer != (address_item *)(+1))
   is not sent. Another attempt will be made at the next delivery attempt (if
   it also defers). */
 
-  if (  !queue_2stage
+  if (  !f.queue_2stage
      && delivery_attempted
-     && (  ((addr_defer->dsn_flags & rf_dsnflags) == 0)
-        || (addr_defer->dsn_flags & rf_notify_delay) == rf_notify_delay
+     && (  !(addr_defer->dsn_flags & rf_dsnflags)
+        || addr_defer->dsn_flags & rf_notify_delay
        )
      && delay_warning[1] > 0
      && sender_address[0] != 0
@@ -8078,7 +8117,7 @@ else if (addr_defer != (address_item *)(+1))
     time off the list. In queue runs, the list pointer gets updated in the
     calling process. */
 
-    if (running_in_test_harness && fudged_queue_times[0] != 0)
+    if (f.running_in_test_harness && fudged_queue_times[0] != 0)
       {
       int qt = readconf_readtime(fudged_queue_times, '/', FALSE);
       if (qt >= 0)
@@ -8309,9 +8348,9 @@ else if (addr_defer != (address_item *)(+1))
   /* If this was a first delivery attempt, unset the first time flag, and
   ensure that the spool gets updated. */
 
-  if (deliver_firsttime)
+  if (f.deliver_firsttime)
     {
-    deliver_firsttime = FALSE;
+    f.deliver_firsttime = FALSE;
     update_spool = TRUE;
     }
 
@@ -8322,9 +8361,9 @@ else if (addr_defer != (address_item *)(+1))
   For the "tell" message, we turn \n back into newline. Also, insert a newline
   near the start instead of the ": " string. */
 
-  if (deliver_freeze)
+  if (f.deliver_freeze)
     {
-    if (freeze_tell && freeze_tell[0] != 0 && !local_error_message)
+    if (freeze_tell && freeze_tell[0] != 0 && !f.local_error_message)
       {
       uschar *s = string_copy(frozen_info);
       uschar *ss = Ustrstr(s, " by the system filter: ");
@@ -8365,9 +8404,9 @@ else if (addr_defer != (address_item *)(+1))
 
   DEBUG(D_deliver)
     debug_printf("delivery deferred: update_spool=%d header_rewritten=%d\n",
-      update_spool, header_rewritten);
+      update_spool, f.header_rewritten);
 
-  if (update_spool || header_rewritten)
+  if (update_spool || f.header_rewritten)
     /* Panic-dies on error */
     (void)spool_write_header(message_id, SW_DELIVERING, NULL);
   }
@@ -8402,7 +8441,7 @@ if (remove_journal)
   /* Move the message off the spool if requested */
 
 #ifdef SUPPORT_MOVE_FROZEN_MESSAGES
-  if (deliver_freeze && move_frozen_messages)
+  if (f.deliver_freeze && move_frozen_messages)
     (void)spool_move_message(id, message_subdir, US"", US"F");
 #endif
   }
@@ -8434,7 +8473,7 @@ deliver_init(void)
 #ifdef EXIM_TFO_PROBE
 tfo_probe();
 #else
-tcp_fastopen_ok = TRUE;
+f.tcp_fastopen_ok = TRUE;
 #endif
 
 
@@ -8451,6 +8490,11 @@ if (!regex_AUTH) regex_AUTH =
 #ifdef SUPPORT_TLS
 if (!regex_STARTTLS) regex_STARTTLS =
   regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
+
+# ifdef EXPERIMENTAL_REQUIRETLS
+if (!regex_REQUIRETLS) regex_REQUIRETLS =
+  regex_must_compile(US"\\n250[\\s\\-]REQUIRETLS(\\s|\\n|$)", FALSE, TRUE);
+# endif
 #endif
 
 if (!regex_CHUNKING) regex_CHUNKING =
@@ -8480,17 +8524,17 @@ deliver_get_sender_address (uschar * id)
 int rc;
 uschar * new_sender_address,
        * save_sender_address;
-BOOL save_qr = queue_running;
+BOOL save_qr = f.queue_running;
 uschar * spoolname;
 
 /* make spool_open_datafile non-noisy on fail */
 
-queue_running = TRUE;
+f.queue_running = TRUE;
 
 /* Side effect: message_subdir is set for the (possibly split) spool directory */
 
 deliver_datafile = spool_open_datafile(id);
-queue_running = save_qr;
+f.queue_running = save_qr;
 if (deliver_datafile < 0)
   return NULL;
 
@@ -8526,9 +8570,9 @@ delivery_re_exec(int exec_type)
 {
 uschar * where;
 
-if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
+if (cutthrough.cctx.sock >= 0 && cutthrough.callout_hold_only)
   {
-  int pfd[2], channel_fd = cutthrough.fd, pid;
+  int channel_fd = cutthrough.cctx.sock;
 
   smtp_peer_options = cutthrough.peer_options;
   continue_sequence = 0;
@@ -8536,6 +8580,8 @@ if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
 #ifdef SUPPORT_TLS
   if (cutthrough.is_tls)
     {
+    int pfd[2], pid;
+
     smtp_peer_options |= OPTION_TLS;
     sending_ip_address = cutthrough.snd_ip;
     sending_port = cutthrough.snd_port;
@@ -8550,9 +8596,10 @@ if (cutthrough.fd >= 0 && cutthrough.callout_hold_only)
 
     else if (pid == 0)         /* child: fork again to totally disconnect */
       {
-      if (running_in_test_harness) millisleep(100); /* let parent debug out */
+      if (f.running_in_test_harness) millisleep(100); /* let parent debug out */
       /* does not return */
-      smtp_proxy_tls(big_buffer, big_buffer_size, pfd, 5*60);
+      smtp_proxy_tls(cutthrough.cctx.tls_ctx, big_buffer, big_buffer_size,
+                     pfd, 5*60);
       }
 
     DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid);
@@ -8573,6 +8620,7 @@ else
   }
 return;                /* compiler quietening; control does not reach here. */
 
+#ifdef SUPPORT_TLS
 fail:
   log_write(0,
     LOG_MAIN | (exec_type == CEE_EXEC_EXIT ? LOG_PANIC : LOG_PANIC_DIE),
@@ -8582,6 +8630,7 @@ fail:
   Note: this must be _exit(), not exit(). */
 
   _exit(EX_EXECFAILED);
+#endif
 }
 
 /* vi: aw ai sw=2