Inline four often-called new functions
[exim.git] / src / src / smtp_in.c
index a238ae261dc3f1f077ebfb079601c6fbe9a0a5ef..93d5cece38525c40dfaaf1d3e32955f1c030c658 100644 (file)
@@ -3,6 +3,7 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
@@ -133,11 +134,8 @@ to the circular buffer that holds a list of the last n received. */
 
 static struct {
   BOOL auth_advertised                 :1;
 
 static struct {
   BOOL auth_advertised                 :1;
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
   BOOL tls_advertised                  :1;
   BOOL tls_advertised                  :1;
-# ifdef EXPERIMENTAL_REQUIRETLS
-  BOOL requiretls_advertised           :1;
-# endif
 #endif
   BOOL dsn_advertised                  :1;
   BOOL esmtp                           :1;
 #endif
   BOOL dsn_advertised                  :1;
   BOOL esmtp                           :1;
@@ -145,7 +143,7 @@ static struct {
   BOOL helo_verify                     :1;
   BOOL helo_seen                       :1;
   BOOL helo_accept_junk                        :1;
   BOOL helo_verify                     :1;
   BOOL helo_seen                       :1;
   BOOL helo_accept_junk                        :1;
-#ifdef EXPERIMENTAL_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
   BOOL pipe_connect_acceptable         :1;
 #endif
   BOOL rcpt_smtp_response_same         :1;
   BOOL pipe_connect_acceptable         :1;
 #endif
   BOOL rcpt_smtp_response_same         :1;
@@ -197,7 +195,7 @@ 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  },
   { "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
+#ifndef DISABLE_TLS
   { "starttls",   sizeof("starttls")-1,   STARTTLS_CMD, FALSE, FALSE },
   { "tls_auth",   0,                      TLS_AUTH_CMD, FALSE, FALSE },
 #endif
   { "starttls",   sizeof("starttls")-1,   STARTTLS_CMD, FALSE, FALSE },
   { "tls_auth",   0,                      TLS_AUTH_CMD, FALSE, FALSE },
 #endif
@@ -229,7 +227,7 @@ static smtp_cmd_list *cmd_list_end =
 /* This list of names is used for performing the smtp_no_mail logging action.
 It must be kept in step with the SCH_xxx enumerations. */
 
 /* This list of names is used for performing the smtp_no_mail logging action.
 It must be kept in step with the SCH_xxx enumerations. */
 
-static uschar *smtp_names[] =
+uschar * smtp_names[] =
   {
   US"NONE", US"AUTH", US"DATA", US"BDAT", US"EHLO", US"ETRN", US"EXPN",
   US"HELO", US"HELP", US"MAIL", US"NOOP", US"QUIT", US"RCPT", US"RSET",
   {
   US"NONE", US"AUTH", US"DATA", US"BDAT", US"EHLO", US"ETRN", US"EXPN",
   US"HELO", US"HELP", US"MAIL", US"NOOP", US"QUIT", US"RCPT", US"RSET",
@@ -267,9 +265,6 @@ enum {
   ENV_MAIL_OPT_RET, ENV_MAIL_OPT_ENVID,
 #ifdef SUPPORT_I18N
   ENV_MAIL_OPT_UTF8,
   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 {
 #endif
   };
 typedef struct {
@@ -289,10 +284,6 @@ 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 */
     { 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 },
 #endif
     /* keep this the last entry */
     { US"NULL",   ENV_MAIL_OPT_NULL,   FALSE },
@@ -356,9 +347,9 @@ wouldblock_reading(void)
 {
 int fd, rc;
 fd_set fds;
 {
 int fd, rc;
 fd_set fds;
-struct timeval tzero;
+struct timeval tzero = {.tv_sec = 0, .tv_usec = 0};
 
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 if (tls_in.active.sock >= 0)
  return !tls_could_read();
 #endif
 if (tls_in.active.sock >= 0)
  return !tls_could_read();
 #endif
@@ -369,8 +360,6 @@ if (smtp_inptr < smtp_inend)
 fd = fileno(smtp_in);
 FD_ZERO(&fds);
 FD_SET(fd, &fds);
 fd = fileno(smtp_in);
 FD_ZERO(&fds);
 FD_SET(fd, &fds);
-tzero.tv_sec = 0;
-tzero.tv_usec = 0;
 rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero);
 
 if (rc <= 0) return TRUE;     /* Not ready to read */
 rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero);
 
 if (rc <= 0) return TRUE;     /* Not ready to read */
@@ -407,7 +396,7 @@ return TRUE;
 }
 
 
 }
 
 
-#ifdef EXPERIMENTAL_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
 static BOOL
 pipeline_connect_sends(void)
 {
 static BOOL
 pipeline_connect_sends(void)
 {
@@ -443,7 +432,7 @@ if (!sender_address                         /* No transaction in progress */
 
 if (recipients_count > 0)
   {
 
 if (recipients_count > 0)
   {
-  raw_recipients = store_get(recipients_count * sizeof(uschar *));
+  raw_recipients = store_get(recipients_count * sizeof(uschar *), FALSE);
   for (int i = 0; i < recipients_count; i++)
     raw_recipients[i] = recipients_list[i].address;
   raw_recipients_count = recipients_count;
   for (int i = 0; i < recipients_count; i++)
     raw_recipients[i] = recipients_list[i].address;
   raw_recipients_count = recipients_count;
@@ -467,7 +456,7 @@ if (smtp_batched_input)
 smtp_notquit_exit(US"command-timeout", US"421",
   US"%s: SMTP command timeout - closing connection",
   smtp_active_hostname);
 smtp_notquit_exit(US"command-timeout", US"421",
   US"%s: SMTP command timeout - closing connection",
   smtp_active_hostname);
-exim_exit(EXIT_FAILURE, US"receiving");
+exim_exit(EXIT_FAILURE);
 }
 
 void
 }
 
 void
@@ -478,7 +467,7 @@ 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);
   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");
+exim_exit(EXIT_FAILURE);
 }
 
 void
 }
 
 void
@@ -537,8 +526,8 @@ if (rc <= 0)
       smtp_data_sigint_exit();
 
     smtp_had_error = save_errno;
       smtp_data_sigint_exit();
 
     smtp_had_error = save_errno;
-    smtp_read_error = string_copy_malloc(
-      string_sprintf(" (error: %s)", strerror(save_errno)));
+    smtp_read_error = string_copy_perm(
+      string_sprintf(" (error: %s)", strerror(save_errno)), FALSE);
     }
   else
     smtp_had_eof = 1;
     }
   else
     smtp_had_eof = 1;
@@ -596,12 +585,19 @@ smtp_get_cache(void)
 {
 #ifndef DISABLE_DKIM
 int n = smtp_inend - smtp_inptr;
 {
 #ifndef DISABLE_DKIM
 int n = smtp_inend - smtp_inptr;
+if (chunking_state == CHUNKING_LAST && chunking_data_left < n)
+  n = chunking_data_left;
 if (n > 0)
   dkim_exim_verify_feed(smtp_inptr, n);
 #endif
 }
 
 
 if (n > 0)
   dkim_exim_verify_feed(smtp_inptr, n);
 #endif
 }
 
 
+/* Forward declarations */
+static inline void bdat_push_receive_functions(void);
+static inline void bdat_pop_receive_functions(void);
+
+
 /* Get a byte from the smtp input, in CHUNKING mode.  Handle ack of the
 previous BDAT chunk and getting new ones when we run out.  Uses the
 underlying smtp_getc or tls_getc both for that and for getting the
 /* Get a byte from the smtp input, in CHUNKING mode.  Handle ack of the
 previous BDAT chunk and getting new ones when we run out.  Uses the
 underlying smtp_getc or tls_getc both for that and for getting the
@@ -633,9 +629,7 @@ for(;;)
   if (chunking_data_left > 0)
     return lwr_receive_getc(chunking_data_left--);
 
   if (chunking_data_left > 0)
     return lwr_receive_getc(chunking_data_left--);
 
-  receive_getc = lwr_receive_getc;
-  receive_getbuf = lwr_receive_getbuf;
-  receive_ungetc = lwr_receive_ungetc;
+  bdat_pop_receive_functions();
 #ifndef DISABLE_DKIM
   dkim_save = dkim_collect_input;
   dkim_collect_input = 0;
 #ifndef DISABLE_DKIM
   dkim_save = dkim_collect_input;
   dkim_collect_input = 0;
@@ -739,9 +733,7 @@ next_cmd:
          goto repeat_until_rset;
          }
 
          goto repeat_until_rset;
          }
 
-      receive_getc = bdat_getc;
-      receive_getbuf = bdat_getbuf;    /* r~getbuf is never actually used */
-      receive_ungetc = bdat_ungetc;
+      bdat_push_receive_functions();
 #ifndef DISABLE_DKIM
       dkim_collect_input = dkim_save;
 #endif
 #ifndef DISABLE_DKIM
       dkim_collect_input = dkim_save;
 #endif
@@ -774,9 +766,7 @@ while (chunking_data_left)
   if (!bdat_getbuf(&n)) break;
   }
 
   if (!bdat_getbuf(&n)) break;
   }
 
-receive_getc = lwr_receive_getc;
-receive_getbuf = lwr_receive_getbuf;
-receive_ungetc = lwr_receive_ungetc;
+bdat_pop_receive_functions();
 
 if (chunking_state != CHUNKING_LAST)
   {
 
 if (chunking_state != CHUNKING_LAST)
   {
@@ -786,7 +776,37 @@ if (chunking_state != CHUNKING_LAST)
 }
 
 
 }
 
 
+static inline void
+bdat_push_receive_functions(void)
+{
+/* push the current receive_* function on the "stack", and
+replace them by bdat_getc(), which in turn will use the lwr_receive_*
+functions to do the dirty work. */
+if (lwr_receive_getc == NULL)
+  {
+  lwr_receive_getc = receive_getc;
+  lwr_receive_getbuf = receive_getbuf;
+  lwr_receive_ungetc = receive_ungetc;
+  }
+else
+  {
+  DEBUG(D_receive) debug_printf("chunking double-push receive functions\n");
+  }
+
+receive_getc = bdat_getc;
+receive_ungetc = bdat_ungetc;
+}
 
 
+static inline void
+bdat_pop_receive_functions(void)
+{
+receive_getc = lwr_receive_getc;
+receive_getbuf = lwr_receive_getbuf;
+receive_ungetc = lwr_receive_ungetc;
+lwr_receive_getc = NULL;
+lwr_receive_getbuf = NULL;
+lwr_receive_ungetc = NULL;
+}
 
 /*************************************************
 *          SMTP version of ungetc()              *
 
 /*************************************************
 *          SMTP version of ungetc()              *
@@ -887,6 +907,8 @@ flush for non-TLS connections. The smtp_fflush() function is available for
 checking that: for convenience, TLS output errors are remembered here so that
 they are also picked up later by smtp_fflush().
 
 checking that: for convenience, TLS output errors are remembered here so that
 they are also picked up later by smtp_fflush().
 
+This function is exposed to the local_scan API; do not change the signature.
+
 Arguments:
   format      format string
   more       further data expected
 Arguments:
   format      format string
   more       further data expected
@@ -907,7 +929,11 @@ va_end(ap);
 
 /* This is split off so that verify.c:respond_printf() can, in effect, call
 smtp_printf(), bearing in mind that in C a vararg function can't directly
 
 /* This is split off so that verify.c:respond_printf() can, in effect, call
 smtp_printf(), bearing in mind that in C a vararg function can't directly
-call another vararg function, only a function which accepts a va_list. */
+call another vararg function, only a function which accepts a va_list.
+
+This function is exposed to the local_scan API; do not change the signature.
+*/
+/*XXX consider passing caller-info in, for string_vformat-onward */
 
 void
 smtp_vprintf(const char *format, BOOL more, va_list ap)
 
 void
 smtp_vprintf(const char *format, BOOL more, va_list ap)
@@ -915,26 +941,27 @@ smtp_vprintf(const char *format, BOOL more, va_list ap)
 gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
 BOOL yield;
 
 gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
 BOOL yield;
 
-yield = !! string_vformat(&gs, FALSE, format, ap);
+/* Use taint-unchecked routines for writing into big_buffer, trusting
+that we'll never expand it. */
+
+yield = !! string_vformat(&gs, SVFMT_TAINT_NOCHK, format, ap);
 string_from_gstring(&gs);
 
 DEBUG(D_receive)
   {
 string_from_gstring(&gs);
 
 DEBUG(D_receive)
   {
-  void *reset_point = store_get(0);
   uschar *msg_copy, *cr, *end;
   msg_copy = string_copy(gs.s);
   end = msg_copy + gs.ptr;
   while ((cr = Ustrchr(msg_copy, '\r')) != NULL)   /* lose CRs */
     memmove(cr, cr + 1, (end--) - cr);
   debug_printf("SMTP>> %s", msg_copy);
   uschar *msg_copy, *cr, *end;
   msg_copy = string_copy(gs.s);
   end = msg_copy + gs.ptr;
   while ((cr = Ustrchr(msg_copy, '\r')) != NULL)   /* lose CRs */
     memmove(cr, cr + 1, (end--) - cr);
   debug_printf("SMTP>> %s", msg_copy);
-  store_reset(reset_point);
   }
 
 if (!yield)
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()");
   smtp_closedown(US"Unexpected error");
   }
 
 if (!yield)
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()");
   smtp_closedown(US"Unexpected error");
-  exim_exit(EXIT_FAILURE, NULL);
+  exim_exit(EXIT_FAILURE);
   }
 
 /* If this is the first output for a (non-batch) RCPT command, see if all RCPTs
   }
 
 /* If this is the first output for a (non-batch) RCPT command, see if all RCPTs
@@ -955,16 +982,13 @@ if (fl.rcpt_in_progress)
 
 /* Now write the string */
 
 
 /* Now write the string */
 
