Use single-bit fields for file-global flags in smtp_in
[exim.git] / src / src / smtp_in.c
index a229721cfb7891668635a3bc874d8d322b8b7bce..55272a99a0ebbbbe153262cf6a391ddb841dbc43 100644 (file)
@@ -131,26 +131,35 @@ to the circular buffer that holds a list of the last n received. */
 *                Local static variables          *
 *************************************************/
 
-static auth_instance *authenticated_by;
-static BOOL auth_advertised;
+static struct {
+  BOOL auth_advertised                 :1;
 #ifdef SUPPORT_TLS
-static BOOL tls_advertised;
+  BOOL tls_advertised                  :1;
+# ifdef EXPERIMENTAL_REQUIRETLS
+  BOOL requiretls_advertised           :1;
+# endif
 #endif
-static BOOL dsn_advertised;
-static BOOL esmtp;
-static BOOL helo_required = FALSE;
-static BOOL helo_verify = FALSE;
-static BOOL helo_seen;
-static BOOL helo_accept_junk;
-static BOOL count_nonmail;
-static BOOL pipelining_advertised;
-static BOOL rcpt_smtp_response_same;
-static BOOL rcpt_in_progress;
-static int  nonmail_command_count;
-static BOOL smtp_exit_function_called = 0;
+  BOOL dsn_advertised                  :1;
+  BOOL esmtp                           :1;
+  BOOL helo_required                   :1;
+  BOOL helo_verify                     :1;
+  BOOL helo_seen                       :1;
+  BOOL helo_accept_junk                        :1;
+  BOOL rcpt_smtp_response_same         :1;
+  BOOL rcpt_in_progress                        :1;
+  BOOL smtp_exit_function_called       :1;
 #ifdef SUPPORT_I18N
-static BOOL smtputf8_advertised;
+  BOOL smtputf8_advertised             :1;
 #endif
+} fl = {
+  .helo_required = FALSE,
+  .helo_verify = FALSE,
+  .smtp_exit_function_called = FALSE,
+};
+
+static auth_instance *authenticated_by;
+static int  count_nonmail;
+static int  nonmail_command_count;
 static int  synprot_error_count;
 static int  unknown_command_count;
 static int  sync_cmd_limit;
@@ -185,10 +194,10 @@ static smtp_cmd_list cmd_list[] = {
   { "helo",       sizeof("helo")-1,       HELO_CMD, TRUE,  FALSE },
   { "ehlo",       sizeof("ehlo")-1,       EHLO_CMD, TRUE,  FALSE },
   { "auth",       sizeof("auth")-1,       AUTH_CMD, TRUE,  TRUE  },
-  #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
   { "starttls",   sizeof("starttls")-1,   STARTTLS_CMD, FALSE, FALSE },
   { "tls_auth",   0,                      TLS_AUTH_CMD, FALSE, FALSE },
-  #endif
+#endif
 
 /* If you change anything above here, also fix the definitions below. */
 
@@ -255,6 +264,9 @@ enum {
   ENV_MAIL_OPT_RET, ENV_MAIL_OPT_ENVID,
 #ifdef SUPPORT_I18N
   ENV_MAIL_OPT_UTF8,
+#endif
+#ifdef EXPERIMENTAL_REQUIRETLS
+  ENV_MAIL_OPT_REQTLS,
 #endif
   };
 typedef struct {
@@ -274,6 +286,10 @@ static env_mail_type_t env_mail_type_list[] = {
     { US"ENVID",  ENV_MAIL_OPT_ENVID,  TRUE },
 #ifdef SUPPORT_I18N
     { US"SMTPUTF8",ENV_MAIL_OPT_UTF8,  FALSE },                /* rfc6531 */
+#endif
+#ifdef EXPERIMENTAL_REQUIRETLS
+    /* https://tools.ietf.org/html/draft-ietf-uta-smtp-require-tls-03 */
+    { US"REQUIRETLS",ENV_MAIL_OPT_REQTLS,  FALSE },
 #endif
     /* keep this the last entry */
     { US"NULL",   ENV_MAIL_OPT_NULL,   FALSE },
@@ -340,7 +356,7 @@ fd_set fds;
 struct timeval tzero;
 
 #ifdef SUPPORT_TLS
-if (tls_in.active >= 0)
+if (tls_in.active.sock >= 0)
  return !tls_could_read();
 #endif
 
@@ -359,16 +375,13 @@ rc = smtp_getc(GETC_BUFFER_UNLIMITED);
 if (rc < 0) return TRUE;      /* End of file or error */
 
 smtp_ungetc(rc);
-rc = smtp_inend - smtp_inptr;
-if (rc > 150) rc = 150;
-smtp_inptr[rc] = 0;
 return FALSE;
 }
 
 static BOOL
 check_sync(void)
 {
-if (!smtp_enforce_sync || sender_host_address == NULL || sender_host_notsocket)
+if (!smtp_enforce_sync || !sender_host_address || f.sender_host_notsocket)
   return TRUE;
 
 return wouldblock_reading();
@@ -382,10 +395,12 @@ static BOOL
 pipeline_response(void)
 {
 if (  !smtp_enforce_sync || !sender_host_address
-   || sender_host_notsocket || !pipelining_advertised)
+   || f.sender_host_notsocket || !f.smtp_in_pipelining_advertised)
   return FALSE;
 
-return !wouldblock_reading();
+if (wouldblock_reading()) return FALSE;
+f.smtp_in_pipelining_used = TRUE;
+return TRUE;
 }
 
 
@@ -427,6 +442,53 @@ log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS,
 
 
 
+void
+smtp_command_timeout_exit(void)
+{
+log_write(L_lost_incoming_connection,
+         LOG_MAIN, "SMTP command timeout on%s connection from %s",
+         tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE));
+if (smtp_batched_input)
+  moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */
+smtp_notquit_exit(US"command-timeout", US"421",
+  US"%s: SMTP command timeout - closing connection",
+  smtp_active_hostname);
+exim_exit(EXIT_FAILURE, US"receiving");
+}
+
+void
+smtp_command_sigterm_exit(void)
+{
+log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info());
+if (smtp_batched_input)
+  moan_smtp_batch(NULL, "421 SIGTERM received");  /* Does not return */
+smtp_notquit_exit(US"signal-exit", US"421",
+  US"%s: Service not available - closing connection", smtp_active_hostname);
+exim_exit(EXIT_FAILURE, US"receiving");
+}
+
+void
+smtp_data_timeout_exit(void)
+{
+log_write(L_lost_incoming_connection,
+  LOG_MAIN, "SMTP data timeout (message abandoned) on connection from %s F=<%s>",
+  sender_fullhost ? sender_fullhost : US"local process", sender_address);
+receive_bomb_out(US"data-timeout", US"SMTP incoming data timeout");
+/* Does not return */
+}
+
+void
+smtp_data_sigint_exit(void)
+{
+log_write(0, LOG_MAIN, "%s closed after %s",
+  smtp_get_connection_info(), had_data_sigint == SIGTERM ? "SIGTERM":"SIGINT");
+receive_bomb_out(US"signal-exit",
+  US"Service not available - SIGTERM or SIGINT received");
+/* Does not return */
+}
+
+
+
 /* Refill the buffer, and notify DKIM verification code.
 Return false for error or EOF.
 */
@@ -439,22 +501,33 @@ if (!smtp_out) return FALSE;
 fflush(smtp_out);
 if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
 
-/* Limit amount read, so non-message data is not fed to DKIM */
+/* Limit amount read, so non-message data is not fed to DKIM.
+Take care to not touch the safety NUL at the end of the buffer. */
 
-rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE, lim));
+rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE-1, lim));
 save_errno = errno;
-alarm(0);
+if (smtp_receive_timeout > 0) alarm(0);
 if (rc <= 0)
   {
   /* Must put the error text in fixed store, because this might be during
   header reading, where it releases unused store above the header. */
   if (rc < 0)
     {
+    if (had_command_timeout)           /* set by signal handler */
+      smtp_command_timeout_exit();     /* does not return */
+    if (had_command_sigterm)
+      smtp_command_sigterm_exit();
+    if (had_data_timeout)
+      smtp_data_timeout_exit();
+    if (had_data_sigint)
+      smtp_data_sigint_exit();
+
     smtp_had_error = save_errno;
     smtp_read_error = string_copy_malloc(
       string_sprintf(" (error: %s)", strerror(save_errno)));
     }
-  else smtp_had_eof = 1;
+  else
+    smtp_had_eof = 1;
   return FALSE;
   }
 #ifndef DISABLE_DKIM
@@ -540,7 +613,7 @@ uschar * log_msg;
 for(;;)
   {
 #ifndef DISABLE_DKIM
-  BOOL dkim_save;
+  unsigned dkim_save;
 #endif
 
   if (chunking_data_left > 0)
@@ -551,13 +624,13 @@ for(;;)
   receive_ungetc = lwr_receive_ungetc;
 #ifndef DISABLE_DKIM
   dkim_save = dkim_collect_input;
-  dkim_collect_input = FALSE;
+  dkim_collect_input = 0;
 #endif
 
   /* Unless PIPELINING was offered, there should be no next command
   until after we ack that chunk */
 
-  if (!pipelining_advertised && !check_sync())
+  if (!f.smtp_in_pipelining_advertised && !check_sync())
     {
     unsigned n = smtp_inend - smtp_inptr;
     if (n > 32) n = 32;
@@ -854,22 +927,22 @@ be tidier to have it only in one place, but when it was added, it was easier to
 do it that way, so as not to have to mess with the code for the RCPT command,
 which sometimes uses smtp_printf() and sometimes smtp_respond(). */
 
-if (rcpt_in_progress)
+if (fl.rcpt_in_progress)
   {
   if (rcpt_smtp_response == NULL)
     rcpt_smtp_response = string_copy(big_buffer);
-  else if (rcpt_smtp_response_same &&
+  else if (fl.rcpt_smtp_response_same &&
            Ustrcmp(rcpt_smtp_response, big_buffer) != 0)
-    rcpt_smtp_response_same = FALSE;
-  rcpt_in_progress = FALSE;
+    fl.rcpt_smtp_response_same = FALSE;
+  fl.rcpt_in_progress = FALSE;
   }
 
 /* Now write the string */
 
 #ifdef SUPPORT_TLS
-if (tls_in.active >= 0)
+if (tls_in.active.sock >= 0)
   {
-  if (tls_write(TRUE, big_buffer, Ustrlen(big_buffer), more) < 0)
+  if (tls_write(NULL, big_buffer, Ustrlen(big_buffer), more) < 0)
     smtp_write_error = -1;
   }
 else
@@ -896,7 +969,7 @@ Returns:    0 for no error; -1 after an error
 int
 smtp_fflush(void)
 {
-if (tls_in.active < 0 && fflush(smtp_out) != 0) smtp_write_error = -1;
+if (tls_in.active.sock < 0 && fflush(smtp_out) != 0) smtp_write_error = -1;
 return smtp_write_error;
 }
 
@@ -916,16 +989,7 @@ Returns:  nothing
 static void
 command_timeout_handler(int sig)
 {
-sig = sig;    /* Keep picky compilers happy */
-log_write(L_lost_incoming_connection,
-          LOG_MAIN, "SMTP command timeout on%s connection from %s",
-          (tls_in.active >= 0)? " TLS" : "",
-          host_and_ident(FALSE));
-if (smtp_batched_input)
-  moan_smtp_batch(NULL, "421 SMTP command timeout");  /* Does not return */
-smtp_notquit_exit(US"command-timeout", US"421",
-  US"%s: SMTP command timeout - closing connection", smtp_active_hostname);
-exim_exit(EXIT_FAILURE, US"receiving");
+had_command_timeout = sig;
 }
 
 
@@ -943,13 +1007,7 @@ Returns:  nothing
 static void
 command_sigterm_handler(int sig)
 {
-sig = sig;    /* Keep picky compilers happy */
-log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info());
-if (smtp_batched_input)
-  moan_smtp_batch(NULL, "421 SIGTERM received");  /* Does not return */
-smtp_notquit_exit(US"signal-exit", US"421",
-  US"%s: Service not available - closing connection", smtp_active_hostname);
-exim_exit(EXIT_FAILURE, US"receiving");
+had_command_sigterm = sig;
 }
 
 
@@ -1469,7 +1527,7 @@ bad:
     }
   else
     {
-    proxy_session_failed = TRUE;
+    f.proxy_session_failed = TRUE;
     DEBUG(D_receive)
       debug_printf("Failure to extract proxied host, only QUIT allowed\n");
     }
@@ -1509,6 +1567,7 @@ int ptr = 0;
 smtp_cmd_list *p;
 BOOL hadnull = FALSE;
 
+had_command_timeout = 0;
 os_non_restarting_signal(SIGALRM, command_timeout_handler);
 
 while ((c = (receive_getc)(buffer_lim)) != '\n' && c != EOF)
@@ -1554,7 +1613,7 @@ for (p = cmd_list; p < cmd_list_end; p++)
   {
 #ifdef SUPPORT_PROXY
   /* Only allow QUIT command if Proxy Protocol parsing failed */
-  if (proxy_session && proxy_session_failed && p->cmd != QUIT_CMD)
+  if (proxy_session && f.proxy_session_failed && p->cmd != QUIT_CMD)
     continue;
 #endif
   if (  p->len
@@ -1569,7 +1628,7 @@ for (p = cmd_list; p < cmd_list_end; p++)
         check_sync &&                                  /* Local flag set */
         smtp_enforce_sync &&                           /* Global flag set */
         sender_host_address != NULL &&                 /* Not local input */
-        !sender_host_notsocket)                        /* Really is a socket */
+        !f.sender_host_notsocket)                        /* Really is a socket */
       return BADSYN_CMD;
 
     /* The variables $smtp_command and $smtp_command_argument point into the
@@ -1608,17 +1667,17 @@ for (p = cmd_list; p < cmd_list_end; p++)
 
 #ifdef SUPPORT_PROXY
 /* Only allow QUIT command if Proxy Protocol parsing failed */
-if (proxy_session && proxy_session_failed)
+if (proxy_session && f.proxy_session_failed)
   return PROXY_FAIL_IGNORE_CMD;
 #endif
 
 /* Enforce synchronization for unknown commands */
 
-if (smtp_inptr < smtp_inend &&                     /* Outstanding input */
-    check_sync &&                                  /* Local flag set */
-    smtp_enforce_sync &&                           /* Global flag set */
-    sender_host_address != NULL &&                 /* Not local input */
-    !sender_host_notsocket)                        /* Really is a socket */
+if (  smtp_inptr < smtp_inend          /* Outstanding input */
+   && check_sync                       /* Local flag set */
+   && smtp_enforce_sync                        /* Global flag set */
+   && sender_host_address              /* Not local input */
+   && !f.sender_host_notsocket)                /* Really is a socket */
   return BADSYN_CMD;
 
 return OTHER_CMD;
@@ -1695,10 +1754,10 @@ const uschar * hostname = sender_fullhost
 if (host_checking)
   return string_sprintf("SMTP connection from %s", hostname);
 
-if (sender_host_unknown || sender_host_notsocket)
+if (f.sender_host_unknown || f.sender_host_notsocket)
   return string_sprintf("SMTP connection from %s", sender_ident);
 
-if (is_inetd)
+if (f.is_inetd)
   return string_sprintf("SMTP connection from %s (via inetd)", hostname);
 
 if (LOGGING(incoming_interface) && interface_address != NULL)
@@ -1783,7 +1842,7 @@ for (i = 0; i < smtp_ch_index; i++)
 if (!(s = string_from_gstring(g))) s = US"";
 
 log_write(0, LOG_MAIN, "no MAIL in %sSMTP connection from %s D=%s%s",
-  tcp_in_fastopen ? US"TFO " : US"",
+  f.tcp_in_fastopen ? US"TFO " : US"",
   host_and_ident(FALSE), string_timesince(&smtp_connection_start), s);
 }
 