-#ifdef SUPPORT_TLS
-if (tls_in.active.sock >= 0)
-  {
-  if (tls_write(NULL, gs.s, gs.ptr, more) < 0)
-    smtp_write_error = -1;
-  }
-else
+if (
+#ifndef DISABLE_TLS
+    tls_in.active.sock >= 0 ? (tls_write(NULL, gs.s, gs.ptr, more) < 0) :
 #endif
 #endif
-
-if (fprintf(smtp_out, "%s", gs.s) < 0) smtp_write_error = -1;
+    (fwrite(gs.s, gs.ptr, 1, smtp_out) == 0)
+   )
+    smtp_write_error = -1;
 }
 
 
 }
 
 
@@ -975,8 +999,7 @@ if (fprintf(smtp_out, "%s", gs.s) < 0) smtp_write_error = -1;
 
 /* This function isn't currently used within Exim (it detects errors when it
 tries to read the next SMTP input), but is available for use in local_scan().
 
 /* This function isn't currently used within Exim (it detects errors when it
 tries to read the next SMTP input), but is available for use in local_scan().
-For non-TLS connections, it flushes the output and checks for errors. For
-TLS-connections, it checks for a previously-detected TLS write error.
+It flushes the output and checks for errors.
 
 Arguments:  none
 Returns:    0 for no error; -1 after an error
 
 Arguments:  none
 Returns:    0 for no error; -1 after an error
@@ -986,6 +1009,15 @@ int
 smtp_fflush(void)
 {
 if (tls_in.active.sock < 0 && fflush(smtp_out) != 0) smtp_write_error = -1;
 smtp_fflush(void)
 {
 if (tls_in.active.sock < 0 && fflush(smtp_out) != 0) smtp_write_error = -1;
+
+if (
+#ifndef DISABLE_TLS
+    tls_in.active.sock >= 0 ? (tls_write(NULL, NULL, 0, FALSE) < 0) :
+#endif
+    (fflush(smtp_out) != 0)
+   )
+    smtp_write_error = -1;
+
 return smtp_write_error;
 }
 
 return smtp_write_error;
 }
 
@@ -1030,25 +1062,6 @@ had_command_sigterm = sig;
 
 
 #ifdef SUPPORT_PROXY
 
 
 #ifdef SUPPORT_PROXY
-/*************************************************
-*     Restore socket timeout to previous value   *
-*************************************************/
-/* If the previous value was successfully retrieved, restore
-it before returning control to the non-proxy routines
-
-Arguments: fd     - File descriptor for input
-           get_ok - Successfully retrieved previous values
-           tvtmp  - Time struct with previous values
-           vslen  - Length of time struct
-Returns:   none
-*/
-static void
-restore_socket_timeout(int fd, int get_ok, struct timeval * tvtmp, socklen_t vslen)
-{
-if (get_ok == 0)
-  (void) setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS tvtmp, vslen);
-}
-
 /*************************************************
 *       Check if host is required proxy host     *
 *************************************************/
 /*************************************************
 *       Check if host is required proxy host     *
 *************************************************/
@@ -1125,7 +1138,7 @@ if (cr != NULL)
 
 while (capacity > 0)
   {
 
 while (capacity > 0)
   {
-  do { ret = recv(fd, to, 1, 0); } while (ret == -1 && errno == EINTR);
+  do { ret = read(fd, to, 1); } while (ret == -1 && errno == EINTR && !had_command_timeout);
   if (ret == -1)
     return -1;
   have++;
   if (ret == -1)
     return -1;
   have++;
@@ -1229,20 +1242,11 @@ int size, ret;
 int fd = fileno(smtp_in);
 const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
 uschar * iptype;  /* To display debug info */
 int fd = fileno(smtp_in);
 const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
 uschar * iptype;  /* To display debug info */
-struct timeval tv;
-struct timeval tvtmp;
 socklen_t vslen = sizeof(struct timeval);
 BOOL yield = FALSE;
 
 socklen_t vslen = sizeof(struct timeval);
 BOOL yield = FALSE;
 
-/* Save current socket timeout values */
-get_ok = getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tvtmp, &vslen);
-
-/* Proxy Protocol host must send header within a short time
-(default 3 seconds) or it's considered invalid */
-tv.tv_sec  = PROXY_NEGOTIATION_TIMEOUT_SEC;
-tv.tv_usec = PROXY_NEGOTIATION_TIMEOUT_USEC;
-if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tv, sizeof(tv)) < 0)
-  goto bad;
+os_non_restarting_signal(SIGALRM, command_timeout_handler);
+ALARM(proxy_protocol_timeout);
 
 do
   {
 
 do
   {
@@ -1250,9 +1254,9 @@ do
   don't do a PEEK into the data, actually slurp up enough to be
   "safe". Can't take it all because TLS-on-connect clients follow
   immediately with TLS handshake. */
   don't do a PEEK into the data, actually slurp up enough to be
   "safe". Can't take it all because TLS-on-connect clients follow
   immediately with TLS handshake. */
-  ret = recv(fd, &hdr, PROXY_INITIAL_READ, 0);
+  ret = read(fd, &hdr, PROXY_INITIAL_READ);
   }
   }
-  while (ret == -1 && errno == EINTR);
+  while (ret == -1 && errno == EINTR && !had_command_timeout);
 
 if (ret == -1)
   goto proxyfail;
 
 if (ret == -1)
   goto proxyfail;
@@ -1266,8 +1270,8 @@ if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
   /* First get the length fields. */
   do
     {
   /* First get the length fields. */
   do
     {
-    retmore = recv(fd, (uschar*)&hdr + ret, PROXY_V2_HEADER_SIZE - PROXY_INITIAL_READ, 0);
-    } while (retmore == -1 && errno == EINTR);
+    retmore = read(fd, (uschar*)&hdr + ret, PROXY_V2_HEADER_SIZE - PROXY_INITIAL_READ);
+    } while (retmore == -1 && errno == EINTR && !had_command_timeout);
   if (retmore == -1)
     goto proxyfail;
   ret += retmore;
   if (retmore == -1)
     goto proxyfail;
   ret += retmore;
@@ -1303,8 +1307,8 @@ if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
     {
     do
       {
     {
     do
       {
-      retmore = recv(fd, (uschar*)&hdr + ret, size-ret, 0);
-      } while (retmore == -1 && errno == EINTR);
+      retmore = read(fd, (uschar*)&hdr + ret, size-ret);
+      } while (retmore == -1 && errno == EINTR && !had_command_timeout);
     if (retmore == -1)
       goto proxyfail;
     ret += retmore;
     if (retmore == -1)
       goto proxyfail;
     ret += retmore;
@@ -1532,7 +1536,8 @@ done:
 should cause a synchronization failure */
 
 proxyfail:
 should cause a synchronization failure */
 
 proxyfail:
-  restore_socket_timeout(fd, get_ok, &tvtmp, vslen);
+  DEBUG(D_receive) if (had_command_timeout)
+    debug_printf("Timeout while reading proxy header\n");
 
 bad:
   if (yield)
 
 bad:
   if (yield)
@@ -1548,6 +1553,7 @@ bad:
       debug_printf("Failure to extract proxied host, only QUIT allowed\n");
     }
 
       debug_printf("Failure to extract proxied host, only QUIT allowed\n");
     }
 
+ALARM(0);
 return;
 }
 #endif
 return;
 }
 #endif
@@ -1730,6 +1736,7 @@ for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
     return;
 
   case QUIT_CMD:
     return;
 
   case QUIT_CMD:
+    f.smtp_in_quit = TRUE;
     smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
     mac_smtp_fflush();
     return;
     smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
     mac_smtp_fflush();
     return;
@@ -1784,7 +1791,7 @@ return string_sprintf("SMTP connection from %s", hostname);
 
 
 
 
 
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 /* Append TLS-related information to a log line
 
 Arguments:
 /* Append TLS-related information to a log line
 
 Arguments:
@@ -1796,17 +1803,43 @@ static gstring *
 s_tlslog(gstring * g)
 {
 if (LOGGING(tls_cipher) && tls_in.cipher)
 s_tlslog(gstring * g)
 {
 if (LOGGING(tls_cipher) && tls_in.cipher)
+  {
   g = string_append(g, 2, US" X=", tls_in.cipher);
   g = string_append(g, 2, US" X=", tls_in.cipher);
+#ifndef DISABLE_TLS_RESUME
+  if (LOGGING(tls_resumption) && tls_in.resumption & RESUME_USED)
+    g = string_catn(g, US"*", 1);
+#endif
+  }
 if (LOGGING(tls_certificate_verified) && tls_in.cipher)
   g = string_append(g, 2, US" CV=", tls_in.certificate_verified? "yes":"no");
 if (LOGGING(tls_peerdn) && tls_in.peerdn)
   g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\"");
 if (LOGGING(tls_sni) && tls_in.sni)
 if (LOGGING(tls_certificate_verified) && tls_in.cipher)
   g = string_append(g, 2, US" CV=", tls_in.certificate_verified? "yes":"no");
 if (LOGGING(tls_peerdn) && tls_in.peerdn)
   g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\"");
 if (LOGGING(tls_sni) && tls_in.sni)
-  g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\"");
+  g = string_append(g, 2, US" SNI=", string_printing2(tls_in.sni, SP_TAB|SP_SPACE));
 return g;
 }
 #endif
 
 return g;
 }
 #endif
 
+
+
+static gstring *
+s_connhad_log(gstring * g)
+{
+const uschar * sep = smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE
+  ? US" C=..." : US" C=";
+
+for (int i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
+  if (smtp_connection_had[i] != SCH_NONE)
+    {
+    g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
+    sep = US",";
+    }
+for (int i = 0; i < smtp_ch_index; i++, sep = US",")
+  g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
+return g;
+}
+
+
 /*************************************************
 *      Log lack of MAIL if so configured         *
 *************************************************/
 /*************************************************
 *      Log lack of MAIL if so configured         *
 *************************************************/
@@ -1822,7 +1855,7 @@ Returns:     nothing
 void
 smtp_log_no_mail(void)
 {
 void
 smtp_log_no_mail(void)
 {
-uschar * sep, * s;
+uschar * s;
 gstring * g = NULL;
 
 if (smtp_mailcmd_count > 0 || !LOGGING(smtp_no_mail))
 gstring * g = NULL;
 
 if (smtp_mailcmd_count > 0 || !LOGGING(smtp_no_mail))
@@ -1834,24 +1867,11 @@ if (sender_host_authenticated)
   if (authenticated_id) g = string_append(g, 2, US":", authenticated_id);
   }
 
   if (authenticated_id) g = string_append(g, 2, US":", authenticated_id);
   }
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 g = s_tlslog(g);
 #endif
 
 g = s_tlslog(g);
 #endif
 
-sep = smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE ?  US" C=..." : US" C=";
-
-for (int i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
-  if (smtp_connection_had[i] != SCH_NONE)
-    {
-    g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
-    sep = US",";
-    }
-
-for (int i = 0; i < smtp_ch_index; i++)
-  {
-  g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
-  sep = US",";
-  }
+g = s_connhad_log(g);
 
 if (!(s = string_from_gstring(g))) s = US"";
 
 
 if (!(s = string_from_gstring(g))) s = US"";
 
@@ -1910,11 +1930,7 @@ BOOL yield = fl.helo_accept_junk;
 
 /* Discard any previous helo name */
 
 
 /* Discard any previous helo name */
 
-if (sender_helo_name)
-  {
-  store_free(sender_helo_name);
-  sender_helo_name = NULL;
-  }
+sender_helo_name = NULL;
 
 /* Skip tests if junk is permitted. */
 
 
 /* Skip tests if junk is permitted. */
 
@@ -1953,7 +1969,7 @@ if (!yield)
 
 /* Save argument if OK */
 
 
 /* Save argument if OK */
 
-if (yield) sender_helo_name = string_copy_malloc(start);
+if (yield) sender_helo_name = string_copy_perm(start, TRUE);
 return yield;
 }
 
 return yield;
 }
 
@@ -1982,12 +1998,13 @@ extract_option(uschar **name, uschar **value)
 uschar *n;
 uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
 while (isspace(*v)) v--;
 uschar *n;
 uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
 while (isspace(*v)) v--;
-v[1] = 0;
+v[1] = '\0';
 while (v > smtp_cmd_data && *v != '=' && !isspace(*v))
   {
   /* Take care to not stop at a space embedded in a quoted local-part */
 
 while (v > smtp_cmd_data && *v != '=' && !isspace(*v))
   {
   /* Take care to not stop at a space embedded in a quoted local-part */
 
-  if (*v == '"') do v--; while (*v != '"' && v > smtp_cmd_data+1);
+  if ((*v == '"') && (v > smtp_cmd_data + 1))
+    do v--; while (*v != '"' && v > smtp_cmd_data+1);
   v--;
   }
 
   v--;
   }
 
@@ -2025,7 +2042,7 @@ Argument:   the stacking pool storage reset point
 Returns:    nothing
 */
 
 Returns:    nothing
 */
 
-void
+void *
 smtp_reset(void *reset_point)
 {
 recipients_list = NULL;
 smtp_reset(void *reset_point)
 {
 recipients_list = NULL;
@@ -2033,29 +2050,32 @@ rcpt_count = rcpt_defer_count = rcpt_fail_count =
   raw_recipients_count = recipients_count = recipients_list_max = 0;
 message_linecount = 0;
 message_size = -1;
   raw_recipients_count = recipients_count = recipients_list_max = 0;
 message_linecount = 0;
 message_size = -1;
+message_body = message_body_end = NULL;
 acl_added_headers = NULL;
 acl_removed_headers = NULL;
 f.queue_only_policy = FALSE;
 rcpt_smtp_response = NULL;
 fl.rcpt_smtp_response_same = TRUE;
 fl.rcpt_in_progress = FALSE;
 acl_added_headers = NULL;
 acl_removed_headers = NULL;
 f.queue_only_policy = FALSE;
 rcpt_smtp_response = NULL;
 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 */
+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
 #ifdef WITH_CONTENT_SCAN
-f.no_mbox_unspool = FALSE;                             /* Can be set by ACL */
+f.no_mbox_unspool = FALSE;                             /* Can be set by ACL */
 #endif
 #endif
-f.submission_mode = FALSE;                             /* 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.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 */
+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;
 sending_ip_address = NULL;
 return_path = sender_address = NULL;
-sender_data = NULL;                                 /* Can be set by ACL */
+deliver_localpart_data = deliver_domain_data =
+recipient_data = sender_data = NULL;                   /* Can be set by ACL */
+recipient_verify_failure = NULL;
 deliver_localpart_parent = deliver_localpart_orig = NULL;
 deliver_domain_parent = deliver_domain_orig = NULL;
 callout_address = 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 */
+submission_name = NULL;                                        /* Can be set by ACL */
 raw_sender = NULL;                  /* After SMTP rewrite, before qualifying */
 sender_address_unrewritten = NULL;  /* Set only after verify rewrite */
 sender_verified_list = NULL;        /* No senders verified */
 raw_sender = NULL;                  /* After SMTP rewrite, before qualifying */
 sender_address_unrewritten = NULL;  /* Set only after verify rewrite */
 sender_verified_list = NULL;        /* No senders verified */
@@ -2081,13 +2101,14 @@ dkim_collect_input = 0;
 dkim_verify_overall = dkim_verify_status = dkim_verify_reason = NULL;
 dkim_key_length = 0;
 #endif
 dkim_verify_overall = dkim_verify_status = dkim_verify_reason = NULL;
 dkim_key_length = 0;
 #endif
-#ifdef EXPERIMENTAL_DMARC
+#ifdef SUPPORT_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;
 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;
+arc_received_instance = 0;
 #endif
 dsn_ret = 0;
 dsn_envid = NULL;
 #endif
 dsn_ret = 0;
 dsn_envid = NULL;
@@ -2108,23 +2129,7 @@ ratelimiters_mail = NULL;           /* Updated by ratelimit ACL condition */
 
 acl_var_m = NULL;
 
 
 acl_var_m = NULL;
 
-/* The message body variables use malloc store. They may be set if this is
-not the first message in an SMTP session and the previous message caused them
-to be referenced in an ACL. */
-
-if (message_body)
-  {
-  store_free(message_body);
-  message_body = NULL;
-  }
-
-if (message_body_end)
-  {
-  store_free(message_body_end);
-  message_body_end = NULL;
-  }
-
-/* Warning log messages are also saved in malloc store. They are saved to avoid
+/* Warning log messages are saved in malloc store. They are saved to avoid
 repetition in the same message, but it seems right to repeat them for different
 messages. */
 
 repetition in the same message, but it seems right to repeat them for different
 messages. */
 
@@ -2134,7 +2139,12 @@ while (acl_warn_logged)
   acl_warn_logged = acl_warn_logged->next;
   store_free(this);
   }
   acl_warn_logged = acl_warn_logged->next;
   store_free(this);
   }
+
+message_tidyup();
 store_reset(reset_point);
 store_reset(reset_point);
+
+message_start();
+return store_mark();
 }
 
 
 }
 
 