@@ -1834,7 +1893,7 @@ check_helo(uschar *s)
 {
 uschar *start = s;
 uschar *end = s + Ustrlen(s);
-BOOL yield = helo_accept_junk;
+BOOL yield = fl.helo_accept_junk;
 
 /* Discard any previous helo name */
 
@@ -1963,25 +2022,25 @@ message_linecount = 0;
 message_size = -1;
 acl_added_headers = NULL;
 acl_removed_headers = NULL;
-queue_only_policy = FALSE;
+f.queue_only_policy = FALSE;
 rcpt_smtp_response = NULL;
-rcpt_smtp_response_same = TRUE;
-rcpt_in_progress = FALSE;
-deliver_freeze = FALSE;                              /* Can be set by ACL */
+fl.rcpt_smtp_response_same = TRUE;
+fl.rcpt_in_progress = FALSE;
+f.deliver_freeze = FALSE;                              /* Can be set by ACL */
 freeze_tell = freeze_tell_config;                    /* Can be set by ACL */
 fake_response = OK;                                  /* Can be set by ACL */
 #ifdef WITH_CONTENT_SCAN
-no_mbox_unspool = FALSE;                             /* Can be set by ACL */
+f.no_mbox_unspool = FALSE;                             /* Can be set by ACL */
 #endif
-submission_mode = FALSE;                             /* Can be set by ACL */
-suppress_local_fixups = suppress_local_fixups_default; /* Can be set by ACL */
-active_local_from_check = local_from_check;          /* Can be set by ACL */
-active_local_sender_retain = local_sender_retain;    /* Can be set by ACL */
+f.submission_mode = FALSE;                             /* Can be set by ACL */
+f.suppress_local_fixups = f.suppress_local_fixups_default; /* Can be set by ACL */
+f.active_local_from_check = local_from_check;          /* Can be set by ACL */
+f.active_local_sender_retain = local_sender_retain;    /* Can be set by ACL */
 sending_ip_address = NULL;
 return_path = sender_address = NULL;
 sender_data = NULL;                                 /* Can be set by ACL */
-deliver_localpart_orig = NULL;
-deliver_domain_orig = NULL;
+deliver_localpart_parent = deliver_localpart_orig = NULL;
+deliver_domain_parent = deliver_domain_orig = NULL;
 callout_address = NULL;
 submission_name = NULL;                              /* Can be set by ACL */
 raw_sender = NULL;                  /* After SMTP rewrite, before qualifying */
@@ -1996,9 +2055,27 @@ bmi_run = 0;
 bmi_verdicts = NULL;
 #endif
 dnslist_domain = dnslist_matched = NULL;
+#ifdef SUPPORT_SPF
+spf_header_comment = spf_received = spf_result = spf_smtp_comment = NULL;
+spf_result_guessed = FALSE;
+#endif
 #ifndef DISABLE_DKIM
-dkim_cur_signer = dkim_signers = NULL;
-dkim_disable_verify = dkim_collect_input = FALSE;
+dkim_cur_signer = dkim_signers =
+dkim_signing_domain = dkim_signing_selector = dkim_signatures = NULL;
+dkim_cur_signer = dkim_signers = dkim_signing_domain = dkim_signing_selector = NULL;
+f.dkim_disable_verify = FALSE;
+dkim_collect_input = 0;
+dkim_verify_overall = dkim_verify_status = dkim_verify_reason = NULL;
+dkim_key_length = 0;
+dkim_verify_signers = US"$dkim_signers";
+#endif
+#ifdef EXPERIMENTAL_DMARC
+f.dmarc_has_been_checked = f.dmarc_disable_verify = f.dmarc_enable_forensic = FALSE;
+dmarc_domain_policy = dmarc_status = dmarc_status_text =
+dmarc_used_domain = NULL;
+#endif
+#ifdef EXPERIMENTAL_ARC
+arc_state = arc_state_reason = NULL;
 #endif
 dsn_ret = 0;
 dsn_envid = NULL;
@@ -2006,9 +2083,6 @@ deliver_host = deliver_host_address = NULL;       /* Can be set by ACL */
 #ifndef DISABLE_PRDR
 prdr_requested = FALSE;
 #endif
-#ifdef SUPPORT_SPF
-spf_header_comment = spf_received = spf_result = spf_smtp_comment = NULL;
-#endif
 #ifdef SUPPORT_I18N
 message_smtputf8 = FALSE;
 #endif
@@ -2159,7 +2233,7 @@ while (done <= 0)
 
       if (  !sender_domain
          && sender_address[0] != 0 && sender_address[0] != '@')
-       if (allow_unqualified_sender)
+       if (f.allow_unqualified_sender)
          {
          sender_address = rewrite_address_qualify(sender_address, FALSE);
          DEBUG(D_receive) debug_printf("unqualified address %s accepted "
@@ -2215,7 +2289,7 @@ while (done <= 0)
       add it to the list of recipients. */
 
       if (!recipient_domain)
-       if (allow_unqualified_recipient)
+       if (f.allow_unqualified_recipient)
          {
          DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
            recipient);
@@ -2321,7 +2395,7 @@ if (  getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0
    )
   {
   DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (state TCP_SYN_RECV)\n");
-  tcp_in_fastopen = TRUE;
+  f.tcp_in_fastopen = TRUE;
   }
 # endif
 }
@@ -2357,21 +2431,21 @@ smtp_ch_index = 0;
 
 /* Default values for certain variables */
 
-helo_seen = esmtp = helo_accept_junk = FALSE;
+fl.helo_seen = fl.esmtp = fl.helo_accept_junk = FALSE;
 smtp_mailcmd_count = 0;
 count_nonmail = TRUE_UNSET;
 synprot_error_count = unknown_command_count = nonmail_command_count = 0;
 smtp_delay_mail = smtp_rlm_base;
-auth_advertised = FALSE;
-pipelining_advertised = FALSE;
-pipelining_enable = TRUE;
+fl.auth_advertised = FALSE;
+f.smtp_in_pipelining_advertised = f.smtp_in_pipelining_used = FALSE;
+f.pipelining_enable = TRUE;
 sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
-smtp_exit_function_called = FALSE;    /* For avoiding loop in not-quit exit */
+fl.smtp_exit_function_called = FALSE;    /* For avoiding loop in not-quit exit */
 
 /* If receiving by -bs from a trusted user, or testing with -bh, we allow
 authentication settings from -oMaa to remain in force. */
 
-if (!host_checking && !sender_host_notsocket)
+if (!host_checking && !f.sender_host_notsocket)
   sender_host_auth_pubname = sender_host_authenticated = NULL;
 authenticated_by = NULL;
 
@@ -2380,11 +2454,14 @@ tls_in.cipher = tls_in.peerdn = NULL;
 tls_in.ourcert = tls_in.peercert = NULL;
 tls_in.sni = NULL;
 tls_in.ocsp = OCSP_NOT_REQ;
-tls_advertised = FALSE;
+fl.tls_advertised = FALSE;
+# ifdef EXPERIMENTAL_REQUIRETLS
+fl.requiretls_advertised = FALSE;
+# endif
 #endif
-dsn_advertised = FALSE;
+fl.dsn_advertised = FALSE;
 #ifdef SUPPORT_I18N
-smtputf8_advertised = FALSE;
+fl.smtputf8_advertised = FALSE;
 #endif
 
 /* Reset ACL connection variables */
@@ -2416,10 +2493,12 @@ else
     (sender_host_address ? protocols : protocols_local) [pnormal];
 
 /* Set up the buffer for inputting using direct read() calls, and arrange to
-call the local functions instead of the standard C ones. */
+call the local functions instead of the standard C ones.  Place a NUL at the
+end of the buffer to safety-stop C-string reads from it. */
 
 if (!(smtp_inbuffer = US malloc(IN_BUFFER_SIZE)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malloc() failed for SMTP input buffer");
+smtp_inbuffer[IN_BUFFER_SIZE-1] = '\0';
 
 receive_getc = smtp_getc;
 receive_getbuf = smtp_getbuf;
@@ -2456,7 +2535,7 @@ the flag sender_host_notsocket is used to suppress it.
 If smtp_accept_max and smtp_accept_reserve are set, keep some connections in
 reserve for certain hosts and/or networks. */
 
-if (!sender_host_unknown)
+if (!f.sender_host_unknown)
   {
   int rc;
   BOOL reserved_host = FALSE;
@@ -2486,7 +2565,7 @@ if (!sender_host_unknown)
 
   How to do this properly in IPv6 is not yet known. */
 
-  #if !HAVE_IPV6 && !defined(NO_IP_OPTIONS)
+#if !HAVE_IPV6 && !defined(NO_IP_OPTIONS)
 
   #ifdef GLIBC_IP_OPTIONS
     #if (!defined __GLIBC__) || (__GLIBC__ < 2)
@@ -2500,7 +2579,7 @@ if (!sender_host_unknown)
     #define OPTSTYLE 3
   #endif
 
-  if (!host_checking && !sender_host_notsocket)
+  if (!host_checking && !f.sender_host_notsocket)
     {
     #if OPTSTYLE == 1
     EXIM_SOCKLEN_T optlen = sizeof(struct ip_options) + MAX_IPOPTLEN;
@@ -2641,13 +2720,13 @@ if (!sender_host_unknown)
 
     else DEBUG(D_receive) debug_printf("no IP options found\n");
     }
-  #endif  /* HAVE_IPV6 && !defined(NO_IP_OPTIONS) */
+#endif  /* HAVE_IPV6 && !defined(NO_IP_OPTIONS) */
 
   /* Set keep-alive in socket options. The option is on by default. This
   setting is an attempt to get rid of some hanging connections that stick in
   read() when the remote end (usually a dialup) goes away. */
 
-  if (smtp_accept_keepalive && !sender_host_notsocket)
+  if (smtp_accept_keepalive && !f.sender_host_notsocket)
     ip_keepalive(fileno(smtp_out), sender_host_address, FALSE);
 
   /* If the current host matches host_lookup, set the name by doing a
@@ -2778,23 +2857,23 @@ if (!sender_host_unknown)
   addresses in the headers. For a site that permits no qualification, this
   won't take long, however. */
 
-  allow_unqualified_sender =
+  f.allow_unqualified_sender =
     verify_check_host(&sender_unqualified_hosts) == OK;
 
-  allow_unqualified_recipient =
+  f.allow_unqualified_recipient =
     verify_check_host(&recipient_unqualified_hosts) == OK;
 
   /* Determine whether HELO/EHLO is required for this host. The requirement
   can be hard or soft. */
 
-  helo_required = verify_check_host(&helo_verify_hosts) == OK;
-  if (!helo_required)
-    helo_verify = verify_check_host(&helo_try_verify_hosts) == OK;
+  fl.helo_required = verify_check_host(&helo_verify_hosts) == OK;
+  if (!fl.helo_required)
+    fl.helo_verify = verify_check_host(&helo_try_verify_hosts) == OK;
 
   /* Determine whether this hosts is permitted to send syntactic junk
   after a HELO or EHLO command. */
 
-  helo_accept_junk = verify_check_host(&helo_accept_junk_hosts) == OK;
+  fl.helo_accept_junk = verify_check_host(&helo_accept_junk_hosts) == OK;
   }
 
 /* For batch SMTP input we are now done. */
@@ -2806,7 +2885,7 @@ if (smtp_batched_input) return TRUE;
 
 #ifdef SUPPORT_PROXY
 proxy_session = FALSE;
-proxy_session_failed = FALSE;
+f.proxy_session_failed = FALSE;
 if (check_proxy_protocol_host())
   setup_proxy_protocol_host();
 #endif
@@ -3011,7 +3090,7 @@ smtp_respond(uschar* code, int codelen, BOOL final, uschar *msg)
 int esclen = 0;
 uschar *esc = US"";
 
-if (!final && no_multiline_responses) return;
+if (!final && f.no_multiline_responses) return;
 
 if (codelen > 4)
   {
@@ -3025,14 +3104,14 @@ be tidier to have it only in one place, but when it was added, it was easier to
 do it that way, so as not to have to mess with the code for the RCPT command,
 which sometimes uses smtp_printf() and sometimes smtp_respond(). */
 
-if (rcpt_in_progress)
+if (fl.rcpt_in_progress)
   {
   if (rcpt_smtp_response == NULL)
     rcpt_smtp_response = string_copy(msg);
-  else if (rcpt_smtp_response_same &&
+  else if (fl.rcpt_smtp_response_same &&
            Ustrcmp(rcpt_smtp_response, msg) != 0)
-    rcpt_smtp_response_same = FALSE;
-  rcpt_in_progress = FALSE;
+    fl.rcpt_smtp_response_same = FALSE;
+  fl.rcpt_in_progress = FALSE;
   }
 
 /* Now output the message, splitting it up into multiple lines if necessary.
@@ -3047,7 +3126,7 @@ for (;;)
     smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg);
     return;
     }
-  else if (nl[1] == 0 || no_multiline_responses)
+  else if (nl[1] == 0 || f.no_multiline_responses)
     {
     smtp_printf("%.3s%c%.*s%.*s\r\n", !final, code, final ? ' ':'-', esclen, esc,
       (int)(nl - msg), msg);
@@ -3219,8 +3298,8 @@ unless the sender_verify_fail log selector has been turned off. */
 if (sender_verified_failed &&
     !testflag(sender_verified_failed, af_sverify_told))
   {
-  BOOL save_rcpt_in_progress = rcpt_in_progress;
-  rcpt_in_progress = FALSE;  /* So as not to treat these as the error */
+  BOOL save_rcpt_in_progress = fl.rcpt_in_progress;
+  fl.rcpt_in_progress = FALSE;  /* So as not to treat these as the error */
 
   setflag(sender_verified_failed, af_sverify_told);
 
@@ -3252,7 +3331,7 @@ if (sender_verified_failed &&
         sender_verified_failed->address,
         sender_verified_failed->user_message));
 
-  rcpt_in_progress = save_rcpt_in_progress;
+  fl.rcpt_in_progress = save_rcpt_in_progress;
   }
 
 /* Sort out text for logging */
@@ -3277,7 +3356,7 @@ interactions between temp_details and return_error_details. One day it should
 be re-implemented in a tidier fashion. */
 
 else
-  if (acl_temp_details && user_msg)
+  if (f.acl_temp_details && user_msg)
     {
     if (  smtp_return_error_details
        && sender_verified_failed
@@ -3365,13 +3444,13 @@ uschar *log_msg = NULL;
 
 /* Check for recursive acll */
 
-if (smtp_exit_function_called)
+if (fl.smtp_exit_function_called)
   {
   log_write(0, LOG_PANIC, "smtp_notquit_exit() called more than once (%s)",
     reason);
   return;
   }
-smtp_exit_function_called = TRUE;
+fl.smtp_exit_function_called = TRUE;
 
 /* Call the not-QUIT ACL, if there is one, unless no reason is given. */
 
@@ -3447,27 +3526,27 @@ if (sender_helo_name == NULL)
 else if (sender_host_address == NULL)
   {
   HDEBUG(D_receive) debug_printf("no client IP address: assume success\n");
-  helo_verified = TRUE;
+  f.helo_verified = TRUE;
   }
 
 /* Deal with the more common case when there is a sending IP address */
 
 else if (sender_helo_name[0] == '[')
   {
-  helo_verified = Ustrncmp(sender_helo_name+1, sender_host_address,
+  f.helo_verified = Ustrncmp(sender_helo_name+1, sender_host_address,
     Ustrlen(sender_host_address)) == 0;
 
-  #if HAVE_IPV6
-  if (!helo_verified)
+#if HAVE_IPV6
+  if (!f.helo_verified)
     {
     if (strncmpic(sender_host_address, US"::ffff:", 7) == 0)
-      helo_verified = Ustrncmp(sender_helo_name + 1,
+      f.helo_verified = Ustrncmp(sender_helo_name + 1,
         sender_host_address + 7, Ustrlen(sender_host_address) - 7) == 0;
     }
-  #endif
+#endif
 
   HDEBUG(D_receive)
-    { if (helo_verified) debug_printf("matched host address\n"); }
+    { if (f.helo_verified) debug_printf("matched host address\n"); }
   }
 
 /* Do a reverse lookup if one hasn't already given a positive or negative
@@ -3482,7 +3561,7 @@ else
   /* If a host name is known, check it and all its aliases. */
 
   if (sender_host_name)
-    if ((helo_verified = strcmpic(sender_host_name, sender_helo_name) == 0))
+    if ((f.helo_verified = strcmpic(sender_host_name, sender_helo_name) == 0))
       {
       sender_helo_dnssec = sender_host_dnssec;
       HDEBUG(D_receive) debug_printf("matched host name\n");
@@ -3491,19 +3570,19 @@ else
       {
       uschar **aliases = sender_host_aliases;
       while (*aliases)
-        if ((helo_verified = strcmpic(*aliases++, sender_helo_name) == 0))
+        if ((f.helo_verified = strcmpic(*aliases++, sender_helo_name) == 0))
          {
          sender_helo_dnssec = sender_host_dnssec;
          break;
          }
 
-      HDEBUG(D_receive) if (helo_verified)
+      HDEBUG(D_receive) if (f.helo_verified)
           debug_printf("matched alias %s\n", *(--aliases));
       }
 
   /* Final attempt: try a forward lookup of the helo name */
 
-  if (!helo_verified)
+  if (!f.helo_verified)
     {
     int rc;
     host_item h;
@@ -3525,7 +3604,7 @@ else
       for (hh = &h; hh; hh = hh->next)
         if (Ustrcmp(hh->address, sender_host_address) == 0)
           {
-          helo_verified = TRUE;
+          f.helo_verified = TRUE;
          if (h.dnssec == DS_YES) sender_helo_dnssec = TRUE;
           HDEBUG(D_receive)
            {
@@ -3538,7 +3617,7 @@ else
     }
   }
 
-if (!helo_verified) helo_verify_failed = TRUE;  /* We've tried ... */
+if (!f.helo_verified) f.helo_verify_failed = TRUE;  /* We've tried ... */
 return yield;
 }
 
@@ -3628,7 +3707,7 @@ switch(rc)
 
     received_protocol =
       (sender_host_address ? protocols : protocols_local)
-       [pextend + pauthed + (tls_in.active >= 0 ? pcrpted:0)];
+       [pextend + pauthed + (tls_in.active.sock >= 0 ? pcrpted:0)];
     *s = *ss = US"235 Authentication succeeded";
     authenticated_by = au;
     break;
@@ -3685,7 +3764,7 @@ static int
 qualify_recipient(uschar ** recipient, uschar * smtp_cmd_data, uschar * tag)
 {
 int rd;
-if (allow_unqualified_recipient || strcmpic(*recipient, US"postmaster") == 0)
+if (f.allow_unqualified_recipient || strcmpic(*recipient, US"postmaster") == 0)
   {
   DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
     *recipient);
@@ -3722,7 +3801,7 @@ else
   smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
 
 #ifdef SUPPORT_TLS
-tls_close(TRUE, TLS_SHUTDOWN_NOWAIT);
+tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
 #endif
 
 log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
@@ -3787,7 +3866,7 @@ for the host). Note: we do NOT reset AUTH at this point. */
 smtp_reset(reset_point);
 message_ended = END_NOTSTARTED;
 
-chunking_state = chunking_offered ? CHUNKING_OFFERED : CHUNKING_NOT_OFFERED;
+chunking_state = f.chunking_offered ? CHUNKING_OFFERED : CHUNKING_NOT_OFFERED;
 
 cmd_list[CMD_LIST_RSET].is_mail_cmd = TRUE;
 cmd_list[CMD_LIST_HELO].is_mail_cmd = TRUE;
@@ -3798,6 +3877,7 @@ cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE;
 
 /* Set the local signal handler for SIGTERM - it tries to end off tidily */
 
+had_command_sigterm = 0;
 os_non_restarting_signal(SIGTERM, command_sigterm_handler);
 
 /* Batched SMTP is handled in a different function. */
@@ -3832,7 +3912,7 @@ while (done <= 0)
 
 #ifdef AUTH_TLS
   /* Check once per STARTTLS or SSL-on-connect for a TLS AUTH */
-  if (  tls_in.active >= 0
+  if (  tls_in.active.sock >= 0
      && tls_in.peercert
      && tls_in.certificate_verified
      && cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd
@@ -3885,86 +3965,86 @@ while (done <= 0)
     AUTHS will eventually hit the nonmail threshold. */
 
     case AUTH_CMD:
-    HAD(SCH_AUTH);
-    authentication_failed = TRUE;
-    cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE;
+      HAD(SCH_AUTH);
+      authentication_failed = TRUE;
+      cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE;
 
-    if (!auth_advertised && !allow_auth_unadvertised)
-      {
-      done = synprot_error(L_smtp_protocol_error, 503, NULL,
-        US"AUTH command used when not advertised");
-      break;
-      }
-    if (sender_host_authenticated)
-      {
-      done = synprot_error(L_smtp_protocol_error, 503, NULL,
-        US"already authenticated");
-      break;
-      }
-    if (sender_address)
-      {
-      done = synprot_error(L_smtp_protocol_error, 503, NULL,
-        US"not permitted in mail transaction");
-      break;
-      }
+      if (!fl.auth_advertised && !f.allow_auth_unadvertised)
+       {
+       done = synprot_error(L_smtp_protocol_error, 503, NULL,
+         US"AUTH command used when not advertised");
+       break;
+       }
+      if (sender_host_authenticated)
+       {
+       done = synprot_error(L_smtp_protocol_error, 503, NULL,
+         US"already authenticated");
+       break;
+       }
+      if (sender_address)
+       {
+       done = synprot_error(L_smtp_protocol_error, 503, NULL,
+         US"not permitted in mail transaction");
+       break;
+       }
 
-    /* Check the ACL */
+      /* Check the ACL */
 
-    if (  acl_smtp_auth
-       && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
-                 &user_msg, &log_msg)) != OK
-       )
-      {
-      done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
-      break;
-      }
+      if (  acl_smtp_auth
+        && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
+                   &user_msg, &log_msg)) != OK
+        )
+       {
+       done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
+       break;
+       }
 
-    /* Find the name of the requested authentication mechanism. */
+      /* Find the name of the requested authentication mechanism. */
 
-    s = smtp_cmd_data;
-    while ((c = *smtp_cmd_data) != 0 && !isspace(c))
-      {
-      if (!isalnum(c) && c != '-' && c != '_')
-        {
-        done = synprot_error(L_smtp_syntax_error, 501, NULL,
-          US"invalid character in authentication mechanism name");
-        goto COMMAND_LOOP;
-        }
-      smtp_cmd_data++;
-      }
+      s = smtp_cmd_data;
+      while ((c = *smtp_cmd_data) != 0 && !isspace(c))
+       {
+       if (!isalnum(c) && c != '-' && c != '_')
+         {
+         done = synprot_error(L_smtp_syntax_error, 501, NULL,
+           US"invalid character in authentication mechanism name");
+         goto COMMAND_LOOP;
+         }
+       smtp_cmd_data++;
+       }
 
-    /* If not at the end of the line, we must be at white space. Terminate the
-    name and move the pointer on to any data that may be present. */
+      /* If not at the end of the line, we must be at white space. Terminate the
+      name and move the pointer on to any data that may be present. */
 
-    if (*smtp_cmd_data != 0)
-      {
-      *smtp_cmd_data++ = 0;
-      while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
-      }
+      if (*smtp_cmd_data != 0)
+       {
+       *smtp_cmd_data++ = 0;
+       while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
+       }
 
-    /* Search for an authentication mechanism which is configured for use
-    as a server and which has been advertised (unless, sigh, allow_auth_
-    unadvertised is set). */
+      /* Search for an authentication mechanism which is configured for use
+      as a server and which has been advertised (unless, sigh, allow_auth_
+      unadvertised is set). */
 
-    for (au = auths; au; au = au->next)
-      if (strcmpic(s, au->public_name) == 0 && au->server &&
-          (au->advertised || allow_auth_unadvertised))
-       break;
+      for (au = auths; au; au = au->next)
+       if (strcmpic(s, au->public_name) == 0 && au->server &&
+           (au->advertised || f.allow_auth_unadvertised))
+         break;
 
-    if (au)
-      {
-      c = smtp_in_auth(au, &s, &ss);
+      if (au)
+       {
+       c = smtp_in_auth(au, &s, &ss);
 
-      smtp_printf("%s\r\n", FALSE, s);
-      if (c != OK)
-       log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
-         au->name, host_and_ident(FALSE), ss);
-      }
-    else
-      done = synprot_error(L_smtp_protocol_error, 504, NULL,
-        string_sprintf("%s authentication mechanism not supported", s));
+       smtp_printf("%s\r\n", FALSE, s);
+       if (c != OK)
+         log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
+           au->name, host_and_ident(FALSE), ss);
+       }
+      else
+       done = synprot_error(L_smtp_protocol_error, 504, NULL,
+         string_sprintf("%s authentication mechanism not supported", s));
 
-    break;  /* AUTH_CMD */
+      break;  /* AUTH_CMD */
 
     /* The HELO/EHLO commands are permitted to appear in the middle of a
     session as well as at the beginning. They have the effect of a reset in
@@ -3983,395 +4063,409 @@ while (done <= 0)
     it did the reset first. */
 
     case HELO_CMD:
-    HAD(SCH_HELO);
-    hello = US"HELO";
-    esmtp = FALSE;
-    goto HELO_EHLO;
+      HAD(SCH_HELO);
+      hello = US"HELO";
+      fl.esmtp = FALSE;
+      goto HELO_EHLO;
 
     case EHLO_CMD:
-    HAD(SCH_EHLO);
-    hello = US"EHLO";
-    esmtp = TRUE;
+      HAD(SCH_EHLO);
+      hello = US"EHLO";
+      fl.esmtp = TRUE;
 
     HELO_EHLO:      /* Common code for HELO and EHLO */
-    cmd_list[CMD_LIST_HELO].is_mail_cmd = FALSE;
-    cmd_list[CMD_LIST_EHLO].is_mail_cmd = FALSE;
+      cmd_list[CMD_LIST_HELO].is_mail_cmd = FALSE;
+      cmd_list[CMD_LIST_EHLO].is_mail_cmd = FALSE;
 
-    /* Reject the HELO if its argument was invalid or non-existent. A
-    successful check causes the argument to be saved in malloc store. */
+      /* Reject the HELO if its argument was invalid or non-existent. A
+      successful check causes the argument to be saved in malloc store. */
 
-    if (!check_helo(smtp_cmd_data))
-      {
-      smtp_printf("501 Syntactically invalid %s argument(s)\r\n", FALSE, hello);
+      if (!check_helo(smtp_cmd_data))
+       {
+       smtp_printf("501 Syntactically invalid %s argument(s)\r\n", FALSE, hello);
 
-      log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically "
-        "invalid argument(s): %s", hello, host_and_ident(FALSE),
-        (*smtp_cmd_argument == 0)? US"(no argument given)" :
-                           string_printing(smtp_cmd_argument));
+       log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically "
+         "invalid argument(s): %s", hello, host_and_ident(FALSE),
+         *smtp_cmd_argument == 0 ? US"(no argument given)" :
+                            string_printing(smtp_cmd_argument));
 
-      if (++synprot_error_count > smtp_max_synprot_errors)
-        {
-        log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
-          "syntax or protocol errors (last command was \"%s\")",
-          host_and_ident(FALSE), string_printing(smtp_cmd_buffer));
-        done = 1;
-        }
+       if (++synprot_error_count > smtp_max_synprot_errors)
+         {
+         log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
+           "syntax or protocol errors (last command was \"%s\")",
+           host_and_ident(FALSE), string_printing(smtp_cmd_buffer));
+         done = 1;
+         }
 
-      break;
-      }
+       break;
+       }
 
-    /* If sender_host_unknown is true, we have got here via the -bs interface,
-    not called from inetd. Otherwise, we are running an IP connection and the
-    host address will be set. If the helo name is the primary name of this
-    host and we haven't done a reverse lookup, force one now. If helo_required
-    is set, ensure that the HELO name matches the actual host. If helo_verify
-    is set, do the same check, but softly. */
+      /* If sender_host_unknown is true, we have got here via the -bs interface,
+      not called from inetd. Otherwise, we are running an IP connection and the
+      host address will be set. If the helo name is the primary name of this
+      host and we haven't done a reverse lookup, force one now. If helo_required
+      is set, ensure that the HELO name matches the actual host. If helo_verify
+      is set, do the same check, but softly. */
 
-    if (!sender_host_unknown)
-      {
-      BOOL old_helo_verified = helo_verified;
-      uschar *p = smtp_cmd_data;
+      if (!f.sender_host_unknown)
+       {
+       BOOL old_helo_verified = f.helo_verified;
+       uschar *p = smtp_cmd_data;
 
-      while (*p != 0 && !isspace(*p)) { *p = tolower(*p); p++; }
-      *p = 0;
+       while (*p != 0 && !isspace(*p)) { *p = tolower(*p); p++; }
+       *p = 0;
 
-      /* Force a reverse lookup if HELO quoted something in helo_lookup_domains
-      because otherwise the log can be confusing. */
+       /* Force a reverse lookup if HELO quoted something in helo_lookup_domains
+       because otherwise the log can be confusing. */
 
-      if (  !sender_host_name
-         && (deliver_domain = sender_helo_name,  /* set $domain */
-             match_isinlist(sender_helo_name, CUSS &helo_lookup_domains, 0,
-              &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL)) == OK)
-        (void)host_name_lookup();
+       if (  !sender_host_name
+          && (deliver_domain = sender_helo_name,  /* set $domain */
+              match_isinlist(sender_helo_name, CUSS &helo_lookup_domains, 0,
+               &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL)) == OK)
+         (void)host_name_lookup();
 
-      /* Rebuild the fullhost info to include the HELO name (and the real name
-      if it was looked up.) */
+       /* Rebuild the fullhost info to include the HELO name (and the real name
+       if it was looked up.) */
 
-      host_build_sender_fullhost();  /* Rebuild */
-      set_process_info("handling%s incoming connection from %s",
-        (tls_in.active >= 0)? " TLS" : "", host_and_ident(FALSE));
+       host_build_sender_fullhost();  /* Rebuild */
+       set_process_info("handling%s incoming connection from %s",
+         tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE));
 
-      /* Verify if configured. This doesn't give much security, but it does
-      make some people happy to be able to do it. If helo_required is set,
-      (host matches helo_verify_hosts) failure forces rejection. If helo_verify
-      is set (host matches helo_try_verify_hosts), it does not. This is perhaps
-      now obsolescent, since the verification can now be requested selectively
-      at ACL time. */
+       /* Verify if configured. This doesn't give much security, but it does
+       make some people happy to be able to do it. If helo_required is set,
+       (host matches helo_verify_hosts) failure forces rejection. If helo_verify
+       is set (host matches helo_try_verify_hosts), it does not. This is perhaps
+       now obsolescent, since the verification can now be requested selectively
+       at ACL time. */
 
-      helo_verified = helo_verify_failed = sender_helo_dnssec = FALSE;
-      if (helo_required || helo_verify)
-        {
-        BOOL tempfail = !smtp_verify_helo();
-        if (!helo_verified)
-          {
-          if (helo_required)
-            {
-            smtp_printf("%d %s argument does not match calling host\r\n", FALSE,
-              tempfail? 451 : 550, hello);
-            log_write(0, LOG_MAIN|LOG_REJECT, "%srejected \"%s %s\" from %s",
-              tempfail? "temporarily " : "",
-              hello, sender_helo_name, host_and_ident(FALSE));
-            helo_verified = old_helo_verified;
-            break;                   /* End of HELO/EHLO processing */
-            }
-          HDEBUG(D_all) debug_printf("%s verification failed but host is in "
-            "helo_try_verify_hosts\n", hello);
-          }
-        }
-      }
+       f.helo_verified = f.helo_verify_failed = sender_helo_dnssec = FALSE;
+       if (fl.helo_required || fl.helo_verify)
+         {
+         BOOL tempfail = !smtp_verify_helo();
+         if (!f.helo_verified)
+           {
+           if (fl.helo_required)
+             {
+             smtp_printf("%d %s argument does not match calling host\r\n", FALSE,
+               tempfail? 451 : 550, hello);
+             log_write(0, LOG_MAIN|LOG_REJECT, "%srejected \"%s %s\" from %s",
+               tempfail? "temporarily " : "",
+               hello, sender_helo_name, host_and_ident(FALSE));
+             f.helo_verified = old_helo_verified;
+             break;                   /* End of HELO/EHLO processing */
+             }
+           HDEBUG(D_all) debug_printf("%s verification failed but host is in "
+             "helo_try_verify_hosts\n", hello);
+           }
+         }
+       }
 
 #ifdef SUPPORT_SPF
-    /* set up SPF context */
-    spf_init(sender_helo_name, sender_host_address);
+      /* set up SPF context */
+      spf_init(sender_helo_name, sender_host_address);
 #endif
 
-    /* Apply an ACL check if one is defined; afterwards, recheck
-    synchronization in case the client started sending in a delay. */
+      /* Apply an ACL check if one is defined; afterwards, recheck
+      synchronization in case the client started sending in a delay. */
 
-    if (acl_smtp_helo)
-      if ((rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo,
-               &user_msg, &log_msg)) != OK)
-        {
-        done = smtp_handle_acl_fail(ACL_WHERE_HELO, rc, user_msg, log_msg);
-       if (sender_helo_name)
+      if (acl_smtp_helo)
+       if ((rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo,
+                 &user_msg, &log_msg)) != OK)
          {
-         store_free(sender_helo_name);
-         sender_helo_name = NULL;
+         done = smtp_handle_acl_fail(ACL_WHERE_HELO, rc, user_msg, log_msg);
+         if (sender_helo_name)
+           {
+           store_free(sender_helo_name);
+           sender_helo_name = NULL;
+           }
+         host_build_sender_fullhost();  /* Rebuild */
+         break;
          }