@@ -2162,7 +2172,7 @@ static int
 smtp_setup_batch_msg(void)
 {
 int done = 0;
 smtp_setup_batch_msg(void)
 {
 int done = 0;
-void *reset_point = store_get(0);
+rmark reset_point = store_mark();
 
 /* Save the line count at the start of each transaction - single commands
 like HELO and RSET count as whole transactions. */
 
 /* Save the line count at the start of each transaction - single commands
 like HELO and RSET count as whole transactions. */
@@ -2172,7 +2182,7 @@ bsmtp_transaction_linecount = receive_linecount;
 if ((receive_feof)()) return 0;   /* Treat EOF as QUIT */
 
 cancel_cutthrough_connection(TRUE, US"smtp_setup_batch_msg");
 if ((receive_feof)()) return 0;   /* Treat EOF as QUIT */
 
 cancel_cutthrough_connection(TRUE, US"smtp_setup_batch_msg");
-smtp_reset(reset_point);                /* Reset for start of message */
+reset_point = smtp_reset(reset_point);                /* Reset for start of message */
 
 /* Deal with SMTP commands. This loop is exited by setting done to a POSITIVE
 value. The values are 2 larger than the required yield of the function. */
 
 /* Deal with SMTP commands. This loop is exited by setting done to a POSITIVE
 value. The values are 2 larger than the required yield of the function. */
@@ -2197,7 +2207,7 @@ while (done <= 0)
 
     case RSET_CMD:
       cancel_cutthrough_connection(TRUE, US"RSET received");
 
     case RSET_CMD:
       cancel_cutthrough_connection(TRUE, US"RSET received");
-      smtp_reset(reset_point);
+      reset_point = smtp_reset(reset_point);
       bsmtp_transaction_linecount = receive_linecount;
       break;
 
       bsmtp_transaction_linecount = receive_linecount;
       break;
 
@@ -2210,7 +2220,7 @@ while (done <= 0)
 
     case MAIL_CMD:
       smtp_mailcmd_count++;              /* Count for no-mail log */
 
     case MAIL_CMD:
       smtp_mailcmd_count++;              /* Count for no-mail log */
-      if (sender_address != NULL)
+      if (sender_address)
        /* The function moan_smtp_batch() does not return. */
        moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given");
 
        /* The function moan_smtp_batch() does not return. */
        moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given");
 
@@ -2221,13 +2231,15 @@ while (done <= 0)
       /* Reset to start of message */
 
       cancel_cutthrough_connection(TRUE, US"MAIL received");
       /* Reset to start of message */
 
       cancel_cutthrough_connection(TRUE, US"MAIL received");
-      smtp_reset(reset_point);
+      reset_point = smtp_reset(reset_point);
 
       /* Apply SMTP rewrite */
 
 
       /* Apply SMTP rewrite */
 
-      raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)?
-       rewrite_one(smtp_cmd_data, rewrite_smtp|rewrite_smtp_sender, NULL, FALSE,
-         US"", global_rewrite_rules) : smtp_cmd_data;
+      raw_sender = rewrite_existflags & rewrite_smtp
+       /* deconst ok as smtp_cmd_data was not const */
+        ? US rewrite_one(smtp_cmd_data, rewrite_smtp|rewrite_smtp_sender, NULL,
+                     FALSE, US"", global_rewrite_rules)
+       : smtp_cmd_data;
 
       /* Extract the address; the TRUE flag allows <> as valid */
 
 
       /* Extract the address; the TRUE flag allows <> as valid */
 
@@ -2247,7 +2259,8 @@ while (done <= 0)
          && sender_address[0] != 0 && sender_address[0] != '@')
        if (f.allow_unqualified_sender)
          {
          && sender_address[0] != 0 && sender_address[0] != '@')
        if (f.allow_unqualified_sender)
          {
-         sender_address = rewrite_address_qualify(sender_address, FALSE);
+         /* deconst ok as sender_address was not const */
+         sender_address = US rewrite_address_qualify(sender_address, FALSE);
          DEBUG(D_receive) debug_printf("unqualified address %s accepted "
            "and rewritten\n", raw_sender);
          }
          DEBUG(D_receive) debug_printf("unqualified address %s accepted "
            "and rewritten\n", raw_sender);
          }
@@ -2286,7 +2299,8 @@ while (done <= 0)
       recipient address */
 
       recipient = rewrite_existflags & rewrite_smtp
       recipient address */
 
       recipient = rewrite_existflags & rewrite_smtp
-       ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+       /* deconst ok as smtp_cmd_data was not const */
+       ? US rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
                      global_rewrite_rules)
        : smtp_cmd_data;
 
                      global_rewrite_rules)
        : smtp_cmd_data;
 
@@ -2305,7 +2319,8 @@ while (done <= 0)
          {
          DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
            recipient);
          {
          DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
            recipient);
-         recipient = rewrite_address_qualify(recipient, TRUE);
+         /* deconst ok as recipient was not const */
+         recipient = US rewrite_address_qualify(recipient, TRUE);
          }
        /* The function moan_smtp_batch() does not return. */
        else
          }
        /* The function moan_smtp_batch() does not return. */
        else
@@ -2348,8 +2363,9 @@ while (done <= 0)
       break;
 
 
       break;
 
 
-    case EOF_CMD:
     case QUIT_CMD:
     case QUIT_CMD:
+      f.smtp_in_quit = TRUE;
+    case EOF_CMD:
       done = 2;
       break;
 
       done = 2;
       break;
 
@@ -2379,7 +2395,7 @@ return done - 2;  /* Convert yield values */
 
 
 
 
 
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 static BOOL
 smtp_log_tls_fail(uschar * errstr)
 {
 static BOOL
 smtp_log_tls_fail(uschar * errstr)
 {
@@ -2400,24 +2416,47 @@ return FALSE;
 static void
 tfo_in_check(void)
 {
 static void
 tfo_in_check(void)
 {
-# ifdef TCP_INFO
+# ifdef __FreeBSD__
+int is_fastopen;
+socklen_t len = sizeof(is_fastopen);
+
+/* The tinfo TCPOPT_FAST_OPEN bit seems unreliable, and we don't see state
+TCP_SYN_RCV (as of 12.1) so no idea about data-use. */
+
+if (getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_FASTOPEN, &is_fastopen, &len) == 0)
+  {
+  if (is_fastopen)
+    {
+    DEBUG(D_receive)
+      debug_printf("TFO mode connection (TCP_FASTOPEN getsockopt)\n");
+    f.tcp_in_fastopen = TRUE;
+    }
+  }
+else DEBUG(D_receive)
+  debug_printf("TCP_INFO getsockopt: %s\n", strerror(errno));
+
+# elif defined(TCP_INFO)
 struct tcp_info tinfo;
 socklen_t len = sizeof(tinfo);
 
 if (getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0)
 struct tcp_info tinfo;
 socklen_t len = sizeof(tinfo);
 
 if (getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0)
-#ifdef TCPI_OPT_SYN_DATA       /* FreeBSD 11 does not seem to have this yet */
+#  ifdef TCPI_OPT_SYN_DATA     /* FreeBSD 11,12 do not seem to have this yet */
   if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA)
     {
   if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA)
     {
-    DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (ACKd data-on-SYN)\n");
+    DEBUG(D_receive)
+      debug_printf("TFO mode connection (ACKd data-on-SYN)\n");
     f.tcp_in_fastopen_data = f.tcp_in_fastopen = TRUE;
     }
   else
     f.tcp_in_fastopen_data = f.tcp_in_fastopen = TRUE;
     }
   else
-#endif
-    if (tinfo.tcpi_state == TCP_SYN_RECV)
+#  endif
+    if (tinfo.tcpi_state == TCP_SYN_RECV)      /* Not seen on FreeBSD 12.1 */
     {
     {
-    DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (state TCP_SYN_RECV)\n");
+    DEBUG(D_receive)
+      debug_printf("TFO mode connection (state TCP_SYN_RECV)\n");
     f.tcp_in_fastopen = TRUE;
     }
     f.tcp_in_fastopen = TRUE;
     }
+else DEBUG(D_receive)
+  debug_printf("TCP_INFO getsockopt: %s\n", strerror(errno));
 # endif
 }
 #endif
 # endif
 }
 #endif
@@ -2470,15 +2509,12 @@ if (!host_checking && !f.sender_host_notsocket)
   sender_host_auth_pubname = sender_host_authenticated = NULL;
 authenticated_by = NULL;
 
   sender_host_auth_pubname = sender_host_authenticated = NULL;
 authenticated_by = NULL;
 
-#ifdef SUPPORT_TLS
-tls_in.cipher = tls_in.peerdn = NULL;
+#ifndef DISABLE_TLS
+tls_in.ver = tls_in.cipher = tls_in.peerdn = NULL;
 tls_in.ourcert = tls_in.peercert = NULL;
 tls_in.sni = NULL;
 tls_in.ocsp = OCSP_NOT_REQ;
 fl.tls_advertised = FALSE;
 tls_in.ourcert = tls_in.peercert = NULL;
 tls_in.sni = NULL;
 tls_in.ocsp = OCSP_NOT_REQ;
 fl.tls_advertised = FALSE;
-# ifdef EXPERIMENTAL_REQUIRETLS
-fl.requiretls_advertised = FALSE;
-# endif
 #endif
 fl.dsn_advertised = FALSE;
 #ifdef SUPPORT_I18N
 #endif
 fl.dsn_advertised = FALSE;
 #ifdef SUPPORT_I18N
@@ -2489,11 +2525,9 @@ fl.smtputf8_advertised = FALSE;
 
 acl_var_c = NULL;
 
 
 acl_var_c = NULL;
 
-/* Allow for trailing 0 in the command and data buffers. */
+/* Allow for trailing 0 in the command and data buffers.  Tainted. */
 
 
-if (!(smtp_cmd_buffer = US malloc(2*SMTP_CMD_BUFFER_SIZE + 2)))
-  log_write(0, LOG_MAIN|LOG_PANIC_DIE,
-    "malloc() failed for SMTP command buffer");
+smtp_cmd_buffer = store_get_perm(2*SMTP_CMD_BUFFER_SIZE + 2, TRUE);
 
 smtp_cmd_buffer[0] = 0;
 smtp_data_buffer = smtp_cmd_buffer + SMTP_CMD_BUFFER_SIZE + 1;
 
 smtp_cmd_buffer[0] = 0;
 smtp_data_buffer = smtp_cmd_buffer + SMTP_CMD_BUFFER_SIZE + 1;
@@ -2528,6 +2562,9 @@ receive_ungetc = smtp_ungetc;
 receive_feof = smtp_feof;
 receive_ferror = smtp_ferror;
 receive_smtp_buffered = smtp_buffered;
 receive_feof = smtp_feof;
 receive_ferror = smtp_ferror;
 receive_smtp_buffered = smtp_buffered;
+lwr_receive_getc = NULL;
+lwr_receive_getbuf = NULL;
+lwr_receive_ungetc = NULL;
 smtp_inptr = smtp_inend = smtp_inbuffer;
 smtp_had_eof = smtp_had_error = 0;
 
 smtp_inptr = smtp_inend = smtp_inbuffer;
 smtp_had_eof = smtp_had_error = 0;
 
@@ -2604,7 +2641,7 @@ if (!f.sender_host_unknown)
     {
     #if OPTSTYLE == 1
     EXIM_SOCKLEN_T optlen = sizeof(struct ip_options) + MAX_IPOPTLEN;
     {
     #if OPTSTYLE == 1
     EXIM_SOCKLEN_T optlen = sizeof(struct ip_options) + MAX_IPOPTLEN;
-    struct ip_options *ipopt = store_get(optlen);
+    struct ip_options *ipopt = store_get(optlen, FALSE);
     #elif OPTSTYLE == 2
     struct ip_opts ipoptblock;
     struct ip_opts *ipopt = &ipoptblock;
     #elif OPTSTYLE == 2
     struct ip_opts ipoptblock;
     struct ip_opts *ipopt = &ipoptblock;
@@ -2898,7 +2935,7 @@ if (!f.sender_host_unknown)
 if (smtp_batched_input) return TRUE;
 
 /* If valid Proxy Protocol source is connecting, set up session.
 if (smtp_batched_input) return TRUE;
 
 /* If valid Proxy Protocol source is connecting, set up session.
- * Failure will not allow any SMTP function other than QUIT. */
+Failure will not allow any SMTP function other than QUIT. */
 
 #ifdef SUPPORT_PROXY
 proxy_session = FALSE;
 
 #ifdef SUPPORT_PROXY
 proxy_session = FALSE;
@@ -2907,16 +2944,16 @@ if (check_proxy_protocol_host())
   setup_proxy_protocol_host();
 #endif
 
   setup_proxy_protocol_host();
 #endif
 
-  /* Start up TLS if tls_on_connect is set. This is for supporting the legacy
-  smtps port for use with older style SSL MTAs. */
+/* Start up TLS if tls_on_connect is set. This is for supporting the legacy
+smtps port for use with older style SSL MTAs. */
 
 
-#ifdef SUPPORT_TLS
-  if (tls_in.on_connect)
-    {
-    if (tls_server_start(tls_require_ciphers, &user_msg) != OK)
-      return smtp_log_tls_fail(user_msg);
-    cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
-    }
+#ifndef DISABLE_TLS
+if (tls_in.on_connect)
+  {
+  if (tls_server_start(&user_msg) != OK)
+    return smtp_log_tls_fail(user_msg);
+  cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
+  }
 #endif
 
 /* Run the connect ACL if it exists */
 #endif
 
 /* Run the connect ACL if it exists */
@@ -3002,7 +3039,7 @@ while (*p);
 /* Before we write the banner, check that there is no input pending, unless
 this synchronisation check is disabled. */
 
 /* Before we write the banner, check that there is no input pending, unless
 this synchronisation check is disabled. */
 
-#ifdef EXPERIMENTAL_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
 fl.pipe_connect_acceptable =
   sender_host_address && verify_check_host(&pipe_connect_advertise_hosts) == OK;
 
 fl.pipe_connect_acceptable =
   sender_host_address && verify_check_host(&pipe_connect_advertise_hosts) == OK;
 
@@ -3015,7 +3052,7 @@ if (!check_sync())
 #endif
     {
     unsigned n = smtp_inend - smtp_inptr;
 #endif
     {
     unsigned n = smtp_inend - smtp_inptr;
-    if (n > 32) n = 32;
+    if (n > 128) n = 128;
 
     log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
       "synchronization error (input sent without waiting for greeting): "
 
     log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
       "synchronization error (input sent without waiting for greeting): "
@@ -3029,7 +3066,7 @@ if (!check_sync())
 /*XXX the ehlo-resp code does its own tls/nontls bit.  Maybe subroutine that? */
 
 smtp_printf("%s",
 /*XXX the ehlo-resp code does its own tls/nontls bit.  Maybe subroutine that? */
 
 smtp_printf("%s",
-#ifdef EXPERIMENTAL_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
   fl.pipe_connect_acceptable && pipeline_connect_sends(),
 #else
   FALSE,
   fl.pipe_connect_acceptable && pipeline_connect_sends(),
 #else
   FALSE,
@@ -3040,7 +3077,7 @@ smtp_printf("%s",
 handshake arrived.  If so we must have managed a TFO. */
 
 #ifdef TCP_FASTOPEN
 handshake arrived.  If so we must have managed a TFO. */
 
 #ifdef TCP_FASTOPEN
-tfo_in_check();
+if (sender_host_address && !f.sender_host_notsocket) tfo_in_check();
 #endif
 
 return TRUE;
 #endif
 
 return TRUE;
@@ -3076,15 +3113,17 @@ synprot_error(int type, int code, uschar *data, uschar *errmess)
 int yield = -1;
 
 log_write(type, LOG_MAIN, "SMTP %s error in \"%s\" %s %s",
 int yield = -1;
 
 log_write(type, LOG_MAIN, "SMTP %s error in \"%s\" %s %s",
-  (type == L_smtp_syntax_error)? "syntax" : "protocol",
+  type == L_smtp_syntax_error ? "syntax" : "protocol",
   string_printing(smtp_cmd_buffer), host_and_ident(TRUE), errmess);
 
 if (++synprot_error_count > smtp_max_synprot_errors)
   {
   yield = 1;
   log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
   string_printing(smtp_cmd_buffer), host_and_ident(TRUE), errmess);
 
 if (++synprot_error_count > smtp_max_synprot_errors)
   {
   yield = 1;
   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));
+    "syntax or protocol errors (last command was \"%s\", %s)",
+    host_and_ident(FALSE), string_printing(smtp_cmd_buffer),
+    string_from_gstring(s_connhad_log(NULL))
+    );
   }
 
 if (code > 0)
   }
 
 if (code > 0)
@@ -3170,7 +3209,7 @@ for (;;)
     {
     smtp_printf("%.3s-%.*s%.*s\r\n", TRUE, code, esclen, esc, (int)(nl - msg), msg);
     msg = nl + 1;
     {
     smtp_printf("%.3s-%.*s%.*s\r\n", TRUE, code, esclen, esc, (int)(nl - msg), msg);
     msg = nl + 1;
-    while (isspace(*msg)) msg++;
+    Uskip_whitespace(&msg);
     }
   }
 }
     }
   }
 }
@@ -3282,18 +3321,7 @@ int codelen = 3;
 uschar *smtp_code;
 uschar *lognl;
 uschar *sender_info = US"";
 uschar *smtp_code;
 uschar *lognl;
 uschar *sender_info = US"";
-uschar *what =
-#ifdef WITH_CONTENT_SCAN
-  where == ACL_WHERE_MIME ? US"during MIME ACL checks" :
-#endif
-  where == ACL_WHERE_PREDATA ? US"DATA" :
-  where == ACL_WHERE_DATA ? US"after DATA" :
-#ifndef DISABLE_PRDR
-  where == ACL_WHERE_PRDR ? US"after DATA PRDR" :
-#endif
-  smtp_cmd_data ?
-    string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data) :
-    string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]);
+uschar *what;
 
 if (drop) rc = FAIL;
 
 
 if (drop) rc = FAIL;
 
@@ -3309,19 +3337,45 @@ fixed, sender_address at this point became the rewritten address. I'm not sure
 this is what should be logged, so I've changed to logging the unrewritten
 address to retain backward compatibility. */
 
 this is what should be logged, so I've changed to logging the unrewritten
 address to retain backward compatibility. */
 
-#ifndef WITH_CONTENT_SCAN
-if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA)
-#else
-if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA || where == ACL_WHERE_MIME)
+switch (where)
+  {
+#ifdef WITH_CONTENT_SCAN
+  case ACL_WHERE_MIME:         what = US"during MIME ACL checks";      break;
+#endif
+  case ACL_WHERE_PREDATA:      what = US"DATA";                        break;
+  case ACL_WHERE_DATA:         what = US"after DATA";                  break;
+#ifndef DISABLE_PRDR
+  case ACL_WHERE_PRDR:         what = US"after DATA PRDR";             break;
 #endif
 #endif
+  default:
+    {
+    uschar * place = smtp_cmd_data ? smtp_cmd_data : US"in \"connect\" ACL";
+    int lim = 100;
+
+    if (where == ACL_WHERE_AUTH)       /* avoid logging auth creds */
+      {
+      uschar * s;
+      for (s = smtp_cmd_data; *s && !isspace(*s); ) s++;
+      lim = s - smtp_cmd_data; /* atop after method */
+      }
+    what = string_sprintf("%s %.*s", acl_wherenames[where], lim, place);
+    }
+  }
+switch (where)
   {
   {
-  sender_info = string_sprintf("F=<%s>%s%s%s%s ",
-    sender_address_unrewritten ? sender_address_unrewritten : sender_address,
-    sender_host_authenticated ? US" A="                                    : US"",
-    sender_host_authenticated ? sender_host_authenticated                  : US"",
-    sender_host_authenticated && authenticated_id ? US":"                  : US"",
-    sender_host_authenticated && authenticated_id ? authenticated_id       : US""
-    );
+  case ACL_WHERE_RCPT:
+  case ACL_WHERE_DATA:
+#ifdef WITH_CONTENT_SCAN
+  case ACL_WHERE_MIME:
+#endif
+    sender_info = string_sprintf("F=<%s>%s%s%s%s ",
+      sender_address_unrewritten ? sender_address_unrewritten : sender_address,
+      sender_host_authenticated ? US" A="                                    : US"",
+      sender_host_authenticated ? sender_host_authenticated                  : US"",
+      sender_host_authenticated && authenticated_id ? US":"                  : US"",
+      sender_host_authenticated && authenticated_id ? authenticated_id       : US""
+      );
+  break;
   }
 
 /* If there's been a sender verification failure with a specific message, and
   }
 
 /* If there's been a sender verification failure with a specific message, and
@@ -3411,7 +3465,7 @@ is closing if required and return 2.  */
 
 if (log_reject_target != 0)
   {
 
 if (log_reject_target != 0)
   {
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
   gstring * g = s_tlslog(NULL);
   uschar * tls = string_from_gstring(g);
   if (!tls) tls = US"";
   gstring * g = s_tlslog(NULL);
   uschar * tls = string_from_gstring(g);
   if (!tls) tls = US"";
@@ -3513,7 +3567,7 @@ if (code && defaultrespond)
     va_list ap;
 
     va_start(ap, defaultrespond);
     va_list ap;
 
     va_start(ap, defaultrespond);
-    g = string_vformat(NULL, TRUE, CS defaultrespond, ap);
+    g = string_vformat(NULL, SVFMT_EXTEND|SVFMT_REBUFFER, CS defaultrespond, ap);
     va_end(ap);
     smtp_printf("%s %s\r\n", FALSE, code, string_from_gstring(g));
     }
     va_end(ap);
     smtp_printf("%s %s\r\n", FALSE, code, string_from_gstring(g));
     }
@@ -3724,60 +3778,60 @@ if (rc != OK)
 switch(rc)
   {
   case OK:
 switch(rc)
   {
   case OK:
-  if (!au->set_id || set_id)    /* Complete success */
-    {
-    if (set_id) authenticated_id = string_copy_malloc(set_id);
-    sender_host_authenticated = au->name;
-    sender_host_auth_pubname  = au->public_name;
-    authentication_failed = FALSE;
-    authenticated_fail_id = NULL;   /* Impossible to already be set? */
-
-    received_protocol =
-      (sender_host_address ? protocols : protocols_local)
-       [pextend + pauthed + (tls_in.active.sock >= 0 ? pcrpted:0)];
-    *s = *ss = US"235 Authentication succeeded";
-    authenticated_by = au;
-    break;
-    }
+    if (!au->set_id || set_id)    /* Complete success */
+      {
+      if (set_id) authenticated_id = string_copy_perm(set_id, TRUE);
+      sender_host_authenticated = au->name;
+      sender_host_auth_pubname  = au->public_name;
+      authentication_failed = FALSE;
+      authenticated_fail_id = NULL;   /* Impossible to already be set? */
 
 
-  /* Authentication succeeded, but we failed to expand the set_id string.
-  Treat this as a temporary error. */
+      received_protocol =
+       (sender_host_address ? protocols : protocols_local)
+         [pextend + pauthed + (tls_in.active.sock >= 0 ? pcrpted:0)];
+      *s = *ss = US"235 Authentication succeeded";
+      authenticated_by = au;
+      break;
+      }
 
 
-  auth_defer_msg = expand_string_message;
-  /* Fall through */
+    /* Authentication succeeded, but we failed to expand the set_id string.
+    Treat this as a temporary error. */
+
+    auth_defer_msg = expand_string_message;
+    /* Fall through */
 
   case DEFER:
 
   case DEFER:
-  if (set_id) authenticated_fail_id = string_copy_malloc(set_id);
-  *s = string_sprintf("435 Unable to authenticate at present%s",
-    auth_defer_user_msg);
-  *ss = string_sprintf("435 Unable to authenticate at present%s: %s",
-    set_id, auth_defer_msg);
-  break;
+    if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE);
+    *s = string_sprintf("435 Unable to authenticate at present%s",
+      auth_defer_user_msg);
+    *ss = string_sprintf("435 Unable to authenticate at present%s: %s",
+      set_id, auth_defer_msg);
+    break;
 
   case BAD64:
 
   case BAD64:
-  *s = *ss = US"501 Invalid base64 data";
-  break;
+    *s = *ss = US"501 Invalid base64 data";
+    break;
 
   case CANCELLED:
 
   case CANCELLED:
-  *s = *ss = US"501 Authentication cancelled";
-  break;
+    *s = *ss = US"501 Authentication cancelled";
+    break;
 
   case UNEXPECTED:
 
   case UNEXPECTED:
-  *s = *ss = US"553 Initial data not expected";
-  break;
+    *s = *ss = US"553 Initial data not expected";
+    break;
 
   case FAIL:
 
   case FAIL:
-  if (set_id) authenticated_fail_id = string_copy_malloc(set_id);
-  *s = US"535 Incorrect authentication data";
-  *ss = string_sprintf("535 Incorrect authentication data%s", set_id);
-  break;
+    if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE);
+    *s = US"535 Incorrect authentication data";
+    *ss = string_sprintf("535 Incorrect authentication data%s", set_id);
+    break;
 
   default:
 
   default:
-  if (set_id) authenticated_fail_id = string_copy_malloc(set_id);
-  *s = US"435 Internal error";
-  *ss = string_sprintf("435 Internal error%s: return %d from authentication "
-    "check", set_id, rc);
-  break;
+    if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE);
+    *s = US"435 Internal error";
+    *ss = string_sprintf("435 Internal error%s: return %d from authentication "
+      "check", set_id, rc);
+    break;
   }
 
 return rc;
   }
 
 return rc;