-        host_build_sender_fullhost();  /* Rebuild */
-        break;
-        }
-      else if (!check_sync()) goto SYNC_FAILURE;
+       else if (!check_sync()) goto SYNC_FAILURE;
 
-    /* Generate an OK reply. The default string includes the ident if present,
-    and also the IP address if present. Reflecting back the ident is intended
-    as a deterrent to mail forgers. For maximum efficiency, and also because
-    some broken systems expect each response to be in a single packet, arrange
-    that the entire reply is sent in one write(). */
+      /* Generate an OK reply. The default string includes the ident if present,
+      and also the IP address if present. Reflecting back the ident is intended
+      as a deterrent to mail forgers. For maximum efficiency, and also because
+      some broken systems expect each response to be in a single packet, arrange
+      that the entire reply is sent in one write(). */
 
-    auth_advertised = FALSE;
-    pipelining_advertised = FALSE;
+      fl.auth_advertised = FALSE;
+      f.smtp_in_pipelining_advertised = FALSE;
 #ifdef SUPPORT_TLS
-    tls_advertised = FALSE;
+      fl.tls_advertised = FALSE;
+# ifdef EXPERIMENTAL_REQUIRETLS
+      fl.requiretls_advertised = FALSE;
+# endif
 #endif
-    dsn_advertised = FALSE;
+      fl.dsn_advertised = FALSE;
 #ifdef SUPPORT_I18N
-    smtputf8_advertised = FALSE;
+      fl.smtputf8_advertised = FALSE;
 #endif
 
-    smtp_code = US"250 ";        /* Default response code plus space*/
-    if (!user_msg)
-      {
-      s = string_sprintf("%.3s %s Hello %s%s%s",
-        smtp_code,
-        smtp_active_hostname,
-        sender_ident ? sender_ident : US"",
-        sender_ident ? US" at " : US"",
-        sender_host_name ? sender_host_name : sender_helo_name);
-      g = string_cat(NULL, s);
-
-      if (sender_host_address)
-        {
-        g = string_catn(g, US" [", 2);
-        g = string_cat (g, sender_host_address);
-        g = string_catn(g, US"]", 1);
-        }
-      }
+      smtp_code = US"250 ";        /* Default response code plus space*/
+      if (!user_msg)
+       {
+       s = string_sprintf("%.3s %s Hello %s%s%s",
+         smtp_code,
+         smtp_active_hostname,
+         sender_ident ? sender_ident : US"",
+         sender_ident ? US" at " : US"",
+         sender_host_name ? sender_host_name : sender_helo_name);
+       g = string_cat(NULL, s);
+
+       if (sender_host_address)
+         {
+         g = string_catn(g, US" [", 2);
+         g = string_cat (g, sender_host_address);
+         g = string_catn(g, US"]", 1);
+         }
+       }
 
-    /* A user-supplied EHLO greeting may not contain more than one line. Note
-    that the code returned by smtp_message_code() includes the terminating
-    whitespace character. */
+      /* A user-supplied EHLO greeting may not contain more than one line. Note
+      that the code returned by smtp_message_code() includes the terminating
+      whitespace character. */
 
-    else
-      {
-      char *ss;
-      int codelen = 4;
-      smtp_message_code(&smtp_code, &codelen, &user_msg, NULL, TRUE);
-      s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg);
-      if ((ss = strpbrk(CS s, "\r\n")) != NULL)
-        {
-        log_write(0, LOG_MAIN|LOG_PANIC, "EHLO/HELO response must not contain "
-          "newlines: message truncated: %s", string_printing(s));
-        *ss = 0;
-        }
-      g = string_cat(NULL, s);
-      }
+      else
+       {
+       char *ss;
+       int codelen = 4;
+       smtp_message_code(&smtp_code, &codelen, &user_msg, NULL, TRUE);
+       s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg);
+       if ((ss = strpbrk(CS s, "\r\n")) != NULL)
+         {
+         log_write(0, LOG_MAIN|LOG_PANIC, "EHLO/HELO response must not contain "
+           "newlines: message truncated: %s", string_printing(s));
+         *ss = 0;
+         }
+       g = string_cat(NULL, s);
+       }
 
-    g = string_catn(g, US"\r\n", 2);
+      g = string_catn(g, US"\r\n", 2);
 
-    /* If we received EHLO, we must create a multiline response which includes
-    the functions supported. */
+      /* If we received EHLO, we must create a multiline response which includes
+      the functions supported. */
 
-    if (esmtp)
-      {
-      g->s[3] = '-';
+      if (fl.esmtp)
+       {
+       g->s[3] = '-';
 
-      /* I'm not entirely happy with this, as an MTA is supposed to check
-      that it has enough room to accept a message of maximum size before
-      it sends this. However, there seems little point in not sending it.
-      The actual size check happens later at MAIL FROM time. By postponing it
-      till then, VRFY and EXPN can be used after EHLO when space is short. */
+       /* I'm not entirely happy with this, as an MTA is supposed to check
+       that it has enough room to accept a message of maximum size before
+       it sends this. However, there seems little point in not sending it.
+       The actual size check happens later at MAIL FROM time. By postponing it
+       till then, VRFY and EXPN can be used after EHLO when space is short. */
 
-      if (thismessage_size_limit > 0)
-        {
-        sprintf(CS big_buffer, "%.3s-SIZE %d\r\n", smtp_code,
-          thismessage_size_limit);
-        g = string_cat(g, big_buffer);
-        }
-      else
-        {
-        g = string_catn(g, smtp_code, 3);
-        g = string_catn(g, US"-SIZE\r\n", 7);
-        }
+       if (thismessage_size_limit > 0)
+         {
+         sprintf(CS big_buffer, "%.3s-SIZE %d\r\n", smtp_code,
+           thismessage_size_limit);
+         g = string_cat(g, big_buffer);
+         }
+       else
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-SIZE\r\n", 7);
+         }
 
-      /* Exim does not do protocol conversion or data conversion. It is 8-bit
-      clean; if it has an 8-bit character in its hand, it just sends it. It
-      cannot therefore specify 8BITMIME and remain consistent with the RFCs.
-      However, some users want this option simply in order to stop MUAs
-      mangling messages that contain top-bit-set characters. It is therefore
-      provided as an option. */
+       /* Exim does not do protocol conversion or data conversion. It is 8-bit
+       clean; if it has an 8-bit character in its hand, it just sends it. It
+       cannot therefore specify 8BITMIME and remain consistent with the RFCs.
+       However, some users want this option simply in order to stop MUAs
+       mangling messages that contain top-bit-set characters. It is therefore
+       provided as an option. */
 
-      if (accept_8bitmime)
-        {
-        g = string_catn(g, smtp_code, 3);
-        g = string_catn(g, US"-8BITMIME\r\n", 11);
-        }
+       if (accept_8bitmime)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-8BITMIME\r\n", 11);
+         }
 
-      /* Advertise DSN support if configured to do so. */
-      if (verify_check_host(&dsn_advertise_hosts) != FAIL)
-        {
-        g = string_catn(g, smtp_code, 3);
-        g = string_catn(g, US"-DSN\r\n", 6);
-        dsn_advertised = TRUE;
-        }
+       /* Advertise DSN support if configured to do so. */
+       if (verify_check_host(&dsn_advertise_hosts) != FAIL)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-DSN\r\n", 6);
+         fl.dsn_advertised = TRUE;
+         }
 
-      /* Advertise ETRN/VRFY/EXPN if there's are ACL checking whether a host is
-      permitted to issue them; a check is made when any host actually tries. */
+       /* Advertise ETRN/VRFY/EXPN if there's are ACL checking whether a host is
+       permitted to issue them; a check is made when any host actually tries. */
 