@@ -3796,7 +3850,8 @@ if (f.allow_unqualified_recipient || strcmpic(*recipient, US"postmaster") == 0)
   DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
     *recipient);
   rd = Ustrlen(recipient) + 1;
   DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
     *recipient);
   rd = Ustrlen(recipient) + 1;
-  *recipient = rewrite_address_qualify(*recipient, TRUE);
+  /* deconst ok as *recipient was not const */
+  *recipient = US rewrite_address_qualify(*recipient, TRUE);
   return rd;
   }
 smtp_printf("501 %s: recipient address must contain a domain\r\n", FALSE,
   return rd;
   }
 smtp_printf("501 %s: recipient address must contain a domain\r\n", FALSE,
@@ -3814,25 +3869,52 @@ static void
 smtp_quit_handler(uschar ** user_msgp, uschar ** log_msgp)
 {
 HAD(SCH_QUIT);
 smtp_quit_handler(uschar ** user_msgp, uschar ** log_msgp)
 {
 HAD(SCH_QUIT);
+f.smtp_in_quit = TRUE;
 incomplete_transaction_log(US"QUIT");
 incomplete_transaction_log(US"QUIT");
-if (acl_smtp_quit)
-  {
-  int rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, user_msgp, log_msgp);
-  if (rc == ERROR)
+if (  acl_smtp_quit
+   && acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, user_msgp, log_msgp)
+       == ERROR)
     log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
       *log_msgp);
     log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
       *log_msgp);
-  }
+
+#ifdef EXIM_TCP_CORK
+(void) setsockopt(fileno(smtp_out), IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
+#endif
+
 if (*user_msgp)
   smtp_respond(US"221", 3, TRUE, *user_msgp);
 else
   smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
 
 if (*user_msgp)
   smtp_respond(US"221", 3, TRUE, *user_msgp);
 else
   smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
 
-#ifdef SUPPORT_TLS
+#ifdef SERVERSIDE_CLOSE_NOWAIT
+# ifndef DISABLE_TLS
 tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
 tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
-#endif
+# endif
 
 log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
   smtp_get_connection_info());
 
 log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
   smtp_get_connection_info());
+#else
+
+# ifndef DISABLE_TLS
+tls_close(NULL, TLS_SHUTDOWN_WAIT);
+# endif
+
+log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
+  smtp_get_connection_info());
+
+/* Pause, hoping client will FIN first so that they get the TIME_WAIT.
+The socket should become readble (though with no data) */
+
+  {
+  int fd = fileno(smtp_in);
+  fd_set fds;
+  struct timeval t_limit = {.tv_sec = 0, .tv_usec = 200*1000};
+
+  FD_ZERO(&fds);
+  FD_SET(fd, &fds);
+  (void) select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &t_limit);
+  }
+#endif /*!DAEMON_CLOSE_NOWAIT*/
 }
 
 
 }
 
 
@@ -3846,6 +3928,13 @@ cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
 }
 
 
 }
 
 
+static int
+expand_mailmax(const uschar * s)
+{
+if (!(s = expand_cstring(s)))
+  log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand smtp_accept_max_per_connection");
+return *s ? Uatoi(s) : 0;
+}
 
 /*************************************************
 *       Initialize for SMTP incoming message     *
 
 /*************************************************
 *       Initialize for SMTP incoming message     *
@@ -3876,11 +3965,12 @@ int
 smtp_setup_msg(void)
 {
 int done = 0;
 smtp_setup_msg(void)
 {
 int done = 0;
+int mailmax = -1;
 BOOL toomany = FALSE;
 BOOL discarded = FALSE;
 BOOL last_was_rej_mail = FALSE;
 BOOL last_was_rcpt = FALSE;
 BOOL toomany = FALSE;
 BOOL discarded = FALSE;
 BOOL last_was_rej_mail = FALSE;
 BOOL last_was_rcpt = FALSE;
-void *reset_point = store_get(0);
+rmark reset_point = store_mark();
 
 DEBUG(D_receive) debug_printf("smtp_setup_msg entered\n");
 
 
 DEBUG(D_receive) debug_printf("smtp_setup_msg entered\n");
 
@@ -3890,7 +3980,7 @@ message. Ditto for EHLO/HELO and for STARTTLS, to allow for going in and out of
 TLS between messages (an Exim client may do this if it has messages queued up
 for the host). Note: we do NOT reset AUTH at this point. */
 
 TLS between messages (an Exim client may do this if it has messages queued up
 for the host). Note: we do NOT reset AUTH at this point. */
 
-smtp_reset(reset_point);
+reset_point = smtp_reset(reset_point);
 message_ended = END_NOTSTARTED;
 
 chunking_state = f.chunking_offered ? CHUNKING_OFFERED : CHUNKING_NOT_OFFERED;
 message_ended = END_NOTSTARTED;
 
 chunking_state = f.chunking_offered ? CHUNKING_OFFERED : CHUNKING_NOT_OFFERED;
@@ -3898,10 +3988,18 @@ 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;
 cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
 cmd_list[CMD_LIST_RSET].is_mail_cmd = TRUE;
 cmd_list[CMD_LIST_HELO].is_mail_cmd = TRUE;
 cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE;
 #endif
 
 cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE;
 #endif
 
+if (lwr_receive_getc != NULL)
+  {
+  /* This should have already happened, but if we've gotten confused,
+  force a reset here. */
+  DEBUG(D_receive) debug_printf("WARNING: smtp_setup_msg had to restore receive functions to lowers\n");
+  bdat_pop_receive_functions();
+  }
+
 /* Set the local signal handler for SIGTERM - it tries to end off tidily */
 
 had_command_sigterm = 0;
 /* Set the local signal handler for SIGTERM - it tries to end off tidily */
 
 had_command_sigterm = 0;
@@ -3911,6 +4009,12 @@ os_non_restarting_signal(SIGTERM, command_sigterm_handler);
 
 if (smtp_batched_input) return smtp_setup_batch_msg();
 
 
 if (smtp_batched_input) return smtp_setup_batch_msg();
 
+#ifdef TCP_QUICKACK
+if (smtp_in)           /* Avoid pure-ACKs while in cmd pingpong phase */
+  (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
+         US &off, sizeof(off));
+#endif
+
 /* Deal with SMTP commands. This loop is exited by setting done to a POSITIVE
 value. The values are 2 larger than the required yield of the function. */
 
 /* Deal with SMTP commands. This loop is exited by setting done to a POSITIVE
 value. The values are 2 larger than the required yield of the function. */
 
@@ -3968,14 +4072,8 @@ while (done <= 0)
     }
 #endif
 
     }
 #endif
 
-#ifdef TCP_QUICKACK
-  if (smtp_in)         /* Avoid pure-ACKs while in cmd pingpong phase */
-    (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
-           US &off, sizeof(off));
-#endif
-
   switch(smtp_read_command(
   switch(smtp_read_command(
-#ifdef EXPERIMENTAL_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
          !fl.pipe_connect_acceptable,
 #else
          TRUE,
          !fl.pipe_connect_acceptable,
 #else
          TRUE,
@@ -4034,21 +4132,18 @@ while (done <= 0)
       /* Find the name of the requested authentication mechanism. */
 
       s = smtp_cmd_data;
       /* Find the name of the requested authentication mechanism. */
 
       s = smtp_cmd_data;
-      while ((c = *smtp_cmd_data) != 0 && !isspace(c))
-       {
+      for (; (c = *smtp_cmd_data) && !isspace(c); smtp_cmd_data++)
        if (!isalnum(c) && c != '-' && c != '_')
          {
          done = synprot_error(L_smtp_syntax_error, 501, NULL,
            US"invalid character in authentication mechanism name");
          goto COMMAND_LOOP;
          }
        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)
+      if (*smtp_cmd_data)
        {
        *smtp_cmd_data++ = 0;
        while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
        {
        *smtp_cmd_data++ = 0;
        while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
@@ -4127,8 +4222,10 @@ while (done <= 0)
        if (++synprot_error_count > smtp_max_synprot_errors)
          {
          log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
        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));
+           "syntax or protocol errors (last command was \"%s\", %s)",
+           host_and_ident(FALSE), string_printing(smtp_cmd_buffer),
+           string_from_gstring(s_connhad_log(NULL))
+           );
          done = 1;
          }
 
          done = 1;
          }
 
@@ -4196,7 +4293,7 @@ while (done <= 0)
 
 #ifdef SUPPORT_SPF
       /* set up SPF context */
 
 #ifdef SUPPORT_SPF
       /* set up SPF context */
-      spf_init(sender_helo_name, sender_host_address);
+      spf_conn_init(sender_helo_name, sender_host_address);
 #endif
 
       /* Apply an ACL check if one is defined; afterwards, recheck
 #endif
 
       /* Apply an ACL check if one is defined; afterwards, recheck
@@ -4207,15 +4304,11 @@ while (done <= 0)
                  &user_msg, &log_msg)) != OK)
          {
          done = smtp_handle_acl_fail(ACL_WHERE_HELO, rc, user_msg, log_msg);
                  &user_msg, &log_msg)) != OK)
          {
          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;
-           }
+         sender_helo_name = NULL;
          host_build_sender_fullhost();  /* Rebuild */
          break;
          }
          host_build_sender_fullhost();  /* Rebuild */
          break;
          }
-#ifdef EXPERIMENTAL_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
        else if (!fl.pipe_connect_acceptable && !check_sync())
 #else
        else if (!check_sync())
        else if (!fl.pipe_connect_acceptable && !check_sync())
 #else
        else if (!check_sync())
@@ -4230,21 +4323,23 @@ while (done <= 0)
 
       fl.auth_advertised = FALSE;
       f.smtp_in_pipelining_advertised = FALSE;
 
       fl.auth_advertised = FALSE;
       f.smtp_in_pipelining_advertised = FALSE;
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
       fl.tls_advertised = FALSE;
       fl.tls_advertised = FALSE;
-# ifdef EXPERIMENTAL_REQUIRETLS
-      fl.requiretls_advertised = FALSE;
-# endif
 #endif
       fl.dsn_advertised = FALSE;
 #ifdef SUPPORT_I18N
       fl.smtputf8_advertised = FALSE;
 #endif
 
 #endif
       fl.dsn_advertised = FALSE;
 #ifdef SUPPORT_I18N
       fl.smtputf8_advertised = FALSE;
 #endif
 
+      /* Expand the per-connection message count limit option */
+      mailmax = expand_mailmax(smtp_accept_max_per_connection);
+
       smtp_code = US"250 ";        /* Default response code plus space*/
       if (!user_msg)
        {
       smtp_code = US"250 ";        /* Default response code plus space*/
       if (!user_msg)
        {
-       g = string_fmt_append(NULL, "%.3s %s Hello %s%s%s",
+       /* sender_host_name below will be tainted, so save on copy when we hit it */
+       g = string_get_tainted(24, TRUE);
+       g = string_fmt_append(g, "%.3s %s Hello %s%s%s",
          smtp_code,
          smtp_active_hostname,
          sender_ident ? sender_ident : US"",
          smtp_code,
          smtp_active_hostname,
          sender_ident ? sender_ident : US"",
@@ -4298,6 +4393,19 @@ while (done <= 0)
          g = string_catn(g, US"-SIZE\r\n", 7);
          }
 
          g = string_catn(g, US"-SIZE\r\n", 7);
          }
 
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+       if (  (mailmax > 0 || recipients_max)
+          && verify_check_host(&limits_advertise_hosts) == OK)
+         {
+         g = string_fmt_append(g, "%.3s-LIMITS", smtp_code);
+         if (mailmax > 0)
+           g = string_fmt_append(g, " MAILMAX=%d", mailmax);
+         if (recipients_max)
+           g = string_fmt_append(g, " RCPTMAX=%d", recipients_max);
+         g = string_catn(g, US"\r\n", 2);
+         }
+#endif
+
        /* 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.
        /* 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.
@@ -4349,7 +4457,7 @@ while (done <= 0)
          sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
          f.smtp_in_pipelining_advertised = TRUE;
 
          sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
          f.smtp_in_pipelining_advertised = TRUE;
 
-#ifdef EXPERIMENTAL_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
          if (fl.pipe_connect_acceptable)
            {
            f.smtp_in_early_pipe_advertised = TRUE;
          if (fl.pipe_connect_acceptable)
            {
            f.smtp_in_early_pipe_advertised = TRUE;
@@ -4384,8 +4492,8 @@ while (done <= 0)
            if (au->server)
              {
              DEBUG(D_auth+D_expand) debug_printf_indent(
            if (au->server)
              {
              DEBUG(D_auth+D_expand) debug_printf_indent(
-               "Evaluating advertise_condition for %s athenticator\n",
-               au->public_name);
+               "Evaluating advertise_condition for %s %s athenticator\n",
+               au->name, au->public_name);
              if (  !au->advertise_condition
                 || expand_check_condition(au->advertise_condition, au->name,
                        US"authenticator")
              if (  !au->advertise_condition
                 || expand_check_condition(au->advertise_condition, au->name,
                        US"authenticator")
@@ -4426,7 +4534,7 @@ while (done <= 0)
        tls_advertise_hosts. We must *not* advertise if we are already in a
        secure connection. */
 
        tls_advertise_hosts. We must *not* advertise if we are already in a
        secure connection. */
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
        if (tls_in.active.sock < 0 &&
            verify_check_host(&tls_advertise_hosts) != FAIL)
          {
        if (tls_in.active.sock < 0 &&
            verify_check_host(&tls_advertise_hosts) != FAIL)
          {
@@ -4434,17 +4542,6 @@ while (done <= 0)
          g = string_catn(g, US"-STARTTLS\r\n", 11);
          fl.tls_advertised = TRUE;
          }
          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
 #endif
 
 #ifndef DISABLE_PRDR
@@ -4475,20 +4572,18 @@ while (done <= 0)
       /* 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
+#ifndef DISABLE_TLS
       if (tls_in.active.sock >= 0)
        (void)tls_write(NULL, g->s, g->ptr,
       if (tls_in.active.sock >= 0)
        (void)tls_write(NULL, g->s, g->ptr,
-# ifdef EXPERIMENTAL_PIPE_CONNECT
+# ifndef DISABLE_PIPE_CONNECT
                        fl.pipe_connect_acceptable && pipeline_connect_sends());
 # else
                        FALSE);
 # endif
       else
 #endif
                        fl.pipe_connect_acceptable && pipeline_connect_sends());
 # else
                        FALSE);
 # endif
       else
 #endif
+       (void) fwrite(g->s, 1, g->ptr, smtp_out);
 
 
-       {
-       int i = fwrite(g->s, 1, g->ptr, smtp_out); i = i; /* compiler quietening */
-       }
       DEBUG(D_receive)
        {
        uschar *cr;
       DEBUG(D_receive)
        {
        uschar *cr;
@@ -4509,7 +4604,7 @@ while (done <= 0)
          + (tls_in.active.sock >= 0 ? pcrpted : 0)
          ];
       cancel_cutthrough_connection(TRUE, US"sent EHLO response");
          + (tls_in.active.sock >= 0 ? pcrpted : 0)
          ];
       cancel_cutthrough_connection(TRUE, US"sent EHLO response");
-      smtp_reset(reset_point);
+      reset_point = smtp_reset(reset_point);
       toomany = FALSE;
       break;   /* HELO/EHLO */
 
       toomany = FALSE;
       break;   /* HELO/EHLO */
 
@@ -4523,16 +4618,20 @@ while (done <= 0)
     case MAIL_CMD:
       HAD(SCH_MAIL);
       smtp_mailcmd_count++;              /* Count for limit and ratelimit */
     case MAIL_CMD:
       HAD(SCH_MAIL);
       smtp_mailcmd_count++;              /* Count for limit and ratelimit */
+      message_start();
       was_rej_mail = TRUE;               /* Reset if accepted */
       env_mail_type_t * mail_args;       /* Sanity check & validate args */
 
       was_rej_mail = TRUE;               /* Reset if accepted */
       env_mail_type_t * mail_args;       /* Sanity check & validate args */
 
-      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 (!fl.helo_seen)
+       if (fl.helo_required)
+         {
+         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;
+         }
+       else if (mailmax < 0)
+         mailmax = expand_mailmax(smtp_accept_max_per_connection);
 
       if (sender_address)
        {
 
       if (sender_address)
        {
@@ -4551,8 +4650,7 @@ while (done <= 0)
       /* 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)
+      if (mailmax > 0 && smtp_mailcmd_count > mailmax)
        {
        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 "
        {
        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 "
@@ -4564,7 +4662,7 @@ while (done <= 0)
       obviously need to throw away any previous data. */
 
       cancel_cutthrough_connection(TRUE, US"MAIL received");
       obviously need to throw away any previous data. */
 
       cancel_cutthrough_connection(TRUE, US"MAIL received");
-      smtp_reset(reset_point);
+      reset_point = smtp_reset(reset_point);
       toomany = FALSE;
       sender_data = recipient_data = NULL;
 
       toomany = FALSE;
       sender_data = recipient_data = NULL;
 
@@ -4769,28 +4867,6 @@ while (done <= 0)
            break;
 #endif
 
            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;
-#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.
          /* 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.
@@ -4808,17 +4884,6 @@ while (done <= 0)
        if (arg_error) break;
        }
 
        if (arg_error) break;
        }
 
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
-      if (tls_requiretls & REQUIRETLS_MSG)
-       {
-       /* Ensure headers-only bounces whether a RET option was given or not. */
-
-       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
-
       /* 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. */
 
@@ -4837,7 +4902,8 @@ while (done <= 0)
       TRUE flag allows "<>" as a sender address. */
 
       raw_sender = rewrite_existflags & rewrite_smtp
       TRUE flag allows "<>" as a sender address. */
 
       raw_sender = rewrite_existflags & rewrite_smtp
-       ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+       /* deconst ok as smtp_cmd_data was not const */
+       ? US rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
                      global_rewrite_rules)
        : smtp_cmd_data;
 
                      global_rewrite_rules)
        : smtp_cmd_data;
 
@@ -4881,8 +4947,8 @@ while (done <= 0)
       and EXPN etc. to be used when space is short. */
 
       if (!receive_check_fs(
       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_check_spool_space && message_size >= 0
+             message_size + 5000 : 0))
        {
        smtp_printf("452 Space shortage, please try later\r\n", FALSE);
        sender_address = NULL;
        {
        smtp_printf("452 Space shortage, please try later\r\n", FALSE);
        sender_address = NULL;
@@ -4899,7 +4965,8 @@ while (done <= 0)
        if (f.allow_unqualified_sender)
          {
          sender_domain = Ustrlen(sender_address) + 1;
        if (f.allow_unqualified_sender)
          {
          sender_domain = Ustrlen(sender_address) + 1;
-         sender_address = rewrite_address_qualify(sender_address, FALSE);
+         /* deconst ok as sender_address was not const */
+         sender_address = US rewrite_address_qualify(sender_address, FALSE);
          DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
            raw_sender);
          }
          DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
            raw_sender);
          }
@@ -5091,7 +5158,8 @@ while (done <= 0)
       as a recipient address */
 
       recipient = rewrite_existflags & rewrite_smtp
       as a recipient address */
 
       recipient = rewrite_existflags & rewrite_smtp
-       ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+       /* deconst ok as smtp_cmd_data was not const */
+       ? US rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
            global_rewrite_rules)
        : smtp_cmd_data;
 
            global_rewrite_rules)
        : smtp_cmd_data;
 
@@ -5191,9 +5259,9 @@ while (done <= 0)
        recipients_list[recipients_count-1].orcpt = orcpt;
        recipients_list[recipients_count-1].dsn_flags = dsn_flags;
 
        recipients_list[recipients_count-1].orcpt = orcpt;
        recipients_list[recipients_count-1].dsn_flags = dsn_flags;
 