-      if (acl_smtp_etrn)
-        {
-        g = string_catn(g, smtp_code, 3);
-        g = string_catn(g, US"-ETRN\r\n", 7);
-        }
-      if (acl_smtp_vrfy)
-        {
-        g = string_catn(g, smtp_code, 3);
-        g = string_catn(g, US"-VRFY\r\n", 7);
-        }
-      if (acl_smtp_expn)
-        {
-        g = string_catn(g, smtp_code, 3);
-        g = string_catn(g, US"-EXPN\r\n", 7);
-        }
+       if (acl_smtp_etrn)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-ETRN\r\n", 7);
+         }
+       if (acl_smtp_vrfy)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-VRFY\r\n", 7);
+         }
+       if (acl_smtp_expn)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-EXPN\r\n", 7);
+         }
 
-      /* Exim is quite happy with pipelining, so let the other end know that
-      it is safe to use it, unless advertising is disabled. */
+       /* Exim is quite happy with pipelining, so let the other end know that
+       it is safe to use it, unless advertising is disabled. */
 
-      if (pipelining_enable &&
-          verify_check_host(&pipelining_advertise_hosts) == OK)
-        {
-        g = string_catn(g, smtp_code, 3);
-        g = string_catn(g, US"-PIPELINING\r\n", 13);
-        sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
-        pipelining_advertised = TRUE;
-        }
+       if (f.pipelining_enable &&
+           verify_check_host(&pipelining_advertise_hosts) == OK)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-PIPELINING\r\n", 13);
+         sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
+         f.smtp_in_pipelining_advertised = TRUE;
+         }
 
 
-      /* If any server authentication mechanisms are configured, advertise
-      them if the current host is in auth_advertise_hosts. The problem with
-      advertising always is that some clients then require users to
-      authenticate (and aren't configurable otherwise) even though it may not
-      be necessary (e.g. if the host is in host_accept_relay).
+       /* If any server authentication mechanisms are configured, advertise
+       them if the current host is in auth_advertise_hosts. The problem with
+       advertising always is that some clients then require users to
+       authenticate (and aren't configurable otherwise) even though it may not
+       be necessary (e.g. if the host is in host_accept_relay).
 
-      RFC 2222 states that SASL mechanism names contain only upper case
-      letters, so output the names in upper case, though we actually recognize
-      them in either case in the AUTH command. */
+       RFC 2222 states that SASL mechanism names contain only upper case
+       letters, so output the names in upper case, though we actually recognize
+       them in either case in the AUTH command. */
 
-      if (  auths
+       if (  auths
 #ifdef AUTH_TLS
-        && !sender_host_authenticated
+          && !sender_host_authenticated
 #endif
-         && verify_check_host(&auth_advertise_hosts) == OK
-        )
-       {
-       auth_instance *au;
-       BOOL first = TRUE;
-       for (au = auths; au; au = au->next)
+          && verify_check_host(&auth_advertise_hosts) == OK
+          )
          {
-         au->advertised = FALSE;
-         if (au->server)
+         auth_instance *au;
+         BOOL first = TRUE;
+         for (au = auths; au; au = au->next)
            {
-           DEBUG(D_auth+D_expand) debug_printf_indent(
-             "Evaluating advertise_condition for %s athenticator\n",
-             au->public_name);
-           if (  !au->advertise_condition
-              || expand_check_condition(au->advertise_condition, au->name,
-                     US"authenticator")
-              )
+           au->advertised = FALSE;
+           if (au->server)
              {
-             int saveptr;
-             if (first)
+             DEBUG(D_auth+D_expand) debug_printf_indent(
+               "Evaluating advertise_condition for %s athenticator\n",
+               au->public_name);
+             if (  !au->advertise_condition
+                || expand_check_condition(au->advertise_condition, au->name,
+                       US"authenticator")
+                )
                {
-               g = string_catn(g, smtp_code, 3);
-               g = string_catn(g, US"-AUTH", 5);
-               first = FALSE;
-               auth_advertised = TRUE;
+               int saveptr;
+               if (first)
+                 {
+                 g = string_catn(g, smtp_code, 3);
+                 g = string_catn(g, US"-AUTH", 5);
+                 first = FALSE;
+                 fl.auth_advertised = TRUE;
+                 }
+               saveptr = g->ptr;
+               g = string_catn(g, US" ", 1);
+               g = string_cat (g, au->public_name);
+               while (++saveptr < g->ptr) g->s[saveptr] = toupper(g->s[saveptr]);
+               au->advertised = TRUE;
                }
-             saveptr = g->ptr;
-             g = string_catn(g, US" ", 1);
-             g = string_cat (g, au->public_name);
-             while (++saveptr < g->ptr) g->s[saveptr] = toupper(g->s[saveptr]);
-             au->advertised = TRUE;
              }
            }
-         }
 
-       if (!first) g = string_catn(g, US"\r\n", 2);
-       }
+         if (!first) g = string_catn(g, US"\r\n", 2);
+         }
 
-      /* RFC 3030 CHUNKING */
+       /* RFC 3030 CHUNKING */
 
-      if (verify_check_host(&chunking_advertise_hosts) != FAIL)
-        {
-        g = string_catn(g, smtp_code, 3);
-        g = string_catn(g, US"-CHUNKING\r\n", 11);
-       chunking_offered = TRUE;
-       chunking_state = CHUNKING_OFFERED;
-        }
+       if (verify_check_host(&chunking_advertise_hosts) != FAIL)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-CHUNKING\r\n", 11);
+         f.chunking_offered = TRUE;
+         chunking_state = CHUNKING_OFFERED;
+         }
 
-      /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
-      if it has been included in the binary, and the host matches
-      tls_advertise_hosts. We must *not* advertise if we are already in a
-      secure connection. */
+       /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
+       if it has been included in the binary, and the host matches
+       tls_advertise_hosts. We must *not* advertise if we are already in a
+       secure connection. */
 
 #ifdef SUPPORT_TLS
-      if (tls_in.active < 0 &&
-          verify_check_host(&tls_advertise_hosts) != FAIL)
-        {
-        g = string_catn(g, smtp_code, 3);
-        g = string_catn(g, US"-STARTTLS\r\n", 11);
-        tls_advertised = TRUE;
-        }
+       if (tls_in.active.sock < 0 &&
+           verify_check_host(&tls_advertise_hosts) != FAIL)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-STARTTLS\r\n", 11);
+         fl.tls_advertised = TRUE;
+         }
+
+# ifdef EXPERIMENTAL_REQUIRETLS
+       /* Advertise REQUIRETLS only once we are in a secure connection */
+       if (  tls_in.active.sock >= 0
+          && verify_check_host(&tls_advertise_requiretls) != FAIL)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-REQUIRETLS\r\n", 13);
+         fl.requiretls_advertised = TRUE;
+         }
+# endif
 #endif
 
 #ifndef DISABLE_PRDR
-      /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */
-      if (prdr_enable)
-        {
-        g = string_catn(g, smtp_code, 3);
-        g = string_catn(g, US"-PRDR\r\n", 7);
-       }
+       /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */
+       if (prdr_enable)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-PRDR\r\n", 7);
+         }
 #endif
 
 #ifdef SUPPORT_I18N
-      if (  accept_8bitmime
-         && verify_check_host(&smtputf8_advertise_hosts) != FAIL)
-       {
-        g = string_catn(g, smtp_code, 3);
-        g = string_catn(g, US"-SMTPUTF8\r\n", 11);
-        smtputf8_advertised = TRUE;
-       }
+       if (  accept_8bitmime
+          && verify_check_host(&smtputf8_advertise_hosts) != FAIL)
+         {
+         g = string_catn(g, smtp_code, 3);
+         g = string_catn(g, US"-SMTPUTF8\r\n", 11);
+         fl.smtputf8_advertised = TRUE;
+         }
 #endif
 
-      /* Finish off the multiline reply with one that is always available. */
+       /* Finish off the multiline reply with one that is always available. */
 
-      g = string_catn(g, smtp_code, 3);
-      g = string_catn(g, US" HELP\r\n", 7);
-      }
+       g = string_catn(g, smtp_code, 3);
+       g = string_catn(g, US" HELP\r\n", 7);
+       }
 
-    /* Terminate the string (for debug), write it, and note that HELO/EHLO
-    has been seen. */
+      /* Terminate the string (for debug), write it, and note that HELO/EHLO
+      has been seen. */
 
 #ifdef SUPPORT_TLS
-    if (tls_in.active >= 0) (void)tls_write(TRUE, g->s, g->ptr, FALSE); else
+      if (tls_in.active.sock >= 0) (void)tls_write(NULL, g->s, g->ptr, FALSE); else
 #endif
 
-      {
-      int i = fwrite(g->s, 1, g->ptr, smtp_out); i = i; /* compiler quietening */
-      }
-    DEBUG(D_receive)
-      {
-      uschar *cr;
+       {
+       int i = fwrite(g->s, 1, g->ptr, smtp_out); i = i; /* compiler quietening */
+       }
+      DEBUG(D_receive)
+       {
+       uschar *cr;
 
-      (void) string_from_gstring(g);
-      while ((cr = Ustrchr(g->s, '\r')) != NULL)   /* lose CRs */
-        memmove(cr, cr + 1, (g->ptr--) - (cr - g->s));
-      debug_printf("SMTP>> %s", g->s);
-      }
-    helo_seen = TRUE;
+       (void) string_from_gstring(g);
+       while ((cr = Ustrchr(g->s, '\r')) != NULL)   /* lose CRs */
+         memmove(cr, cr + 1, (g->ptr--) - (cr - g->s));
+       debug_printf("SMTP>> %s", g->s);
+       }
+      fl.helo_seen = TRUE;
 
-    /* Reset the protocol and the state, abandoning any previous message. */
-    received_protocol =
-      (sender_host_address ? protocols : protocols_local)
-       [ (esmtp
-         ? pextend + (sender_host_authenticated ? pauthed : 0)
-         : pnormal)
-       + (tls_in.active >= 0 ? pcrpted : 0)
-       ];
-    cancel_cutthrough_connection(TRUE, US"sent EHLO response");
-    smtp_reset(reset_point);
-    toomany = FALSE;
-    break;   /* HELO/EHLO */
+      /* Reset the protocol and the state, abandoning any previous message. */
+      received_protocol =
+       (sender_host_address ? protocols : protocols_local)
+         [ (fl.esmtp
+           ? pextend + (sender_host_authenticated ? pauthed : 0)
+           : pnormal)
+         + (tls_in.active.sock >= 0 ? pcrpted : 0)
+         ];
+      cancel_cutthrough_connection(TRUE, US"sent EHLO response");
+      smtp_reset(reset_point);
+      toomany = FALSE;
+      break;   /* HELO/EHLO */
 
 
     /* The MAIL command requires an address as an operand. All we do
@@ -4381,407 +4475,445 @@ while (done <= 0)
     it is the canonical extracted address which is all that is kept. */
 
     case MAIL_CMD:
-    HAD(SCH_MAIL);
-    smtp_mailcmd_count++;              /* Count for limit and ratelimit */
-    was_rej_mail = TRUE;               /* Reset if accepted */
-    env_mail_type_t * mail_args;       /* Sanity check & validate args */
+      HAD(SCH_MAIL);
+      smtp_mailcmd_count++;              /* Count for limit and ratelimit */
+      was_rej_mail = TRUE;               /* Reset if accepted */
+      env_mail_type_t * mail_args;       /* Sanity check & validate args */
 
-    if (helo_required && !helo_seen)
-      {
-      smtp_printf("503 HELO or EHLO required\r\n", FALSE);
-      log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
-        "HELO/EHLO given", host_and_ident(FALSE));
-      break;
-      }
+      if (fl.helo_required && !fl.helo_seen)
+       {
+       smtp_printf("503 HELO or EHLO required\r\n", FALSE);
+       log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
+         "HELO/EHLO given", host_and_ident(FALSE));
+       break;
+       }
 
-    if (sender_address != NULL)
-      {
-      done = synprot_error(L_smtp_protocol_error, 503, NULL,
-        US"sender already given");
-      break;
-      }
+      if (sender_address)
+       {
+       done = synprot_error(L_smtp_protocol_error, 503, NULL,
+         US"sender already given");
+       break;
+       }
 
-    if (smtp_cmd_data[0] == 0)
-      {
-      done = synprot_error(L_smtp_protocol_error, 501, NULL,
-        US"MAIL must have an address operand");
-      break;
-      }
+      if (!*smtp_cmd_data)
+       {
+       done = synprot_error(L_smtp_protocol_error, 501, NULL,
+         US"MAIL must have an address operand");
+       break;
+       }
 
-    /* Check to see if the limit for messages per connection would be
-    exceeded by accepting further messages. */
+      /* Check to see if the limit for messages per connection would be
+      exceeded by accepting further messages. */
 
-    if (smtp_accept_max_per_connection > 0 &&
-        smtp_mailcmd_count > smtp_accept_max_per_connection)
-      {
-      smtp_printf("421 too many messages in this connection\r\n", FALSE);
-      log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many "
-        "messages in one connection", host_and_ident(TRUE));
-      break;
-      }
+      if (smtp_accept_max_per_connection > 0 &&
+         smtp_mailcmd_count > smtp_accept_max_per_connection)
+       {
+       smtp_printf("421 too many messages in this connection\r\n", FALSE);
+       log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many "
+         "messages in one connection", host_and_ident(TRUE));
+       break;
+       }
 
-    /* Reset for start of message - even if this is going to fail, we
-    obviously need to throw away any previous data. */
+      /* Reset for start of message - even if this is going to fail, we
+      obviously need to throw away any previous data. */
 
-    cancel_cutthrough_connection(TRUE, US"MAIL received");
-    smtp_reset(reset_point);
-    toomany = FALSE;
-    sender_data = recipient_data = NULL;
+      cancel_cutthrough_connection(TRUE, US"MAIL received");
+      smtp_reset(reset_point);
+      toomany = FALSE;
+      sender_data = recipient_data = NULL;
 
-    /* Loop, checking for ESMTP additions to the MAIL FROM command. */
+      /* Loop, checking for ESMTP additions to the MAIL FROM command. */
 
-    if (esmtp) for(;;)
-      {
-      uschar *name, *value, *end;
-      unsigned long int size;
-      BOOL arg_error = FALSE;
+      if (fl.esmtp) for(;;)
+       {
+       uschar *name, *value, *end;
+       unsigned long int size;
+       BOOL arg_error = FALSE;
 
-      if (!extract_option(&name, &value)) break;
+       if (!extract_option(&name, &value)) break;
 
-      for (mail_args = env_mail_type_list;
-           mail_args->value != ENV_MAIL_OPT_NULL;
-           mail_args++
-          )
-        if (strcmpic(name, mail_args->name) == 0)
-          break;
-      if (mail_args->need_value && strcmpic(value, US"") == 0)
-        break;
-
-      switch(mail_args->value)
-        {
-        /* Handle SIZE= by reading the value. We don't do the check till later,
-        in order to be able to log the sender address on failure. */
-        case ENV_MAIL_OPT_SIZE:
-          if (((size = Ustrtoul(value, &end, 10)), *end == 0))
-            {
-            if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX)
-              size = INT_MAX;
-            message_size = (int)size;
-            }
-          else
-            arg_error = TRUE;
-          break;
+       for (mail_args = env_mail_type_list;
+            mail_args->value != ENV_MAIL_OPT_NULL;
+            mail_args++
+           )
+         if (strcmpic(name, mail_args->name) == 0)
+           break;
+       if (mail_args->need_value && strcmpic(value, US"") == 0)
+         break;
 
-        /* If this session was initiated with EHLO and accept_8bitmime is set,
-        Exim will have indicated that it supports the BODY=8BITMIME option. In
-        fact, it does not support this according to the RFCs, in that it does not
-        take any special action for forwarding messages containing 8-bit
-        characters. That is why accept_8bitmime is not the default setting, but
-        some sites want the action that is provided. We recognize both "8BITMIME"
-        and "7BIT" as body types, but take no action. */
-        case ENV_MAIL_OPT_BODY:
-          if (accept_8bitmime) {
-            if (strcmpic(value, US"8BITMIME") == 0)
-              body_8bitmime = 8;
-            else if (strcmpic(value, US"7BIT") == 0)
-              body_8bitmime = 7;
-            else
+       switch(mail_args->value)
+         {
+         /* Handle SIZE= by reading the value. We don't do the check till later,
+         in order to be able to log the sender address on failure. */
+         case ENV_MAIL_OPT_SIZE:
+           if (((size = Ustrtoul(value, &end, 10)), *end == 0))
              {
-              body_8bitmime = 0;
-              done = synprot_error(L_smtp_syntax_error, 501, NULL,
-                US"invalid data for BODY");
-              goto COMMAND_LOOP;
-              }
-            DEBUG(D_receive) debug_printf("8BITMIME: %d\n", body_8bitmime);
+             if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX)
+               size = INT_MAX;
+             message_size = (int)size;
+             }
+           else
+             arg_error = TRUE;
            break;
-          }
-          arg_error = TRUE;
-          break;
 
-        /* Handle the two DSN options, but only if configured to do so (which
-        will have caused "DSN" to be given in the EHLO response). The code itself
-        is included only if configured in at build time. */
+         /* If this session was initiated with EHLO and accept_8bitmime is set,
+         Exim will have indicated that it supports the BODY=8BITMIME option. In
+         fact, it does not support this according to the RFCs, in that it does not
+         take any special action for forwarding messages containing 8-bit
+         characters. That is why accept_8bitmime is not the default setting, but
+         some sites want the action that is provided. We recognize both "8BITMIME"
+         and "7BIT" as body types, but take no action. */
+         case ENV_MAIL_OPT_BODY:
+           if (accept_8bitmime) {
+             if (strcmpic(value, US"8BITMIME") == 0)
+               body_8bitmime = 8;
+             else if (strcmpic(value, US"7BIT") == 0)
+               body_8bitmime = 7;
+             else
+               {
+               body_8bitmime = 0;
+               done = synprot_error(L_smtp_syntax_error, 501, NULL,
+                 US"invalid data for BODY");
+               goto COMMAND_LOOP;
+               }
+             DEBUG(D_receive) debug_printf("8BITMIME: %d\n", body_8bitmime);
+             break;
+           }
+           arg_error = TRUE;
+           break;
 
-        case ENV_MAIL_OPT_RET:
-          if (dsn_advertised)
-           {
-            /* Check if RET has already been set */
-            if (dsn_ret > 0)
-             {
-              synprot_error(L_smtp_syntax_error, 501, NULL,
-                US"RET can be specified once only");
-              goto COMMAND_LOOP;
-             }
-            dsn_ret = strcmpic(value, US"HDRS") == 0
-             ? dsn_ret_hdrs
-             : strcmpic(value, US"FULL") == 0
-             ? dsn_ret_full
-             : 0;
-            DEBUG(D_receive) debug_printf("DSN_RET: %d\n", dsn_ret);
-            /* Check for invalid invalid value, and exit with error */
-            if (dsn_ret == 0)
+         /* Handle the two DSN options, but only if configured to do so (which
+         will have caused "DSN" to be given in the EHLO response). The code itself
+         is included only if configured in at build time. */
+
+         case ENV_MAIL_OPT_RET:
+           if (fl.dsn_advertised)
              {
-              synprot_error(L_smtp_syntax_error, 501, NULL,
-                US"Value for RET is invalid");
-              goto COMMAND_LOOP;
+             /* Check if RET has already been set */
+             if (dsn_ret > 0)
+               {
+               done = synprot_error(L_smtp_syntax_error, 501, NULL,
+                 US"RET can be specified once only");
+               goto COMMAND_LOOP;
+               }
+             dsn_ret = strcmpic(value, US"HDRS") == 0
+               ? dsn_ret_hdrs
+               : strcmpic(value, US"FULL") == 0
+               ? dsn_ret_full
+               : 0;
+             DEBUG(D_receive) debug_printf("DSN_RET: %d\n", dsn_ret);
+             /* Check for invalid invalid value, and exit with error */
+             if (dsn_ret == 0)
+               {
+               done = synprot_error(L_smtp_syntax_error, 501, NULL,
+                 US"Value for RET is invalid");
+               goto COMMAND_LOOP;
+               }
              }
-           }
-          break;
-        case ENV_MAIL_OPT_ENVID:
-          if (dsn_advertised)
-           {
-            /* Check if the dsn envid has been already set */
-            if (dsn_envid != NULL)
+           break;
+         case ENV_MAIL_OPT_ENVID:
+           if (fl.dsn_advertised)
              {
-              synprot_error(L_smtp_syntax_error, 501, NULL,
-                US"ENVID can be specified once only");
-              goto COMMAND_LOOP;
+             /* Check if the dsn envid has been already set */
+             if (dsn_envid)
+               {
+               done = synprot_error(L_smtp_syntax_error, 501, NULL,
+                 US"ENVID can be specified once only");
+               goto COMMAND_LOOP;
+               }
+             dsn_envid = string_copy(value);
+             DEBUG(D_receive) debug_printf("DSN_ENVID: %s\n", dsn_envid);
              }
-            dsn_envid = string_copy(value);
-            DEBUG(D_receive) debug_printf("DSN_ENVID: %s\n", dsn_envid);
-           }
-          break;
-
-        /* Handle the AUTH extension. If the value given is not "<>" and either
-        the ACL says "yes" or there is no ACL but the sending host is
-        authenticated, we set it up as the authenticated sender. However, if the
-        authenticator set a condition to be tested, we ignore AUTH on MAIL unless
-        the condition is met. The value of AUTH is an xtext, which means that +,
-        = and cntrl chars are coded in hex; however "<>" is unaffected by this
-        coding. */
-        case ENV_MAIL_OPT_AUTH:
-          if (Ustrcmp(value, "<>") != 0)
-            {
-            int rc;
-            uschar *ignore_msg;
+           break;
 
-            if (auth_xtextdecode(value, &authenticated_sender) < 0)
-              {
-              /* Put back terminator overrides for error message */
-              value[-1] = '=';
-              name[-1] = ' ';
-              done = synprot_error(L_smtp_syntax_error, 501, NULL,
-                US"invalid data for AUTH");
-              goto COMMAND_LOOP;
-              }
-            if (!acl_smtp_mailauth)
-              {
-              ignore_msg = US"client not authenticated";
-              rc = sender_host_authenticated ? OK : FAIL;
-              }
-            else
-              {
-              ignore_msg = US"rejected by ACL";
-              rc = acl_check(ACL_WHERE_MAILAUTH, NULL, acl_smtp_mailauth,
-                &user_msg, &log_msg);
-              }
+         /* Handle the AUTH extension. If the value given is not "<>" and either
+         the ACL says "yes" or there is no ACL but the sending host is
+         authenticated, we set it up as the authenticated sender. However, if the
+         authenticator set a condition to be tested, we ignore AUTH on MAIL unless
+         the condition is met. The value of AUTH is an xtext, which means that +,
+         = and cntrl chars are coded in hex; however "<>" is unaffected by this
+         coding. */
+         case ENV_MAIL_OPT_AUTH:
+           if (Ustrcmp(value, "<>") != 0)
+             {
+             int rc;
+             uschar *ignore_msg;
 
-            switch (rc)
-              {
-              case OK:
-               if (authenticated_by == NULL ||
-                   authenticated_by->mail_auth_condition == NULL ||
-                   expand_check_condition(authenticated_by->mail_auth_condition,
-                       authenticated_by->name, US"authenticator"))
-                 break;     /* Accept the AUTH */
-
-               ignore_msg = US"server_mail_auth_condition failed";
-               if (authenticated_id != NULL)
-                 ignore_msg = string_sprintf("%s: authenticated ID=\"%s\"",
-                   ignore_msg, authenticated_id);
-
-              /* Fall through */
-
-              case FAIL:
-               authenticated_sender = NULL;
-               log_write(0, LOG_MAIN, "ignoring AUTH=%s from %s (%s)",
-                 value, host_and_ident(TRUE), ignore_msg);
-               break;
-
-              /* Should only get DEFER or ERROR here. Put back terminator
-              overrides for error message */
-
-              default:
+             if (auth_xtextdecode(value, &authenticated_sender) < 0)
+               {
+               /* Put back terminator overrides for error message */
                value[-1] = '=';
                name[-1] = ' ';
-               (void)smtp_handle_acl_fail(ACL_WHERE_MAILAUTH, rc, user_msg,
-                 log_msg);
+               done = synprot_error(L_smtp_syntax_error, 501, NULL,
+                 US"invalid data for AUTH");
                goto COMMAND_LOOP;
-              }
-            }
-            break;
+               }
+             if (!acl_smtp_mailauth)
+               {
+               ignore_msg = US"client not authenticated";
+               rc = sender_host_authenticated ? OK : FAIL;
+               }
+             else
+               {
+               ignore_msg = US"rejected by ACL";
+               rc = acl_check(ACL_WHERE_MAILAUTH, NULL, acl_smtp_mailauth,
+                 &user_msg, &log_msg);
+               }
+
+             switch (rc)
+               {
+               case OK:
+                 if (authenticated_by == NULL ||
+                     authenticated_by->mail_auth_condition == NULL ||
+                     expand_check_condition(authenticated_by->mail_auth_condition,
+                         authenticated_by->name, US"authenticator"))
+                   break;     /* Accept the AUTH */
+
+                 ignore_msg = US"server_mail_auth_condition failed";
+                 if (authenticated_id != NULL)
+                   ignore_msg = string_sprintf("%s: authenticated ID=\"%s\"",
+                     ignore_msg, authenticated_id);
+
+               /* Fall through */
+
+               case FAIL:
+                 authenticated_sender = NULL;
+                 log_write(0, LOG_MAIN, "ignoring AUTH=%s from %s (%s)",
+                   value, host_and_ident(TRUE), ignore_msg);
+                 break;
+
+               /* Should only get DEFER or ERROR here. Put back terminator
+               overrides for error message */
+
+               default:
+                 value[-1] = '=';
+                 name[-1] = ' ';
+                 (void)smtp_handle_acl_fail(ACL_WHERE_MAILAUTH, rc, user_msg,
+                   log_msg);
+                 goto COMMAND_LOOP;
+               }
+             }
+             break;
 
 #ifndef DISABLE_PRDR
-        case ENV_MAIL_OPT_PRDR:
-          if (prdr_enable)
-            prdr_requested = TRUE;
-          break;
+         case ENV_MAIL_OPT_PRDR:
+           if (prdr_enable)
+             prdr_requested = TRUE;
+           break;
 #endif
 
 #ifdef SUPPORT_I18N
-        case ENV_MAIL_OPT_UTF8:
-         if (smtputf8_advertised)
-           {
-           int old_pool = store_pool;
+         case ENV_MAIL_OPT_UTF8:
+           if (!fl.smtputf8_advertised)
+             {
+             done = synprot_error(L_smtp_syntax_error, 501, NULL,
+               US"SMTPUTF8 used when not advertised");
+             goto COMMAND_LOOP;
+             }
 
            DEBUG(D_receive) debug_printf("smtputf8 requested\n");
            message_smtputf8 = allow_utf8_domains = TRUE;
-           store_pool = POOL_PERM;
-           received_protocol = string_sprintf("utf8%s", received_protocol);
-           store_pool = old_pool;
+           if (Ustrncmp(received_protocol, US"utf8", 4) != 0)
+             {
+             int old_pool = store_pool;
+             store_pool = POOL_PERM;
+             received_protocol = string_sprintf("utf8%s", received_protocol);
+             store_pool = old_pool;
+             }
+           break;
+#endif
+
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+         case ENV_MAIL_OPT_REQTLS:
+           {
+           uschar * r, * t;
+
+           if (!fl.requiretls_advertised)
+             {
+             done = synprot_error(L_smtp_syntax_error, 555, NULL,
+               US"unadvertised MAIL option: REQUIRETLS");
+             goto COMMAND_LOOP;
+             }
+
+           DEBUG(D_receive) debug_printf("requiretls requested\n");
+           tls_requiretls = REQUIRETLS_MSG;
+
+           r = string_copy_malloc(received_protocol);
+           if ((t = Ustrrchr(r, 's'))) *t = 'S';
+           received_protocol = r;
            }
-         break;
+           break;
 #endif
-        /* No valid option. Stick back the terminator characters and break
-        the loop.  Do the name-terminator second as extract_option sets
-        value==name when it found no equal-sign.
-        An error for a malformed address will occur. */
-        case ENV_MAIL_OPT_NULL:
-          value[-1] = '=';
-          name[-1] = ' ';
-          arg_error = TRUE;
-          break;
 
-        default:  assert(0);
-        }
-      /* Break out of for loop if switch() had bad argument or
-         when start of the email address is reached */
-      if (arg_error) break;
-      }
+         /* No valid option. Stick back the terminator characters and break
+         the loop.  Do the name-terminator second as extract_option sets
+         value==name when it found no equal-sign.
+         An error for a malformed address will occur. */
+         case ENV_MAIL_OPT_NULL:
+           value[-1] = '=';
+           name[-1] = ' ';
+           arg_error = TRUE;
+           break;
 
-    /* If we have passed the threshold for rate limiting, apply the current
-    delay, and update it for next time, provided this is a limited host. */
+         default:  assert(0);
+         }
+       /* Break out of for loop if switch() had bad argument or
+          when start of the email address is reached */
+       if (arg_error) break;
+       }
 
-    if (smtp_mailcmd_count > smtp_rlm_threshold &&
-        verify_check_host(&smtp_ratelimit_hosts) == OK)
-      {
-      DEBUG(D_receive) debug_printf("rate limit MAIL: delay %.3g sec\n",
-        smtp_delay_mail/1000.0);
-      millisleep((int)smtp_delay_mail);
-      smtp_delay_mail *= smtp_rlm_factor;
-      if (smtp_delay_mail > (double)smtp_rlm_limit)
-        smtp_delay_mail = (double)smtp_rlm_limit;
-      }
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+      if (tls_requiretls & REQUIRETLS_MSG)
+       {
+       /* Ensure headers-only bounces whether a RET option was given or not. */
 
-    /* Now extract the address, first applying any SMTP-time rewriting. The
-    TRUE flag allows "<>" as a sender address. */
+       DEBUG(D_receive) if (dsn_ret == dsn_ret_full)
+         debug_printf("requiretls override: dsn_ret_full -> dsn_ret_hdrs\n");
+       dsn_ret = dsn_ret_hdrs;
+       }
+#endif
 
-    raw_sender = rewrite_existflags & rewrite_smtp
-      ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
-                   global_rewrite_rules)
-      : smtp_cmd_data;
+      /* If we have passed the threshold for rate limiting, apply the current
+      delay, and update it for next time, provided this is a limited host. */
 
-    raw_sender =
-      parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
-        TRUE);
+      if (smtp_mailcmd_count > smtp_rlm_threshold &&
+         verify_check_host(&smtp_ratelimit_hosts) == OK)
+       {
+       DEBUG(D_receive) debug_printf("rate limit MAIL: delay %.3g sec\n",
+         smtp_delay_mail/1000.0);
+       millisleep((int)smtp_delay_mail);
+       smtp_delay_mail *= smtp_rlm_factor;
+       if (smtp_delay_mail > (double)smtp_rlm_limit)
+         smtp_delay_mail = (double)smtp_rlm_limit;
+       }
 
-    if (!raw_sender)
-      {
-      done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
-      break;
-      }
+      /* Now extract the address, first applying any SMTP-time rewriting. The
+      TRUE flag allows "<>" as a sender address. */
 
-    sender_address = raw_sender;
+      raw_sender = rewrite_existflags & rewrite_smtp
+       ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+                     global_rewrite_rules)
+       : smtp_cmd_data;
 
-    /* If there is a configured size limit for mail, check that this message
-    doesn't exceed it. The check is postponed to this point so that the sender
-    can be logged. */
+      raw_sender =
+       parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
+         TRUE);
 
-    if (thismessage_size_limit > 0 && message_size > thismessage_size_limit)
-      {
-      smtp_printf("552 Message size exceeds maximum permitted\r\n", FALSE);
-      log_write(L_size_reject,
-          LOG_MAIN|LOG_REJECT, "rejected MAIL FROM:<%s> %s: "
-          "message too big: size%s=%d max=%d",
-          sender_address,
-          host_and_ident(TRUE),
-          (message_size == INT_MAX)? ">" : "",
-          message_size,
-          thismessage_size_limit);
-      sender_address = NULL;
-      break;
-      }
+      if (!raw_sender)
+       {
+       done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
+       break;
+       }
 
-    /* Check there is enough space on the disk unless configured not to.
-    When smtp_check_spool_space is set, the check is for thismessage_size_limit
-    plus the current message - i.e. we accept the message only if it won't
-    reduce the space below the threshold. Add 5000 to the size to allow for
-    overheads such as the Received: line and storing of recipients, etc.
-    By putting the check here, even when SIZE is not given, it allow VRFY
-    and EXPN etc. to be used when space is short. */
-
-    if (!receive_check_fs(
-         (smtp_check_spool_space && message_size >= 0)?
-            message_size + 5000 : 0))
-      {
-      smtp_printf("452 Space shortage, please try later\r\n", FALSE);
-      sender_address = NULL;
-      break;
-      }
+      sender_address = raw_sender;
 
-    /* If sender_address is unqualified, reject it, unless this is a locally
-    generated message, or the sending host or net is permitted to send
-    unqualified addresses - typically local machines behaving as MUAs -
-    in which case just qualify the address. The flag is set above at the start
-    of the SMTP connection. */
+      /* If there is a configured size limit for mail, check that this message
+      doesn't exceed it. The check is postponed to this point so that the sender
+      can be logged. */
 
-    if (sender_domain == 0 && sender_address[0] != 0)
-      {
-      if (allow_unqualified_sender)
-        {
-        sender_domain = Ustrlen(sender_address) + 1;
-        sender_address = rewrite_address_qualify(sender_address, FALSE);
-        DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
-          raw_sender);
-        }
-      else
-        {
-        smtp_printf("501 %s: sender address must contain a domain\r\n", FALSE,
-          smtp_cmd_data);
-        log_write(L_smtp_syntax_error,
-          LOG_MAIN|LOG_REJECT,
-          "unqualified sender rejected: <%s> %s%s",
-          raw_sender,
-          host_and_ident(TRUE),
-          host_lookup_msg);
-        sender_address = NULL;
-        break;
-        }
-      }
+      if (thismessage_size_limit > 0 && message_size > thismessage_size_limit)
+       {
+       smtp_printf("552 Message size exceeds maximum permitted\r\n", FALSE);
+       log_write(L_size_reject,
+           LOG_MAIN|LOG_REJECT, "rejected MAIL FROM:<%s> %s: "
+           "message too big: size%s=%d max=%d",
+           sender_address,
+           host_and_ident(TRUE),
+           (message_size == INT_MAX)? ">" : "",
+           message_size,
+           thismessage_size_limit);
+       sender_address = NULL;
+       break;
+       }
 
-    /* Apply an ACL check if one is defined, before responding. Afterwards,
-    when pipelining is not advertised, do another sync check in case the ACL
-    delayed and the client started sending in the meantime. */
+      /* Check there is enough space on the disk unless configured not to.
+      When smtp_check_spool_space is set, the check is for thismessage_size_limit
+      plus the current message - i.e. we accept the message only if it won't
+      reduce the space below the threshold. Add 5000 to the size to allow for
+      overheads such as the Received: line and storing of recipients, etc.
+      By putting the check here, even when SIZE is not given, it allow VRFY
+      and EXPN etc. to be used when space is short. */
+
+      if (!receive_check_fs(
+          (smtp_check_spool_space && message_size >= 0)?
+             message_size + 5000 : 0))
+       {
+       smtp_printf("452 Space shortage, please try later\r\n", FALSE);
+       sender_address = NULL;
+       break;
+       }
 
-    if (acl_smtp_mail)
-      {
-      rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
-      if (rc == OK && !pipelining_advertised && !check_sync())
-        goto SYNC_FAILURE;
-      }
-    else
-      rc = OK;
+      /* If sender_address is unqualified, reject it, unless this is a locally
+      generated message, or the sending host or net is permitted to send
+      unqualified addresses - typically local machines behaving as MUAs -
+      in which case just qualify the address. The flag is set above at the start
+      of the SMTP connection. */
 
-    if (rc == OK || rc == DISCARD)
-      {
-      BOOL more = pipeline_response();
+      if (!sender_domain && *sender_address)
+       if (f.allow_unqualified_sender)
+         {
+         sender_domain = Ustrlen(sender_address) + 1;
+         sender_address = rewrite_address_qualify(sender_address, FALSE);
+         DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
+           raw_sender);
+         }
+       else
+         {
+         smtp_printf("501 %s: sender address must contain a domain\r\n", FALSE,
+           smtp_cmd_data);
+         log_write(L_smtp_syntax_error,
+           LOG_MAIN|LOG_REJECT,
+           "unqualified sender rejected: <%s> %s%s",
+           raw_sender,
+           host_and_ident(TRUE),
+           host_lookup_msg);
+         sender_address = NULL;
+         break;
+         }
 
-      if (!user_msg)
-        smtp_printf("%s%s%s", more, US"250 OK",
-                  #ifndef DISABLE_PRDR
-                    prdr_requested ? US", PRDR Requested" : US"",
-                 #else
-                    US"",
-                  #endif
-                    US"\r\n");
+      /* Apply an ACL check if one is defined, before responding. Afterwards,
+      when pipelining is not advertised, do another sync check in case the ACL
+      delayed and the client started sending in the meantime. */
+
+      if (acl_smtp_mail)
+       {
+       rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
+       if (rc == OK && !f.smtp_in_pipelining_advertised && !check_sync())
+         goto SYNC_FAILURE;
+       }
       else