-       DEBUG(D_receive) debug_printf("DSN: orcpt: %s  flags: %d\n",
+       /* DEBUG(D_receive) debug_printf("DSN: orcpt: %s  flags: %d\n",
          recipients_list[recipients_count-1].orcpt,
          recipients_list[recipients_count-1].orcpt,
-         recipients_list[recipients_count-1].dsn_flags);
+         recipients_list[recipients_count-1].dsn_flags); */
        }
 
       /* The recipient was discarded */
        }
 
       /* The recipient was discarded */
@@ -5208,8 +5276,8 @@ while (done <= 0)
        discarded = TRUE;
        log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: "
          "discarded by %s ACL%s%s", host_and_ident(TRUE),
        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",
+         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"");
        }
 
          log_msg ? US": " : US"", log_msg ? log_msg : US"");
        }
 
@@ -5269,16 +5337,7 @@ while (done <= 0)
       DEBUG(D_receive) debug_printf("chunking state %d, %d bytes\n",
                                    (int)chunking_state, chunking_data_left);
 
       DEBUG(D_receive) debug_printf("chunking state %d, %d bytes\n",
                                    (int)chunking_state, chunking_data_left);
 
-      /* push the current receive_* function on the "stack", and
-      replace them by bdat_getc(), which in turn will use the lwr_receive_*
-      functions to do the dirty work. */
-      lwr_receive_getc = receive_getc;
-      lwr_receive_getbuf = receive_getbuf;
-      lwr_receive_ungetc = receive_ungetc;
-
-      receive_getc = bdat_getc;
-      receive_ungetc = bdat_ungetc;
-
+      f.bdat_readers_wanted = TRUE;
       f.dot_ends = FALSE;
 
       goto DATA_BDAT;
       f.dot_ends = FALSE;
 
       goto DATA_BDAT;
@@ -5287,9 +5346,10 @@ while (done <= 0)
     case DATA_CMD:
       HAD(SCH_DATA);
       f.dot_ends = TRUE;
     case DATA_CMD:
       HAD(SCH_DATA);
       f.dot_ends = TRUE;
+      f.bdat_readers_wanted = FALSE;
 
     DATA_BDAT:         /* Common code for DATA and BDAT */
 
     DATA_BDAT:         /* Common code for DATA and BDAT */
-#ifdef EXPERIMENTAL_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
       fl.pipe_connect_acceptable = FALSE;
 #endif
       if (!discarded && recipients_count <= 0)
       fl.pipe_connect_acceptable = FALSE;
 #endif
       if (!discarded && recipients_count <= 0)
@@ -5307,15 +5367,18 @@ while (done <= 0)
          }
        if (f.smtp_in_pipelining_advertised && last_was_rcpt)
          smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE,
          }
        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]]);
+           smtp_names[smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)]]);
        else
          done = synprot_error(L_smtp_protocol_error, 503, NULL,
        else
          done = synprot_error(L_smtp_protocol_error, 503, NULL,
-           smtp_connection_had[smtp_ch_index-1] == SCH_DATA
+           smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)] == SCH_DATA
            ? US"valid RCPT command must precede DATA"
            : US"valid RCPT command must precede BDAT");
 
        if (chunking_state > CHUNKING_OFFERED)
            ? US"valid RCPT command must precede DATA"
            : US"valid RCPT command must precede BDAT");
 
        if (chunking_state > CHUNKING_OFFERED)
+         {
+         bdat_push_receive_functions();
          bdat_flush_data();
          bdat_flush_data();
+         }
        break;
        }
 
        break;
        }
 
@@ -5361,6 +5424,9 @@ while (done <= 0)
            "354 Enter message, ending with \".\" on a line by itself\r\n", FALSE);
        }
 
            "354 Enter message, ending with \".\" on a line by itself\r\n", FALSE);
        }
 
+      if (f.bdat_readers_wanted)
+       bdat_push_receive_functions();
+
 #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,
 #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,
@@ -5444,7 +5510,7 @@ while (done <= 0)
       break;
 
 
       break;
 
 
-    #ifdef SUPPORT_TLS
+    #ifndef DISABLE_TLS
 
     case STARTTLS_CMD:
       HAD(SCH_STARTTLS);
 
     case STARTTLS_CMD:
       HAD(SCH_STARTTLS);
@@ -5473,7 +5539,7 @@ while (done <= 0)
 
       incomplete_transaction_log(US"STARTTLS");
       cancel_cutthrough_connection(TRUE, US"STARTTLS received");
 
       incomplete_transaction_log(US"STARTTLS");
       cancel_cutthrough_connection(TRUE, US"STARTTLS received");
-      smtp_reset(reset_point);
+      reset_point = smtp_reset(reset_point);
       toomany = FALSE;
       cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE;
 
       toomany = FALSE;
       cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE;
 
@@ -5512,7 +5578,7 @@ while (done <= 0)
       STARTTLS that don't add to the nonmail command count. */
 
       s = NULL;
       STARTTLS that don't add to the nonmail command count. */
 
       s = NULL;
-      if ((rc = tls_server_start(tls_require_ciphers, &s)) == OK)
+      if ((rc = tls_server_start(&s)) == OK)
        {
        if (!tls_remember_esmtp)
          fl.helo_seen = fl.esmtp = fl.auth_advertised = f.smtp_in_pipelining_advertised = FALSE;
        {
        if (!tls_remember_esmtp)
          fl.helo_seen = fl.esmtp = fl.auth_advertised = f.smtp_in_pipelining_advertised = FALSE;
@@ -5521,7 +5587,6 @@ while (done <= 0)
        cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
        if (sender_helo_name)
          {
        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",
          sender_helo_name = NULL;
          host_build_sender_fullhost();  /* Rebuild */
          set_process_info("handling incoming TLS connection from %s",
@@ -5575,6 +5640,7 @@ while (done <= 0)
        some sense is perhaps "right". */
 
        case QUIT_CMD:
        some sense is perhaps "right". */
 
        case QUIT_CMD:
+         f.smtp_in_quit = TRUE;
          user_msg = NULL;
          if (  acl_smtp_quit
             && ((rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg,
          user_msg = NULL;
          if (  acl_smtp_quit
             && ((rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg,
@@ -5612,7 +5678,7 @@ while (done <= 0)
     case RSET_CMD:
       smtp_rset_handler();
       cancel_cutthrough_connection(TRUE, US"RSET received");
     case RSET_CMD:
       smtp_rset_handler();
       cancel_cutthrough_connection(TRUE, US"RSET received");
-      smtp_reset(reset_point);
+      reset_point = smtp_reset(reset_point);
       toomany = FALSE;
       break;
 
       toomany = FALSE;
       break;
 
@@ -5634,17 +5700,17 @@ while (done <= 0)
        {
        uschar buffer[256];
        buffer[0] = 0;
        {
        uschar buffer[256];
        buffer[0] = 0;
-       Ustrcat(buffer, " AUTH");
-       #ifdef SUPPORT_TLS
+       Ustrcat(buffer, US" AUTH");
+       #ifndef DISABLE_TLS
        if (tls_in.active.sock < 0 &&
            verify_check_host(&tls_advertise_hosts) != FAIL)
        if (tls_in.active.sock < 0 &&
            verify_check_host(&tls_advertise_hosts) != FAIL)
-         Ustrcat(buffer, " STARTTLS");
+         Ustrcat(buffer, US" STARTTLS");
        #endif
        #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");
+       Ustrcat(buffer, US" HELO EHLO MAIL RCPT DATA BDAT");
+       Ustrcat(buffer, US" NOOP QUIT RSET HELP");
+       if (acl_smtp_etrn) Ustrcat(buffer, US" ETRN");
+       if (acl_smtp_expn) Ustrcat(buffer, US" EXPN");
+       if (acl_smtp_vrfy) Ustrcat(buffer, US" VRFY");
        smtp_printf("214%s\r\n", FALSE, buffer);
        }
       break;
        smtp_printf("214%s\r\n", FALSE, buffer);
        }
       break;
@@ -5780,7 +5846,7 @@ while (done <= 0)
 
       oldsignal = signal(SIGCHLD, SIG_IGN);
 
 
       oldsignal = signal(SIGCHLD, SIG_IGN);
 
-      if ((pid = fork()) == 0)
+      if ((pid = exim_fork(US"etrn-command")) == 0)
        {
        smtp_input = FALSE;       /* This process is not associated with the */
        (void)fclose(smtp_in);    /* SMTP call any more. */
        {
        smtp_input = FALSE;       /* This process is not associated with the */
        (void)fclose(smtp_in);    /* SMTP call any more. */
@@ -5791,7 +5857,8 @@ while (done <= 0)
        /* 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)
+       if (  !smtp_etrn_serialize
+          || (pid = exim_fork(US"etrn-serialised-command")) == 0)
          {
          DEBUG(D_exec) debug_print_argv(argv);
          exim_nullstd();                   /* Ensure std{in,out,err} exist */
          {
          DEBUG(D_exec) debug_print_argv(argv);
          exim_nullstd();                   /* Ensure std{in,out,err} exist */
@@ -5820,7 +5887,7 @@ while (done <= 0)
          }
 
        enq_end(etrn_serialize_key);
          }
 
        enq_end(etrn_serialize_key);
-       _exit(EXIT_SUCCESS);
+       exim_underbar_exit(EXIT_SUCCESS);
        }
 
       /* Back in the top level SMTP process. Check that we started a subprocess
        }
 
       /* Back in the top level SMTP process. Check that we started a subprocess
@@ -5834,10 +5901,10 @@ while (done <= 0)
        if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
        }
       else
        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 (!user_msg)
+         smtp_printf("250 OK\r\n", FALSE);
+       else
+         smtp_user_msg(US"250", user_msg);
 
       signal(SIGCHLD, oldsignal);
       break;
 
       signal(SIGCHLD, oldsignal);
       break;
@@ -5939,12 +6006,14 @@ if (!sender_host_authenticated)
 
 g = string_append(g, 2, US";\n\tauth=pass (", sender_host_auth_pubname);
 
 
 g = string_append(g, 2, US";\n\tauth=pass (", sender_host_auth_pubname);
 
-if (Ustrcmp(sender_host_auth_pubname, "tls") != 0)
-  g = string_append(g, 2, US") smtp.auth=", authenticated_id);
-else if (authenticated_id)
-  g = string_append(g, 2, US") x509.auth=", authenticated_id);
+if (Ustrcmp(sender_host_auth_pubname, "tls") == 0)
+  g = authenticated_id
+    ? string_append(g, 2, US") x509.auth=", authenticated_id)
+    : string_cat(g, US") reason=x509.auth");
 else
 else
-  g = string_catn(g, US") reason=x509.auth", 17);
+  g = authenticated_id
+    ? string_append(g, 2, US") smtp.auth=", authenticated_id)
+    : string_cat(g, US", no id saved)");
 
 if (authenticated_sender)
   g = string_append(g, 2, US" smtp.mailfrom=", authenticated_sender);
 
 if (authenticated_sender)
   g = string_append(g, 2, US" smtp.mailfrom=", authenticated_sender);