-        {
-      #ifndef DISABLE_PRDR
-        if (prdr_requested)
-           user_msg = string_sprintf("%s%s", user_msg, US", PRDR Requested");
-      #endif
-        smtp_user_msg(US"250", user_msg);
-        }
-      smtp_delay_rcpt = smtp_rlr_base;
-      recipients_discarded = (rc == DISCARD);
-      was_rej_mail = FALSE;
-      }
-    else
-      {
-      done = smtp_handle_acl_fail(ACL_WHERE_MAIL, rc, user_msg, log_msg);
-      sender_address = NULL;
-      }
-    break;
+       rc = OK;
+
+      if (rc == OK || rc == DISCARD)
+       {
+       BOOL more = pipeline_response();
+
+       if (!user_msg)
+         smtp_printf("%s%s%s", more, US"250 OK",
+                   #ifndef DISABLE_PRDR
+                     prdr_requested ? US", PRDR Requested" : US"",
+                   #else
+                     US"",
+                   #endif
+                     US"\r\n");
+       else
+         {
+       #ifndef DISABLE_PRDR
+         if (prdr_requested)
+            user_msg = string_sprintf("%s%s", user_msg, US", PRDR Requested");
+       #endif
+         smtp_user_msg(US"250", user_msg);
+         }
+       smtp_delay_rcpt = smtp_rlr_base;
+       f.recipients_discarded = (rc == DISCARD);
+       was_rej_mail = FALSE;
+       }
+      else
+       {
+       done = smtp_handle_acl_fail(ACL_WHERE_MAIL, rc, user_msg, log_msg);
+       sender_address = NULL;
+       }
+      break;
 
 
     /* The RCPT command requires an address as an operand. There may be any
@@ -4790,259 +4922,259 @@ while (done <= 0)
     are not used, as we keep only the extracted address. */
 
     case RCPT_CMD:
-    HAD(SCH_RCPT);
-    rcpt_count++;
-    was_rcpt = rcpt_in_progress = TRUE;
+      HAD(SCH_RCPT);
+      rcpt_count++;
+      was_rcpt = fl.rcpt_in_progress = TRUE;
 
-    /* There must be a sender address; if the sender was rejected and
-    pipelining was advertised, we assume the client was pipelining, and do not
-    count this as a protocol error. Reset was_rej_mail so that further RCPTs
-    get the same treatment. */
+      /* There must be a sender address; if the sender was rejected and
+      pipelining was advertised, we assume the client was pipelining, and do not
+      count this as a protocol error. Reset was_rej_mail so that further RCPTs
+      get the same treatment. */
 
-    if (sender_address == NULL)
-      {
-      if (pipelining_advertised && last_was_rej_mail)
-        {
-        smtp_printf("503 sender not yet given\r\n", FALSE);
-        was_rej_mail = TRUE;
-        }
-      else
-        {
-        done = synprot_error(L_smtp_protocol_error, 503, NULL,
-          US"sender not yet given");
-        was_rcpt = FALSE;             /* Not a valid RCPT */
-        }
-      rcpt_fail_count++;
-      break;
-      }
+      if (sender_address == NULL)
+       {
+       if (f.smtp_in_pipelining_advertised && last_was_rej_mail)
+         {
+         smtp_printf("503 sender not yet given\r\n", FALSE);
+         was_rej_mail = TRUE;
+         }
+       else
+         {
+         done = synprot_error(L_smtp_protocol_error, 503, NULL,
+           US"sender not yet given");
+         was_rcpt = FALSE;             /* Not a valid RCPT */
+         }
+       rcpt_fail_count++;
+       break;
+       }
 
-    /* Check for an operand */
+      /* Check for an operand */
 
-    if (smtp_cmd_data[0] == 0)
-      {
-      done = synprot_error(L_smtp_syntax_error, 501, NULL,
-        US"RCPT must have an address operand");
-      rcpt_fail_count++;
-      break;
-      }
+      if (smtp_cmd_data[0] == 0)
+       {
+       done = synprot_error(L_smtp_syntax_error, 501, NULL,
+         US"RCPT must have an address operand");
+       rcpt_fail_count++;
+       break;
+       }
 
-    /* Set the DSN flags orcpt and dsn_flags from the session*/
-    orcpt = NULL;
-    flags = 0;
+      /* Set the DSN flags orcpt and dsn_flags from the session*/
+      orcpt = NULL;
+      flags = 0;
 
-    if (esmtp) for(;;)
-      {
-      uschar *name, *value;
+      if (fl.esmtp) for(;;)
+       {
+       uschar *name, *value;
 
-      if (!extract_option(&name, &value))
-        break;
+       if (!extract_option(&name, &value))
+         break;
 
-      if (dsn_advertised && strcmpic(name, US"ORCPT") == 0)
-        {
-        /* Check whether orcpt has been already set */
-        if (orcpt)
+       if (fl.dsn_advertised && strcmpic(name, US"ORCPT") == 0)
          {
-          synprot_error(L_smtp_syntax_error, 501, NULL,
-            US"ORCPT can be specified once only");
-          goto COMMAND_LOOP;
-          }
-        orcpt = string_copy(value);
-        DEBUG(D_receive) debug_printf("DSN orcpt: %s\n", orcpt);
-        }
+         /* Check whether orcpt has been already set */
+         if (orcpt)
+           {
+           done = synprot_error(L_smtp_syntax_error, 501, NULL,
+             US"ORCPT can be specified once only");
+           goto COMMAND_LOOP;
+           }
+         orcpt = string_copy(value);
+         DEBUG(D_receive) debug_printf("DSN orcpt: %s\n", orcpt);
+         }
 
-      else if (dsn_advertised && strcmpic(name, US"NOTIFY") == 0)
-        {
-        /* Check if the notify flags have been already set */
-        if (flags > 0)
+       else if (fl.dsn_advertised && strcmpic(name, US"NOTIFY") == 0)
          {
-          synprot_error(L_smtp_syntax_error, 501, NULL,
-              US"NOTIFY can be specified once only");
-          goto COMMAND_LOOP;
-          }
-        if (strcmpic(value, US"NEVER") == 0)
-         flags |= rf_notify_never;
-       else
-          {
-          uschar *p = value;
-          while (*p != 0)
-            {
-            uschar *pp = p;
-            while (*pp != 0 && *pp != ',') pp++;
-           if (*pp == ',') *pp++ = 0;
-            if (strcmpic(p, US"SUCCESS") == 0)
-             {
-             DEBUG(D_receive) debug_printf("DSN: Setting notify success\n");
-             flags |= rf_notify_success;
-              }
-            else if (strcmpic(p, US"FAILURE") == 0)
-             {
-             DEBUG(D_receive) debug_printf("DSN: Setting notify failure\n");
-             flags |= rf_notify_failure;
-              }
-            else if (strcmpic(p, US"DELAY") == 0)
-             {
-             DEBUG(D_receive) debug_printf("DSN: Setting notify delay\n");
-             flags |= rf_notify_delay;
-              }
-            else
+         /* Check if the notify flags have been already set */
+         if (flags > 0)
+           {
+           done = synprot_error(L_smtp_syntax_error, 501, NULL,
+               US"NOTIFY can be specified once only");
+           goto COMMAND_LOOP;
+           }
+         if (strcmpic(value, US"NEVER") == 0)
+           flags |= rf_notify_never;
+         else
+           {
+           uschar *p = value;
+           while (*p != 0)
              {
-              /* Catch any strange values */
-              synprot_error(L_smtp_syntax_error, 501, NULL,
-                US"Invalid value for NOTIFY parameter");
-              goto COMMAND_LOOP;
-              }
-            p = pp;
-            }
-            DEBUG(D_receive) debug_printf("DSN Flags: %x\n", flags);
-          }
-        }
+             uschar *pp = p;
+             while (*pp != 0 && *pp != ',') pp++;
+             if (*pp == ',') *pp++ = 0;
+             if (strcmpic(p, US"SUCCESS") == 0)
+               {
+               DEBUG(D_receive) debug_printf("DSN: Setting notify success\n");
+               flags |= rf_notify_success;
+               }
+             else if (strcmpic(p, US"FAILURE") == 0)
+               {
+               DEBUG(D_receive) debug_printf("DSN: Setting notify failure\n");
+               flags |= rf_notify_failure;
+               }
+             else if (strcmpic(p, US"DELAY") == 0)
+               {
+               DEBUG(D_receive) debug_printf("DSN: Setting notify delay\n");
+               flags |= rf_notify_delay;
+               }
+             else
+               {
+               /* Catch any strange values */
+               done = synprot_error(L_smtp_syntax_error, 501, NULL,
+                 US"Invalid value for NOTIFY parameter");
+               goto COMMAND_LOOP;
+               }
+             p = pp;
+             }
+             DEBUG(D_receive) debug_printf("DSN Flags: %x\n", flags);
+           }
+         }
 
-      /* Unknown option. Stick back the terminator characters and break
-      the loop. An error for a malformed address will occur. */
+       /* Unknown option. Stick back the terminator characters and break
+       the loop. An error for a malformed address will occur. */
 
-      else
-        {
-        DEBUG(D_receive) debug_printf("Invalid RCPT option: %s : %s\n", name, value);
-        name[-1] = ' ';
-        value[-1] = '=';
-        break;
-        }
-      }
+       else
+         {
+         DEBUG(D_receive) debug_printf("Invalid RCPT option: %s : %s\n", name, value);
+         name[-1] = ' ';
+         value[-1] = '=';
+         break;
+         }
+       }
 
-    /* Apply SMTP rewriting then extract the working address. Don't allow "<>"
-    as a recipient address */
+      /* Apply SMTP rewriting then extract the working address. Don't allow "<>"
+      as a recipient address */
 
-    recipient = rewrite_existflags & rewrite_smtp
-      ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
-         global_rewrite_rules)
-      : smtp_cmd_data;
+      recipient = rewrite_existflags & rewrite_smtp
+       ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+           global_rewrite_rules)
+       : smtp_cmd_data;
 
-    if (!(recipient = parse_extract_address(recipient, &errmess, &start, &end,
-      &recipient_domain, FALSE)))
-      {
-      done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
-      rcpt_fail_count++;
-      break;
-      }
+      if (!(recipient = parse_extract_address(recipient, &errmess, &start, &end,
+       &recipient_domain, FALSE)))
+       {
+       done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
+       rcpt_fail_count++;
+       break;
+       }
 
-    /* If the recipient address is unqualified, reject it, unless this is a
-    locally generated message. However, unqualified addresses are permitted
-    from a configured list of hosts and nets - typically when behaving as
-    MUAs rather than MTAs. Sad that SMTP is used for both types of traffic,
-    really. The flag is set at the start of the SMTP connection.
+      /* If the recipient address is unqualified, reject it, unless this is a
+      locally generated message. However, unqualified addresses are permitted
+      from a configured list of hosts and nets - typically when behaving as
+      MUAs rather than MTAs. Sad that SMTP is used for both types of traffic,
+      really. The flag is set at the start of the SMTP connection.
 
-    RFC 1123 talks about supporting "the reserved mailbox postmaster"; I always
-    assumed this meant "reserved local part", but the revision of RFC 821 and
-    friends now makes it absolutely clear that it means *mailbox*. Consequently
-    we must always qualify this address, regardless. */
+      RFC 1123 talks about supporting "the reserved mailbox postmaster"; I always
+      assumed this meant "reserved local part", but the revision of RFC 821 and
+      friends now makes it absolutely clear that it means *mailbox*. Consequently
+      we must always qualify this address, regardless. */
 
-    if (!recipient_domain)
-      if (!(recipient_domain = qualify_recipient(&recipient, smtp_cmd_data,
-                                 US"recipient")))
-        {
-        rcpt_fail_count++;
-        break;
-        }
+      if (!recipient_domain)
+       if (!(recipient_domain = qualify_recipient(&recipient, smtp_cmd_data,
+                                   US"recipient")))
+         {
+         rcpt_fail_count++;
+         break;
+         }
 
-    /* Check maximum allowed */
+      /* Check maximum allowed */
 
-    if (rcpt_count > recipients_max && recipients_max > 0)
-      {
-      if (recipients_max_reject)
-        {
-        rcpt_fail_count++;
-        smtp_printf("552 too many recipients\r\n", FALSE);
-        if (!toomany)
-          log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: message "
-            "rejected: sender=<%s> %s", sender_address, host_and_ident(TRUE));
-        }
-      else
-        {
-        rcpt_defer_count++;
-        smtp_printf("452 too many recipients\r\n", FALSE);
-        if (!toomany)
-          log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: excess "
-            "temporarily rejected: sender=<%s> %s", sender_address,
-            host_and_ident(TRUE));
-        }
+      if (rcpt_count > recipients_max && recipients_max > 0)
+       {
+       if (recipients_max_reject)
+         {
+         rcpt_fail_count++;
+         smtp_printf("552 too many recipients\r\n", FALSE);
+         if (!toomany)
+           log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: message "
+             "rejected: sender=<%s> %s", sender_address, host_and_ident(TRUE));
+         }
+       else
+         {
+         rcpt_defer_count++;
+         smtp_printf("452 too many recipients\r\n", FALSE);
+         if (!toomany)
+           log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: excess "
+             "temporarily rejected: sender=<%s> %s", sender_address,
+             host_and_ident(TRUE));
+         }
 
-      toomany = TRUE;
-      break;
-      }
+       toomany = TRUE;
+       break;
+       }
 
-    /* If we have passed the threshold for rate limiting, apply the current
-    delay, and update it for next time, provided this is a limited host. */
+      /* If we have passed the threshold for rate limiting, apply the current
+      delay, and update it for next time, provided this is a limited host. */
 
-    if (rcpt_count > smtp_rlr_threshold &&
-        verify_check_host(&smtp_ratelimit_hosts) == OK)
-      {
-      DEBUG(D_receive) debug_printf("rate limit RCPT: delay %.3g sec\n",
-        smtp_delay_rcpt/1000.0);
-      millisleep((int)smtp_delay_rcpt);
-      smtp_delay_rcpt *= smtp_rlr_factor;
-      if (smtp_delay_rcpt > (double)smtp_rlr_limit)
-        smtp_delay_rcpt = (double)smtp_rlr_limit;
-      }
+      if (rcpt_count > smtp_rlr_threshold &&
+         verify_check_host(&smtp_ratelimit_hosts) == OK)
+       {
+       DEBUG(D_receive) debug_printf("rate limit RCPT: delay %.3g sec\n",
+         smtp_delay_rcpt/1000.0);
+       millisleep((int)smtp_delay_rcpt);
+       smtp_delay_rcpt *= smtp_rlr_factor;
+       if (smtp_delay_rcpt > (double)smtp_rlr_limit)
+         smtp_delay_rcpt = (double)smtp_rlr_limit;
+       }
 
-    /* If the MAIL ACL discarded all the recipients, we bypass ACL checking
-    for them. Otherwise, check the access control list for this recipient. As
-    there may be a delay in this, re-check for a synchronization error
-    afterwards, unless pipelining was advertised. */
+      /* If the MAIL ACL discarded all the recipients, we bypass ACL checking
+      for them. Otherwise, check the access control list for this recipient. As
+      there may be a delay in this, re-check for a synchronization error
+      afterwards, unless pipelining was advertised. */
 
-    if (recipients_discarded)
-      rc = DISCARD;
-    else
-      if (  (rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg,
-                   &log_msg)) == OK
-        && !pipelining_advertised && !check_sync())
-        goto SYNC_FAILURE;
+      if (f.recipients_discarded)
+       rc = DISCARD;
+      else
+       if (  (rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg,
+                     &log_msg)) == OK
+          && !f.smtp_in_pipelining_advertised && !check_sync())
+         goto SYNC_FAILURE;
 
-    /* The ACL was happy */
+      /* The ACL was happy */
 
-    if (rc == OK)
-      {
-      BOOL more = pipeline_response();
+      if (rc == OK)
+       {
+       BOOL more = pipeline_response();
 
-      if (user_msg)
-        smtp_user_msg(US"250", user_msg);
-      else
-        smtp_printf("250 Accepted\r\n", more);
-      receive_add_recipient(recipient, -1);
+       if (user_msg)
+         smtp_user_msg(US"250", user_msg);
+       else
+         smtp_printf("250 Accepted\r\n", more);
+       receive_add_recipient(recipient, -1);
 
-      /* Set the dsn flags in the recipients_list */
-      recipients_list[recipients_count-1].orcpt = orcpt;
-      recipients_list[recipients_count-1].dsn_flags = flags;
+       /* Set the dsn flags in the recipients_list */
+       recipients_list[recipients_count-1].orcpt = orcpt;
+       recipients_list[recipients_count-1].dsn_flags = flags;
 
-      DEBUG(D_receive) debug_printf("DSN: orcpt: %s  flags: %d\n",
-       recipients_list[recipients_count-1].orcpt,
-       recipients_list[recipients_count-1].dsn_flags);
-      }
+       DEBUG(D_receive) debug_printf("DSN: orcpt: %s  flags: %d\n",
+         recipients_list[recipients_count-1].orcpt,
+         recipients_list[recipients_count-1].dsn_flags);
+       }
 
-    /* The recipient was discarded */
+      /* The recipient was discarded */
 
-    else if (rc == DISCARD)
-      {
-      if (user_msg)
-        smtp_user_msg(US"250", user_msg);
-      else
-        smtp_printf("250 Accepted\r\n", FALSE);
-      rcpt_fail_count++;
-      discarded = TRUE;
-      log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: "
-        "discarded by %s ACL%s%s", host_and_ident(TRUE),
-        sender_address_unrewritten? sender_address_unrewritten : sender_address,
-        smtp_cmd_argument, recipients_discarded? "MAIL" : "RCPT",
-        log_msg ? US": " : US"", log_msg ? log_msg : US"");
-      }
+      else if (rc == DISCARD)
+       {
+       if (user_msg)
+         smtp_user_msg(US"250", user_msg);
+       else
+         smtp_printf("250 Accepted\r\n", FALSE);
+       rcpt_fail_count++;
+       discarded = TRUE;
+       log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: "
+         "discarded by %s ACL%s%s", host_and_ident(TRUE),
+         sender_address_unrewritten? sender_address_unrewritten : sender_address,
+         smtp_cmd_argument, f.recipients_discarded? "MAIL" : "RCPT",
+         log_msg ? US": " : US"", log_msg ? log_msg : US"");
+       }
 
-    /* Either the ACL failed the address, or it was deferred. */
+      /* Either the ACL failed the address, or it was deferred. */
 
-    else
-      {
-      if (rc == FAIL) rcpt_fail_count++; else rcpt_defer_count++;
-      done = smtp_handle_acl_fail(ACL_WHERE_RCPT, rc, user_msg, log_msg);
-      }
-    break;
+      else
+       {
+       if (rc == FAIL) rcpt_fail_count++; else rcpt_defer_count++;
+       done = smtp_handle_acl_fail(ACL_WHERE_RCPT, rc, user_msg, log_msg);
+       }
+      break;
 
 
     /* The DATA command is legal only if it follows successful MAIL FROM
@@ -5066,10 +5198,10 @@ while (done <= 0)
     with the DATA rejection (an idea suggested by Tony Finch). */
 
     case BDAT_CMD:
-    HAD(SCH_BDAT);
       {
       int n;
 
+      HAD(SCH_BDAT);
       if (chunking_state != CHUNKING_OFFERED)
        {
        done = synprot_error(L_smtp_protocol_error, 503, NULL,
@@ -5101,94 +5233,94 @@ while (done <= 0)
       receive_getc = bdat_getc;
       receive_ungetc = bdat_ungetc;
 
-      dot_ends = FALSE;
+      f.dot_ends = FALSE;
 
       goto DATA_BDAT;
       }
 
     case DATA_CMD:
-    HAD(SCH_DATA);
-    dot_ends = TRUE;
-
-    DATA_BDAT:         /* Common code for DATA and BDAT */
-    if (!discarded && recipients_count <= 0)
-      {
-      if (rcpt_smtp_response_same && rcpt_smtp_response != NULL)
-        {
-        uschar *code = US"503";
-        int len = Ustrlen(rcpt_smtp_response);
-        smtp_respond(code, 3, FALSE, US"All RCPT commands were rejected with "
-          "this error:");
-        /* Responses from smtp_printf() will have \r\n on the end */
-        if (len > 2 && rcpt_smtp_response[len-2] == '\r')
-          rcpt_smtp_response[len-2] = 0;
-        smtp_respond(code, 3, FALSE, rcpt_smtp_response);
-        }
-      if (pipelining_advertised && last_was_rcpt)
-        smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE,
-         smtp_names[smtp_connection_had[smtp_ch_index-1]]);
-      else
-        done = synprot_error(L_smtp_protocol_error, 503, NULL,
-         smtp_connection_had[smtp_ch_index-1] == SCH_DATA
-         ? US"valid RCPT command must precede DATA"
-         : US"valid RCPT command must precede BDAT");
+      HAD(SCH_DATA);
+      f.dot_ends = TRUE;
 
-      if (chunking_state > CHUNKING_OFFERED)
-       bdat_flush_data();
-      break;
-      }
+      DATA_BDAT:               /* Common code for DATA and BDAT */
+      if (!discarded && recipients_count <= 0)
+       {
+       if (fl.rcpt_smtp_response_same && rcpt_smtp_response != NULL)
+         {
+         uschar *code = US"503";
+         int len = Ustrlen(rcpt_smtp_response);
+         smtp_respond(code, 3, FALSE, US"All RCPT commands were rejected with "
+           "this error:");
+         /* Responses from smtp_printf() will have \r\n on the end */
+         if (len > 2 && rcpt_smtp_response[len-2] == '\r')
+           rcpt_smtp_response[len-2] = 0;
+         smtp_respond(code, 3, FALSE, rcpt_smtp_response);
+         }
+       if (f.smtp_in_pipelining_advertised && last_was_rcpt)
+         smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE,
+           smtp_names[smtp_connection_had[smtp_ch_index-1]]);
+       else
+         done = synprot_error(L_smtp_protocol_error, 503, NULL,
+           smtp_connection_had[smtp_ch_index-1] == SCH_DATA
+           ? US"valid RCPT command must precede DATA"
+           : US"valid RCPT command must precede BDAT");
 
-    if (toomany && recipients_max_reject)
-      {
-      sender_address = NULL;  /* This will allow a new MAIL without RSET */
-      sender_address_unrewritten = NULL;
-      smtp_printf("554 Too many recipients\r\n", FALSE);
-      break;
-      }
+       if (chunking_state > CHUNKING_OFFERED)
+         bdat_flush_data();
+       break;
+       }
 
-    if (chunking_state > CHUNKING_OFFERED)
-      rc = OK;                 /* No predata ACL or go-ahead output for BDAT */
-    else
-      {
-      /* If there is an ACL, re-check the synchronization afterwards, since the
-      ACL may have delayed.  To handle cutthrough delivery enforce a dummy call
-      to get the DATA command sent. */
+      if (toomany && recipients_max_reject)
+       {
+       sender_address = NULL;  /* This will allow a new MAIL without RSET */
+       sender_address_unrewritten = NULL;
+       smtp_printf("554 Too many recipients\r\n", FALSE);
+       break;
+       }
 
-      if (acl_smtp_predata == NULL && cutthrough.fd < 0)
-       rc = OK;
+      if (chunking_state > CHUNKING_OFFERED)
+       rc = OK;                        /* No predata ACL or go-ahead output for BDAT */
       else
        {
-       uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept";
-       enable_dollar_recipients = TRUE;
-       rc = acl_check(ACL_WHERE_PREDATA, NULL, acl, &user_msg,
-         &log_msg);
-       enable_dollar_recipients = FALSE;
-       if (rc == OK && !check_sync())
-         goto SYNC_FAILURE;
+       /* If there is an ACL, re-check the synchronization afterwards, since the
+       ACL may have delayed.  To handle cutthrough delivery enforce a dummy call
+       to get the DATA command sent. */
 
-       if (rc != OK)
-         {     /* Either the ACL failed the address, or it was deferred. */
-         done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg);
-         break;
+       if (acl_smtp_predata == NULL && cutthrough.cctx.sock < 0)
+         rc = OK;
+       else
+         {
+         uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept";
+         f.enable_dollar_recipients = TRUE;
+         rc = acl_check(ACL_WHERE_PREDATA, NULL, acl, &user_msg,
+           &log_msg);
+         f.enable_dollar_recipients = FALSE;
+         if (rc == OK && !check_sync())
+           goto SYNC_FAILURE;
+
+         if (rc != OK)
+           {   /* Either the ACL failed the address, or it was deferred. */
+           done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg);
+           break;
+           }
          }
-       }
 
-      if (user_msg)
-       smtp_user_msg(US"354", user_msg);
-      else
-       smtp_printf(
-         "354 Enter message, ending with \".\" on a line by itself\r\n", FALSE);
-      }
+       if (user_msg)
+         smtp_user_msg(US"354", user_msg);
+       else
+         smtp_printf(
+           "354 Enter message, ending with \".\" on a line by itself\r\n", FALSE);
+       }
 
 #ifdef TCP_QUICKACK
-    if (smtp_in)       /* all ACKs needed to ramp window up for bulk data */
-      (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
-             US &on, sizeof(on));
+      if (smtp_in)     /* all ACKs needed to ramp window up for bulk data */
+       (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
+               US &on, sizeof(on));
 #endif
-    done = 3;
-    message_ended = END_NOTENDED;   /* Indicate in middle of data */
+      done = 3;
+      message_ended = END_NOTENDED;   /* Indicate in middle of data */
 
-    break;
+      break;
 
 
     case VRFY_CMD:
@@ -5246,173 +5378,173 @@ while (done <= 0)
 
 
     case EXPN_CMD:
-    HAD(SCH_EXPN);
-    rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg);
-    if (rc != OK)
-      done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg);
-    else
-      {
-      BOOL save_log_testing_mode = log_testing_mode;
-      address_test_mode = log_testing_mode = TRUE;
-      (void) verify_address(deliver_make_addr(smtp_cmd_data, FALSE),
-        smtp_out, vopt_is_recipient | vopt_qualify | vopt_expn, -1, -1, -1,
-        NULL, NULL, NULL);
-      address_test_mode = FALSE;
-      log_testing_mode = save_log_testing_mode;    /* true for -bh */
-      }
-    break;
+      HAD(SCH_EXPN);
+      rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg);
+      if (rc != OK)
+       done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg);
+      else
+       {
+       BOOL save_log_testing_mode = f.log_testing_mode;
+       f.address_test_mode = f.log_testing_mode = TRUE;
+       (void) verify_address(deliver_make_addr(smtp_cmd_data, FALSE),
+         smtp_out, vopt_is_recipient | vopt_qualify | vopt_expn, -1, -1, -1,
+         NULL, NULL, NULL);
+       f.address_test_mode = FALSE;
+       f.log_testing_mode = save_log_testing_mode;    /* true for -bh */
+       }
+      break;
 
 
     #ifdef SUPPORT_TLS
 
     case STARTTLS_CMD:
-    HAD(SCH_STARTTLS);
-    if (!tls_advertised)
-      {
-      done = synprot_error(L_smtp_protocol_error, 503, NULL,
-        US"STARTTLS command used when not advertised");
-      break;
-      }
+      HAD(SCH_STARTTLS);
+      if (!fl.tls_advertised)
+       {
+       done = synprot_error(L_smtp_protocol_error, 503, NULL,
+         US"STARTTLS command used when not advertised");
+       break;
+       }
 
-    /* Apply an ACL check if one is defined */
+      /* Apply an ACL check if one is defined */
 
-    if (  acl_smtp_starttls
-       && (rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls,
-                 &user_msg, &log_msg)) != OK
-       )
-      {
-      done = smtp_handle_acl_fail(ACL_WHERE_STARTTLS, rc, user_msg, log_msg);
-      break;
-      }
+      if (  acl_smtp_starttls
+        && (rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls,
+                   &user_msg, &log_msg)) != OK
+        )
+       {
+       done = smtp_handle_acl_fail(ACL_WHERE_STARTTLS, rc, user_msg, log_msg);
+       break;
+       }
 
-    /* RFC 2487 is not clear on when this command may be sent, though it
-    does state that all information previously obtained from the client
-    must be discarded if a TLS session is started. It seems reasonable to
-    do an implied RSET when STARTTLS is received. */
-
-    incomplete_transaction_log(US"STARTTLS");
-    cancel_cutthrough_connection(TRUE, US"STARTTLS received");
-    smtp_reset(reset_point);
-    toomany = FALSE;
-    cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE;
-
-    /* There's an attack where more data is read in past the STARTTLS command
-    before TLS is negotiated, then assumed to be part of the secure session
-    when used afterwards; we use segregated input buffers, so are not
-    vulnerable, but we want to note when it happens and, for sheer paranoia,
-    ensure that the buffer is "wiped".
-    Pipelining sync checks will normally have protected us too, unless disabled
-    by configuration. */
-
-    if (receive_smtp_buffered())
-      {
-      DEBUG(D_any)
-        debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n");
-      if (tls_in.active < 0)
-        smtp_inend = smtp_inptr = smtp_inbuffer;
-      /* and if TLS is already active, tls_server_start() should fail */
-      }
+      /* RFC 2487 is not clear on when this command may be sent, though it
+      does state that all information previously obtained from the client
+      must be discarded if a TLS session is started. It seems reasonable to
+      do an implied RSET when STARTTLS is received. */
 
-    /* There is nothing we value in the input buffer and if TLS is successfully
-    negotiated, we won't use this buffer again; if TLS fails, we'll just read
-    fresh content into it.  The buffer contains arbitrary content from an
-    untrusted remote source; eg: NOOP <shellcode>\r\nSTARTTLS\r\n
-    It seems safest to just wipe away the content rather than leave it as a
-    target to jump to. */
+      incomplete_transaction_log(US"STARTTLS");
+      cancel_cutthrough_connection(TRUE, US"STARTTLS received");
+      smtp_reset(reset_point);
+      toomany = FALSE;
+      cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE;
+
+      /* There's an attack where more data is read in past the STARTTLS command
+      before TLS is negotiated, then assumed to be part of the secure session
+      when used afterwards; we use segregated input buffers, so are not
+      vulnerable, but we want to note when it happens and, for sheer paranoia,
+      ensure that the buffer is "wiped".
+      Pipelining sync checks will normally have protected us too, unless disabled
+      by configuration. */
+
+      if (receive_smtp_buffered())
+       {
+       DEBUG(D_any)
+         debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n");
+       if (tls_in.active.sock < 0)
+         smtp_inend = smtp_inptr = smtp_inbuffer;
+       /* and if TLS is already active, tls_server_start() should fail */
+       }
 
-    memset(smtp_inbuffer, 0, IN_BUFFER_SIZE);
+      /* There is nothing we value in the input buffer and if TLS is successfully
+      negotiated, we won't use this buffer again; if TLS fails, we'll just read
+      fresh content into it.  The buffer contains arbitrary content from an
+      untrusted remote source; eg: NOOP <shellcode>\r\nSTARTTLS\r\n
+      It seems safest to just wipe away the content rather than leave it as a
+      target to jump to. */
 
-    /* Attempt to start up a TLS session, and if successful, discard all
-    knowledge that was obtained previously. At least, that's what the RFC says,
-    and that's what happens by default. However, in order to work round YAEB,
-    there is an option to remember the esmtp state. Sigh.
+      memset(smtp_inbuffer, 0, IN_BUFFER_SIZE);
 
-    We must allow for an extra EHLO command and an extra AUTH command after
-    STARTTLS that don't add to the nonmail command count. */
+      /* Attempt to start up a TLS session, and if successful, discard all
+      knowledge that was obtained previously. At least, that's what the RFC says,
+      and that's what happens by default. However, in order to work round YAEB,
+      there is an option to remember the esmtp state. Sigh.
 
-    s = NULL;
-    if ((rc = tls_server_start(tls_require_ciphers, &s)) == OK)
-      {
-      if (!tls_remember_esmtp)
-        helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE;
-      cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
-      cmd_list[CMD_LIST_AUTH].is_mail_cmd = TRUE;
-      cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
-      if (sender_helo_name)
-        {
-        store_free(sender_helo_name);
-        sender_helo_name = NULL;
-        host_build_sender_fullhost();  /* Rebuild */
-        set_process_info("handling incoming TLS connection from %s",
-          host_and_ident(FALSE));
-        }
-      received_protocol =
-       (sender_host_address ? protocols : protocols_local)
-         [ (esmtp
-           ? pextend + (sender_host_authenticated ? pauthed : 0)
-           : pnormal)
-         + (tls_in.active >= 0 ? pcrpted : 0)
-         ];
+      We must allow for an extra EHLO command and an extra AUTH command after
+      STARTTLS that don't add to the nonmail command count. */
 
-      sender_host_auth_pubname = sender_host_authenticated = NULL;
-      authenticated_id = NULL;
-      sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
-      DEBUG(D_tls) debug_printf("TLS active\n");
-      break;     /* Successful STARTTLS */
-      }
-    else
-      (void) smtp_log_tls_fail(s);
+      s = NULL;
+      if ((rc = tls_server_start(tls_require_ciphers, &s)) == OK)
+       {
+       if (!tls_remember_esmtp)
+         fl.helo_seen = fl.esmtp = fl.auth_advertised = f.smtp_in_pipelining_advertised = FALSE;
+       cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
+       cmd_list[CMD_LIST_AUTH].is_mail_cmd = TRUE;
+       cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
+       if (sender_helo_name)
+         {
+         store_free(sender_helo_name);
+         sender_helo_name = NULL;
+         host_build_sender_fullhost();  /* Rebuild */
+         set_process_info("handling incoming TLS connection from %s",
+           host_and_ident(FALSE));
+         }
+       received_protocol =
+         (sender_host_address ? protocols : protocols_local)
+           [ (fl.esmtp
+             ? pextend + (sender_host_authenticated ? pauthed : 0)
+             : pnormal)
+           + (tls_in.active.sock >= 0 ? pcrpted : 0)
+           ];
+
+       sender_host_auth_pubname = sender_host_authenticated = NULL;
+       authenticated_id = NULL;
+       sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
+       DEBUG(D_tls) debug_printf("TLS active\n");
+       break;     /* Successful STARTTLS */
+       }
+      else
+       (void) smtp_log_tls_fail(s);
 
-    /* Some local configuration problem was discovered before actually trying
-    to do a TLS handshake; give a temporary error. */
+      /* Some local configuration problem was discovered before actually trying
+      to do a TLS handshake; give a temporary error. */
 
-    if (rc == DEFER)
-      {
-      smtp_printf("454 TLS currently unavailable\r\n", FALSE);
-      break;
-      }
+      if (rc == DEFER)
+       {
+       smtp_printf("454 TLS currently unavailable\r\n", FALSE);
+       break;
+       }
 
-    /* Hard failure. Reject everything except QUIT or closed connection. One
-    cause for failure is a nested STARTTLS, in which case tls_in.active remains
-    set, but we must still reject all incoming commands. */
+      /* Hard failure. Reject everything except QUIT or closed connection. One
+      cause for failure is a nested STARTTLS, in which case tls_in.active remains
+      set, but we must still reject all incoming commands. */
 
-    DEBUG(D_tls) debug_printf("TLS failed to start\n");
-    while (done <= 0) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
-      {
-      case EOF_CMD:
-       log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF",
-         smtp_get_connection_info());
-       smtp_notquit_exit(US"tls-failed", NULL, NULL);
-       done = 2;
-       break;
+      DEBUG(D_tls) debug_printf("TLS failed to start\n");
+      while (done <= 0) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
+       {
+       case EOF_CMD:
+         log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF",
+           smtp_get_connection_info());
+         smtp_notquit_exit(US"tls-failed", NULL, NULL);
+         done = 2;
+         break;
 
-      /* It is perhaps arguable as to which exit ACL should be called here,
-      but as it is probably a situation that almost never arises, it
-      probably doesn't matter. We choose to call the real QUIT ACL, which in
-      some sense is perhaps "right". */
-
-      case QUIT_CMD:
-       user_msg = NULL;
-       if (  acl_smtp_quit
-          && ((rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg,
-                             &log_msg)) == ERROR))
-           log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
-             log_msg);
-       if (user_msg)
-         smtp_respond(US"221", 3, TRUE, user_msg);
-       else
-         smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
-       log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
-         smtp_get_connection_info());
-       done = 2;
-       break;
+       /* It is perhaps arguable as to which exit ACL should be called here,
+       but as it is probably a situation that almost never arises, it
+       probably doesn't matter. We choose to call the real QUIT ACL, which in
+       some sense is perhaps "right". */
+
+       case QUIT_CMD:
+         user_msg = NULL;
+         if (  acl_smtp_quit
+            && ((rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg,
+                               &log_msg)) == ERROR))
+             log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
+               log_msg);
+         if (user_msg)
+           smtp_respond(US"221", 3, TRUE, user_msg);
+         else
+           smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
+         log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
+           smtp_get_connection_info());
+         done = 2;
+         break;
 
-      default:
-       smtp_printf("554 Security failure\r\n", FALSE);
-       break;
-      }
-    tls_close(TRUE, TLS_SHUTDOWN_NOWAIT);
-    break;
+       default:
+         smtp_printf("554 Security failure\r\n", FALSE);
+         break;
+       }
+      tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
+      break;
     #endif
 
 
@@ -5421,23 +5553,23 @@ while (done <= 0)
     message. */
 
     case QUIT_CMD:
-    smtp_quit_handler(&user_msg, &log_msg);
-    done = 2;
-    break;
+      smtp_quit_handler(&user_msg, &log_msg);
+      done = 2;
+      break;
 
 
     case RSET_CMD:
-    smtp_rset_handler();
-    cancel_cutthrough_connection(TRUE, US"RSET received");
-    smtp_reset(reset_point);
-    toomany = FALSE;
-    break;
+      smtp_rset_handler();
+      cancel_cutthrough_connection(TRUE, US"RSET received");
+      smtp_reset(reset_point);
+      toomany = FALSE;
+      break;
 
 
     case NOOP_CMD:
-    HAD(SCH_NOOP);
-    smtp_printf("250 OK\r\n", FALSE);
-    break;
+      HAD(SCH_NOOP);
+      smtp_printf("250 OK\r\n", FALSE);
+      break;
 
 
     /* Show ETRN/EXPN/VRFY if there's an ACL for checking hosts; if actually
@@ -5446,287 +5578,288 @@ while (done <= 0)
     response. */
 
     case HELP_CMD:
-    HAD(SCH_HELP);
-    smtp_printf("214-Commands supported:\r\n", TRUE);
-      {
-      uschar buffer[256];
-      buffer[0] = 0;
-      Ustrcat(buffer, " AUTH");
-      #ifdef SUPPORT_TLS
-      if (tls_in.active < 0 &&
-          verify_check_host(&tls_advertise_hosts) != FAIL)
-        Ustrcat(buffer, " STARTTLS");
-      #endif
-      Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA BDAT");
-      Ustrcat(buffer, " NOOP QUIT RSET HELP");
-      if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN");
-      if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN");
-      if (acl_smtp_vrfy != NULL) Ustrcat(buffer, " VRFY");
-      smtp_printf("214%s\r\n", FALSE, buffer);
-      }
-    break;
+      HAD(SCH_HELP);
+      smtp_printf("214-Commands supported:\r\n", TRUE);
+       {
+       uschar buffer[256];
+       buffer[0] = 0;
+       Ustrcat(buffer, " AUTH");
+       #ifdef SUPPORT_TLS
+       if (tls_in.active.sock < 0 &&
+           verify_check_host(&tls_advertise_hosts) != FAIL)
+         Ustrcat(buffer, " STARTTLS");
+       #endif
+       Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA BDAT");
+       Ustrcat(buffer, " NOOP QUIT RSET HELP");
+       if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN");
+       if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN");
+       if (acl_smtp_vrfy != NULL) Ustrcat(buffer, " VRFY");
+       smtp_printf("214%s\r\n", FALSE, buffer);
+       }
+      break;
 
 
     case EOF_CMD:
-    incomplete_transaction_log(US"connection lost");
-    smtp_notquit_exit(US"connection-lost", US"421",
-      US"%s lost input connection", smtp_active_hostname);
-
-    /* Don't log by default unless in the middle of a message, as some mailers
-    just drop the call rather than sending QUIT, and it clutters up the logs.
-    */
-
-    if (sender_address || recipients_count > 0)
-      log_write(L_lost_incoming_connection, LOG_MAIN,
-       "unexpected %s while reading SMTP command from %s%s%s D=%s",
-       sender_host_unknown ? "EOF" : "disconnection",
-       tcp_in_fastopen && !tcp_in_fastopen_logged ? US"TFO " : US"",
-       host_and_ident(FALSE), smtp_read_error,
-       string_timesince(&smtp_connection_start)
-       );
+      incomplete_transaction_log(US"connection lost");
+      smtp_notquit_exit(US"connection-lost", US"421",
+       US"%s lost input connection", smtp_active_hostname);
+
+      /* Don't log by default unless in the middle of a message, as some mailers
+      just drop the call rather than sending QUIT, and it clutters up the logs.
+      */
+
+      if (sender_address || recipients_count > 0)
+       log_write(L_lost_incoming_connection, LOG_MAIN,
+         "unexpected %s while reading SMTP command from %s%s%s D=%s",
+         f.sender_host_unknown ? "EOF" : "disconnection",
+         f.tcp_in_fastopen && !f.tcp_in_fastopen_logged ? US"TFO " : US"",
+         host_and_ident(FALSE), smtp_read_error,
+         string_timesince(&smtp_connection_start)
+         );
 
-    else
-      log_write(L_smtp_connection, LOG_MAIN, "%s %slost%s D=%s",
-        smtp_get_connection_info(),
-       tcp_in_fastopen && !tcp_in_fastopen_logged ? US"TFO " : US"",
-       smtp_read_error,
-       string_timesince(&smtp_connection_start)
-       );
-
-    done = 1;
-    break;
+      else
+       log_write(L_smtp_connection, LOG_MAIN, "%s %slost%s D=%s",
+         smtp_get_connection_info(),
+         f.tcp_in_fastopen && !f.tcp_in_fastopen_logged ? US"TFO " : US"",
+         smtp_read_error,
+         string_timesince(&smtp_connection_start)
+         );
+
+      done = 1;
+      break;
 
 
     case ETRN_CMD:
-    HAD(SCH_ETRN);
-    if (sender_address)
-      {
-      done = synprot_error(L_smtp_protocol_error, 503, NULL,
-        US"ETRN is not permitted inside a transaction");
-      break;
-      }
+      HAD(SCH_ETRN);
+      if (sender_address)
+       {
+       done = synprot_error(L_smtp_protocol_error, 503, NULL,
+         US"ETRN is not permitted inside a transaction");
+       break;
+       }
 
-    log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument,
-      host_and_ident(FALSE));
+      log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument,
+       host_and_ident(FALSE));
 
-    if ((rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn,
-               &user_msg, &log_msg)) != OK)
-      {
-      done = smtp_handle_acl_fail(ACL_WHERE_ETRN, rc, user_msg, log_msg);
-      break;
-      }
+      if ((rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn,
+                 &user_msg, &log_msg)) != OK)
+       {
+       done = smtp_handle_acl_fail(ACL_WHERE_ETRN, rc, user_msg, log_msg);
+       break;
+       }
 
-    /* Compute the serialization key for this command. */
+      /* Compute the serialization key for this command. */
 
-    etrn_serialize_key = string_sprintf("etrn-%s\n", smtp_cmd_data);
+      etrn_serialize_key = string_sprintf("etrn-%s\n", smtp_cmd_data);
 
-    /* If a command has been specified for running as a result of ETRN, we
-    permit any argument to ETRN. If not, only the # standard form is permitted,
-    since that is strictly the only kind of ETRN that can be implemented
-    according to the RFC. */
+      /* If a command has been specified for running as a result of ETRN, we
+      permit any argument to ETRN. If not, only the # standard form is permitted,
+      since that is strictly the only kind of ETRN that can be implemented
+      according to the RFC. */
 
-    if (smtp_etrn_command)
-      {
-      uschar *error;
-      BOOL rc;
-      etrn_command = smtp_etrn_command;
-      deliver_domain = smtp_cmd_data;
-      rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL,
-        US"ETRN processing", &error);
-      deliver_domain = NULL;
-      if (!rc)
-        {
-        log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s",
-          error);
-        smtp_printf("458 Internal failure\r\n", FALSE);
-        break;
-        }
-      }
+      if (smtp_etrn_command)
+       {
+       uschar *error;
+       BOOL rc;
+       etrn_command = smtp_etrn_command;
+       deliver_domain = smtp_cmd_data;
+       rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL,
+         US"ETRN processing", &error);
+       deliver_domain = NULL;
+       if (!rc)
+         {
+         log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s",
+           error);
+         smtp_printf("458 Internal failure\r\n", FALSE);
+         break;
+         }
+       }
 
-    /* Else set up to call Exim with the -R option. */
+      /* Else set up to call Exim with the -R option. */
 
-    else
-      {
-      if (*smtp_cmd_data++ != '#')
-        {
-        done = synprot_error(L_smtp_syntax_error, 501, NULL,
-          US"argument must begin with #");
-        break;
-        }
-      etrn_command = US"exim -R";
-      argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE,
-        *queue_name ? 4 : 2,
-       US"-R", smtp_cmd_data,
-       US"-MCG", queue_name);
-      }
+      else
+       {
+       if (*smtp_cmd_data++ != '#')
+         {
+         done = synprot_error(L_smtp_syntax_error, 501, NULL,
+           US"argument must begin with #");
+         break;
+         }
+       etrn_command = US"exim -R";
+       argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE,
+         *queue_name ? 4 : 2,
+         US"-R", smtp_cmd_data,
+         US"-MCG", queue_name);
+       }
 
-    /* If we are host-testing, don't actually do anything. */
+      /* If we are host-testing, don't actually do anything. */
 
-    if (host_checking)
-      {
-      HDEBUG(D_any)
-        {
-        debug_printf("ETRN command is: %s\n", etrn_command);
-        debug_printf("ETRN command execution skipped\n");
-        }
-      if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
-        else smtp_user_msg(US"250", user_msg);
-      break;
-      }
+      if (host_checking)
+       {
+       HDEBUG(D_any)
+         {
+         debug_printf("ETRN command is: %s\n", etrn_command);
+         debug_printf("ETRN command execution skipped\n");
+         }
+       if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
+         else smtp_user_msg(US"250", user_msg);
+       break;
+       }
 
 
-    /* If ETRN queue runs are to be serialized, check the database to
-    ensure one isn't already running. */
+      /* If ETRN queue runs are to be serialized, check the database to
+      ensure one isn't already running. */
 
-    if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
-      {
-      smtp_printf("458 Already processing %s\r\n", FALSE, smtp_cmd_data);
-      break;
-      }
+      if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
+       {
+       smtp_printf("458 Already processing %s\r\n", FALSE, smtp_cmd_data);
+       break;
+       }
 
-    /* Fork a child process and run the command. We don't want to have to
-    wait for the process at any point, so set SIGCHLD to SIG_IGN before
-    forking. It should be set that way anyway for external incoming SMTP,
-    but we save and restore to be tidy. If serialization is required, we
-    actually run the command in yet another process, so we can wait for it
-    to complete and then remove the serialization lock. */
+      /* Fork a child process and run the command. We don't want to have to
+      wait for the process at any point, so set SIGCHLD to SIG_IGN before
+      forking. It should be set that way anyway for external incoming SMTP,
+      but we save and restore to be tidy. If serialization is required, we
+      actually run the command in yet another process, so we can wait for it
+      to complete and then remove the serialization lock. */
 
-    oldsignal = signal(SIGCHLD, SIG_IGN);
+      oldsignal = signal(SIGCHLD, SIG_IGN);
 
-    if ((pid = fork()) == 0)
-      {
-      smtp_input = FALSE;       /* This process is not associated with the */
-      (void)fclose(smtp_in);    /* SMTP call any more. */
-      (void)fclose(smtp_out);
+      if ((pid = fork()) == 0)
+       {
+       smtp_input = FALSE;       /* This process is not associated with the */
+       (void)fclose(smtp_in);    /* SMTP call any more. */
+       (void)fclose(smtp_out);
 
-      signal(SIGCHLD, SIG_DFL);      /* Want to catch child */
+       signal(SIGCHLD, SIG_DFL);      /* Want to catch child */
 
-      /* If not serializing, do the exec right away. Otherwise, fork down
-      into another process. */
+       /* If not serializing, do the exec right away. Otherwise, fork down
+       into another process. */
 
-      if (!smtp_etrn_serialize || (pid = fork()) == 0)
-        {
-        DEBUG(D_exec) debug_print_argv(argv);
-        exim_nullstd();                   /* Ensure std{in,out,err} exist */
-        execv(CS argv[0], (char *const *)argv);
-        log_write(0, LOG_MAIN|LOG_PANIC_DIE, "exec of \"%s\" (ETRN) failed: %s",
-          etrn_command, strerror(errno));
-        _exit(EXIT_FAILURE);         /* paranoia */
-        }
+       if (!smtp_etrn_serialize || (pid = fork()) == 0)
+         {
+         DEBUG(D_exec) debug_print_argv(argv);
+         exim_nullstd();                   /* Ensure std{in,out,err} exist */
+         execv(CS argv[0], (char *const *)argv);
+         log_write(0, LOG_MAIN|LOG_PANIC_DIE, "exec of \"%s\" (ETRN) failed: %s",
+           etrn_command, strerror(errno));
+         _exit(EXIT_FAILURE);         /* paranoia */
+         }
 
-      /* Obey this if smtp_serialize and the 2nd fork yielded non-zero. That
-      is, we are in the first subprocess, after forking again. All we can do
-      for a failing fork is to log it. Otherwise, wait for the 2nd process to
-      complete, before removing the serialization. */
+       /* Obey this if smtp_serialize and the 2nd fork yielded non-zero. That
+       is, we are in the first subprocess, after forking again. All we can do
+       for a failing fork is to log it. Otherwise, wait for the 2nd process to
+       complete, before removing the serialization. */
 
-      if (pid < 0)
-        log_write(0, LOG_MAIN|LOG_PANIC, "2nd fork for serialized ETRN "
-          "failed: %s", strerror(errno));
-      else
-        {
-        int status;
-        DEBUG(D_any) debug_printf("waiting for serialized ETRN process %d\n",
-          (int)pid);
-        (void)wait(&status);
-        DEBUG(D_any) debug_printf("serialized ETRN process %d ended\n",
-          (int)pid);
-        }
+       if (pid < 0)
+         log_write(0, LOG_MAIN|LOG_PANIC, "2nd fork for serialized ETRN "
+           "failed: %s", strerror(errno));
+       else
+         {
+         int status;
+         DEBUG(D_any) debug_printf("waiting for serialized ETRN process %d\n",
+           (int)pid);
+         (void)wait(&status);
+         DEBUG(D_any) debug_printf("serialized ETRN process %d ended\n",
+           (int)pid);
+         }
 
-      enq_end(etrn_serialize_key);
-      _exit(EXIT_SUCCESS);
-      }
+       enq_end(etrn_serialize_key);
+       _exit(EXIT_SUCCESS);
+       }
 
-    /* Back in the top level SMTP process. Check that we started a subprocess
-    and restore the signal state. */
+      /* Back in the top level SMTP process. Check that we started a subprocess
+      and restore the signal state. */
 
-    if (pid < 0)
-      {
-      log_write(0, LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s",
-        strerror(errno));
-      smtp_printf("458 Unable to fork process\r\n", FALSE);
-      if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
-      }
-    else
-      {
-      if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
-        else smtp_user_msg(US"250", user_msg);
-      }
+      if (pid < 0)
+       {
+       log_write(0, LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s",
+         strerror(errno));
+       smtp_printf("458 Unable to fork process\r\n", FALSE);
+       if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
+       }
+      else
+       {
+       if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
+         else smtp_user_msg(US"250", user_msg);
+       }
 
-    signal(SIGCHLD, oldsignal);
-    break;
+      signal(SIGCHLD, oldsignal);
+      break;
 
 
     case BADARG_CMD:
-    done = synprot_error(L_smtp_syntax_error, 501, NULL,
-      US"unexpected argument data");
-    break;
+      done = synprot_error(L_smtp_syntax_error, 501, NULL,
+       US"unexpected argument data");
+      break;
 
 
     /* This currently happens only for NULLs, but could be extended. */
 
     case BADCHAR_CMD:
-    done = synprot_error(L_smtp_syntax_error, 0, NULL,       /* Just logs */
-      US"NULL character(s) present (shown as '?')");
-    smtp_printf("501 NULL characters are not allowed in SMTP commands\r\n", FALSE);
-    break;
+      done = synprot_error(L_smtp_syntax_error, 0, NULL,       /* Just logs */
+       US"NUL character(s) present (shown as '?')");
+      smtp_printf("501 NUL characters are not allowed in SMTP commands\r\n",
+                 FALSE);
+      break;
 
 
     case BADSYN_CMD:
     SYNC_FAILURE:
-    if (smtp_inend >= smtp_inbuffer + IN_BUFFER_SIZE)
-      smtp_inend = smtp_inbuffer + IN_BUFFER_SIZE - 1;
-    c = smtp_inend - smtp_inptr;
-    if (c > 150) c = 150;
-    smtp_inptr[c] = 0;
-    incomplete_transaction_log(US"sync failure");
-    log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
-      "(next input sent too soon: pipelining was%s advertised): "
-      "rejected \"%s\" %s next input=\"%s\"",
-      pipelining_advertised? "" : " not",
-      smtp_cmd_buffer, host_and_ident(TRUE),
-      string_printing(smtp_inptr));
-    smtp_notquit_exit(US"synchronization-error", US"554",
-      US"SMTP synchronization error");
-    done = 1;   /* Pretend eof - drops connection */
-    break;
+      if (smtp_inend >= smtp_inbuffer + IN_BUFFER_SIZE)
+       smtp_inend = smtp_inbuffer + IN_BUFFER_SIZE - 1;
+      c = smtp_inend - smtp_inptr;
+      if (c > 150) c = 150;    /* limit logged amount */
+      smtp_inptr[c] = 0;
+      incomplete_transaction_log(US"sync failure");
+      log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
+       "(next input sent too soon: pipelining was%s advertised): "
+       "rejected \"%s\" %s next input=\"%s\"",
+       f.smtp_in_pipelining_advertised ? "" : " not",
+       smtp_cmd_buffer, host_and_ident(TRUE),
+       string_printing(smtp_inptr));
+      smtp_notquit_exit(US"synchronization-error", US"554",
+       US"SMTP synchronization error");
+      done = 1;   /* Pretend eof - drops connection */
+      break;
 
 
     case TOO_MANY_NONMAIL_CMD:
-    s = smtp_cmd_buffer;
-    while (*s != 0 && !isspace(*s)) s++;
-    incomplete_transaction_log(US"too many non-mail commands");
-    log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
-      "nonmail commands (last was \"%.*s\")",  host_and_ident(FALSE),
-      (int)(s - smtp_cmd_buffer), smtp_cmd_buffer);
-    smtp_notquit_exit(US"bad-commands", US"554", US"Too many nonmail commands");
-    done = 1;   /* Pretend eof - drops connection */
-    break;
+      s = smtp_cmd_buffer;
+      while (*s != 0 && !isspace(*s)) s++;
+      incomplete_transaction_log(US"too many non-mail commands");
+      log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
+       "nonmail commands (last was \"%.*s\")",  host_and_ident(FALSE),
+       (int)(s - smtp_cmd_buffer), smtp_cmd_buffer);
+      smtp_notquit_exit(US"bad-commands", US"554", US"Too many nonmail commands");
+      done = 1;   /* Pretend eof - drops connection */
+      break;
 
 #ifdef SUPPORT_PROXY
     case PROXY_FAIL_IGNORE_CMD:
-    smtp_printf("503 Command refused, required Proxy negotiation failed\r\n", FALSE);
-    break;
+      smtp_printf("503 Command refused, required Proxy negotiation failed\r\n", FALSE);
+      break;
 #endif
 
     default:
-    if (unknown_command_count++ >= smtp_max_unknown_commands)
-      {
-      log_write(L_smtp_syntax_error, LOG_MAIN,
-        "SMTP syntax error in \"%s\" %s %s",
-        string_printing(smtp_cmd_buffer), host_and_ident(TRUE),
-        US"unrecognized command");
-      incomplete_transaction_log(US"unrecognized command");
-      smtp_notquit_exit(US"bad-commands", US"500",
-        US"Too many unrecognized commands");
-      done = 2;
-      log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
-        "unrecognized commands (last was \"%s\")", host_and_ident(FALSE),
-        string_printing(smtp_cmd_buffer));
-      }
-    else
-      done = synprot_error(L_smtp_syntax_error, 500, NULL,
-        US"unrecognized command");
-    break;
+      if (unknown_command_count++ >= smtp_max_unknown_commands)
+       {
+       log_write(L_smtp_syntax_error, LOG_MAIN,
+         "SMTP syntax error in \"%s\" %s %s",
+         string_printing(smtp_cmd_buffer), host_and_ident(TRUE),
+         US"unrecognized command");
+       incomplete_transaction_log(US"unrecognized command");
+       smtp_notquit_exit(US"bad-commands", US"500",
+         US"Too many unrecognized commands");
+       done = 2;
+       log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
+         "unrecognized commands (last was \"%s\")", host_and_ident(FALSE),
+         string_printing(smtp_cmd_buffer));
+       }
+      else
+       done = synprot_error(L_smtp_syntax_error, 500, NULL,
+         US"unrecognized command");
+      break;
     }
 
   /* This label is used by goto's inside loops that want to break out to