SPDX: Mass-update to GPL-2.0-or-later
[exim.git] / src / src / smtp_in.c
index 86f87eae1e406d02bb593ecb95b736e15a2662b9..9b60702c15accc42b9068d3f0a9039799f11369c 100644 (file)
@@ -2,8 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 /* Functions for handling an incoming SMTP call. */
 
@@ -133,19 +135,16 @@ to the circular buffer that holds a list of the last n received. */
 
 static struct {
   BOOL auth_advertised                 :1;
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
   BOOL tls_advertised                  :1;
-# ifdef EXPERIMENTAL_REQUIRETLS
-  BOOL requiretls_advertised           :1;
-# endif
 #endif
   BOOL dsn_advertised                  :1;
   BOOL esmtp                           :1;
-  BOOL helo_required                   :1;
+  BOOL helo_verify_required            :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;
@@ -155,7 +154,7 @@ static struct {
   BOOL smtputf8_advertised             :1;
 #endif
 } fl = {
-  .helo_required = FALSE,
+  .helo_verify_required = FALSE,
   .helo_verify = FALSE,
   .smtp_exit_function_called = FALSE,
 };
@@ -197,7 +196,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  },
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
   { "starttls",   sizeof("starttls")-1,   STARTTLS_CMD, FALSE, FALSE },
   { "tls_auth",   0,                      TLS_AUTH_CMD, FALSE, FALSE },
 #endif
@@ -229,7 +228,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. */
 
-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",
@@ -267,9 +266,6 @@ 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 {
@@ -289,10 +285,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 */
-#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 },
@@ -328,98 +320,6 @@ static int synprot_error(int type, int code, uschar *data, uschar *errmess);
 static void smtp_quit_handler(uschar **, uschar **);
 static void smtp_rset_handler(void);
 
-/*************************************************
-*          Recheck synchronization               *
-*************************************************/
-
-/* Synchronization checks can never be perfect because a packet may be on its
-way but not arrived when the check is done.  Normally, the checks happen when
-commands are read: Exim ensures that there is no more input in the input buffer.
-In normal cases, the response to the command will be fast, and there is no
-further check.
-
-However, for some commands an ACL is run, and that can include delays. In those
-cases, it is useful to do another check on the input just before sending the
-response. This also applies at the start of a connection. This function does
-that check by means of the select() function, as long as the facility is not
-disabled or inappropriate. A failure of select() is ignored.
-
-When there is unwanted input, we read it so that it appears in the log of the
-error.
-
-Arguments: none
-Returns:   TRUE if all is well; FALSE if there is input pending
-*/
-
-static BOOL
-wouldblock_reading(void)
-{
-int fd, rc;
-fd_set fds;
-struct timeval tzero;
-
-#ifdef SUPPORT_TLS
-if (tls_in.active.sock >= 0)
- return !tls_could_read();
-#endif
-
-if (smtp_inptr < smtp_inend)
-  return FALSE;
-
-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 = smtp_getc(GETC_BUFFER_UNLIMITED);
-if (rc < 0) return TRUE;      /* End of file or error */
-
-smtp_ungetc(rc);
-return FALSE;
-}
-
-static BOOL
-check_sync(void)
-{
-if (!smtp_enforce_sync || !sender_host_address || f.sender_host_notsocket)
-  return TRUE;
-
-return wouldblock_reading();
-}
-
-
-/* If there's input waiting (and we're doing pipelineing) then we can pipeline
-a reponse with the one following. */
-
-static BOOL
-pipeline_response(void)
-{
-if (  !smtp_enforce_sync || !sender_host_address
-   || f.sender_host_notsocket || !f.smtp_in_pipelining_advertised)
-  return FALSE;
-
-if (wouldblock_reading()) return FALSE;
-f.smtp_in_pipelining_used = TRUE;
-return TRUE;
-}
-
-
-#ifdef EXPERIMENTAL_PIPE_CONNECT
-static BOOL
-pipeline_connect_sends(void)
-{
-if (!sender_host_address || f.sender_host_notsocket || !fl.pipe_connect_acceptable)
-  return FALSE;
-
-if (wouldblock_reading()) return FALSE;
-f.smtp_in_early_pipe_used = TRUE;
-return TRUE;
-}
-#endif
-
 /*************************************************
 *          Log incomplete transactions           *
 *************************************************/
@@ -435,17 +335,16 @@ Returns:    nothing
 static void
 incomplete_transaction_log(uschar *what)
 {
-if (sender_address == NULL ||                 /* No transaction in progress */
-    !LOGGING(smtp_incomplete_transaction))
+if (!sender_address                            /* No transaction in progress */
+   || !LOGGING(smtp_incomplete_transaction))
   return;
 
 /* Build list of recipients for logging */
 
 if (recipients_count > 0)
   {
-  int i;
-  raw_recipients = store_get(recipients_count * sizeof(uschar *));
-  for (i = 0; i < recipients_count; i++)
+  raw_recipients = store_get(recipients_count * sizeof(uschar *), GET_UNTAINTED);
+  for (int i = 0; i < recipients_count; i++)
     raw_recipients[i] = recipients_list[i].address;
   raw_recipients_count = recipients_count;
   }
@@ -468,7 +367,7 @@ if (smtp_batched_input)
 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
@@ -479,7 +378,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);
-exim_exit(EXIT_FAILURE, US"receiving");
+exim_exit(EXIT_FAILURE);
 }
 
 void
@@ -503,6 +402,25 @@ receive_bomb_out(US"signal-exit",
 }
 
 
+/******************************************************************************/
+/* SMTP input buffer handling.  Most of these are similar to stdio routines.  */
+
+static void
+smtp_buf_init(void)
+{
+/* Set up the buffer for inputting using direct read() calls, and arrange to
+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';
+
+smtp_inptr = smtp_inend = smtp_inbuffer;
+smtp_had_eof = smtp_had_error = 0;
+}
+
+
 
 /* Refill the buffer, and notify DKIM verification code.
 Return false for error or EOF.
@@ -512,6 +430,7 @@ static BOOL
 smtp_refill(unsigned lim)
 {
 int rc, save_errno;
+
 if (!smtp_out) return FALSE;
 fflush(smtp_out);
 if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
@@ -538,8 +457,8 @@ if (rc <= 0)
       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;
@@ -553,11 +472,18 @@ smtp_inptr = smtp_inbuffer;
 return TRUE;
 }
 
-/*************************************************
-*          SMTP version of getc()                *
-*************************************************/
 
-/* This gets the next byte from the SMTP input buffer. If the buffer is empty,
+/* Check if there is buffered data */
+
+BOOL
+smtp_hasc(void)
+{
+return smtp_inptr < smtp_inend;
+}
+
+/* SMTP version of getc()
+
+This gets the next byte from the SMTP input buffer. If the buffer is empty,
 it flushes the output, and refills the buffer, with a timeout. The signal
 handler is set appropriately by the calling function. This function is not used
 after a connection has negotiated itself into an TLS/SSL state.
@@ -569,21 +495,20 @@ Returns:    the next character or EOF
 int
 smtp_getc(unsigned lim)
 {
-if (smtp_inptr >= smtp_inend)
-  if (!smtp_refill(lim))
-    return EOF;
+if (!smtp_hasc() && !smtp_refill(lim)) return EOF;
 return *smtp_inptr++;
 }
 
+/* Get many bytes, refilling buffer if needed */
+
 uschar *
 smtp_getbuf(unsigned * len)
 {
 unsigned size;
 uschar * buf;
 
-if (smtp_inptr >= smtp_inend)
-  if (!smtp_refill(*len))
-    { *len = 0; return NULL; }
+if (!smtp_hasc() && !smtp_refill(*len))
+  { *len = 0; return NULL; }
 
 if ((size = smtp_inend - smtp_inptr) > *len) size = *len;
 buf = smtp_inptr;
@@ -592,17 +517,151 @@ smtp_inptr += size;
 return buf;
 }
 
+/* Copy buffered data to the dkim feed.
+Called, unless TLS, just before starting to read message headers. */
+
 void
-smtp_get_cache(void)
+smtp_get_cache(unsigned lim)
 {
 #ifndef DISABLE_DKIM
 int n = smtp_inend - smtp_inptr;
+if (n > lim)
+  n = lim;
 if (n > 0)
   dkim_exim_verify_feed(smtp_inptr, n);
 #endif
 }
 
 
+/* SMTP version of ungetc()
+Puts a character back in the input buffer. Only ever called once.
+
+Arguments:
+  ch           the character
+
+Returns:       the character
+*/
+
+int
+smtp_ungetc(int ch)
+{
+if (smtp_inptr <= smtp_inbuffer)       /* NB: NOT smtp_hasc() ! */
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in smtp_ungetc");
+
+*--smtp_inptr = ch;
+return ch;
+}
+
+
+/* SMTP version of feof()
+Tests for a previous EOF
+
+Arguments:     none
+Returns:       non-zero if the eof flag is set
+*/
+
+int
+smtp_feof(void)
+{
+return smtp_had_eof;
+}
+
+
+/* SMTP version of ferror()
+Tests for a previous read error, and returns with errno
+restored to what it was when the error was detected.
+
+Arguments:     none
+Returns:       non-zero if the error flag is set
+*/
+
+int
+smtp_ferror(void)
+{
+errno = smtp_had_error;
+return smtp_had_error;
+}
+
+
+/* Check if a getc will block or not */
+
+static BOOL
+smtp_could_getc(void)
+{
+int fd, rc;
+fd_set fds;
+struct timeval tzero = {.tv_sec = 0, .tv_usec = 0};
+
+if (smtp_inptr < smtp_inend)
+  return TRUE;
+
+fd = fileno(smtp_in);
+FD_ZERO(&fds);
+FD_SET(fd, &fds);
+rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero);
+
+if (rc <= 0) return FALSE;     /* Not ready to read */
+rc = smtp_getc(GETC_BUFFER_UNLIMITED);
+if (rc < 0) return FALSE;      /* End of file or error */
+
+smtp_ungetc(rc);
+return TRUE;
+}
+
+
+/******************************************************************************/
+/*************************************************
+*          Recheck synchronization               *
+*************************************************/
+
+/* Synchronization checks can never be perfect because a packet may be on its
+way but not arrived when the check is done.  Normally, the checks happen when
+commands are read: Exim ensures that there is no more input in the input buffer.
+In normal cases, the response to the command will be fast, and there is no
+further check.
+
+However, for some commands an ACL is run, and that can include delays. In those
+cases, it is useful to do another check on the input just before sending the
+response. This also applies at the start of a connection. This function does
+that check by means of the select() function, as long as the facility is not
+disabled or inappropriate. A failure of select() is ignored.
+
+When there is unwanted input, we read it so that it appears in the log of the
+error.
+
+Arguments: none
+Returns:   TRUE if all is well; FALSE if there is input pending
+*/
+
+static BOOL
+wouldblock_reading(void)
+{
+#ifndef DISABLE_TLS
+if (tls_in.active.sock >= 0)
+ return !tls_could_getc();
+#endif
+
+return !smtp_could_getc();
+}
+
+static BOOL
+check_sync(void)
+{
+if (!smtp_enforce_sync || !sender_host_address || f.sender_host_notsocket)
+  return TRUE;
+
+return wouldblock_reading();
+}
+
+
+/******************************************************************************/
+/* Variants of the smtp_* input handling functions for use in CHUNKING mode */
+
+/* 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
@@ -634,9 +693,7 @@ for(;;)
   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;
@@ -668,7 +725,9 @@ for(;;)
   if (chunking_state == CHUNKING_LAST)
     {
 #ifndef DISABLE_DKIM
+    dkim_collect_input = dkim_save;
     dkim_exim_verify_feed(NULL, 0);    /* notify EOD */
+    dkim_collect_input = 0;
 #endif
     return EOD;
     }
@@ -740,9 +799,7 @@ next_cmd:
          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
@@ -752,6 +809,14 @@ next_cmd:
   }
 }
 
+BOOL
+bdat_hasc(void)
+{
+if (chunking_data_left > 0)
+  return lwr_receive_hasc();
+return TRUE;
+}
+
 uschar *
 bdat_getbuf(unsigned * len)
 {
@@ -775,106 +840,66 @@ while (chunking_data_left)
   if (!bdat_getbuf(&n)) break;
   }
 
-receive_getc = lwr_receive_getc;
-receive_getbuf = lwr_receive_getbuf;
-receive_ungetc = lwr_receive_ungetc;
-
-if (chunking_state != CHUNKING_LAST)
-  {
-  chunking_state = CHUNKING_OFFERED;
-  DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
-  }
+bdat_pop_receive_functions();
+chunking_state = CHUNKING_OFFERED;
+DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
 }
 
 
+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)
+  {
+  lwr_receive_getc = receive_getc;
+  lwr_receive_getbuf = receive_getbuf;
+  lwr_receive_hasc = receive_hasc;
+  lwr_receive_ungetc = receive_ungetc;
+  }
+else
+  {
+  DEBUG(D_receive) debug_printf("chunking double-push receive functions\n");
+  }
 
+receive_getc = bdat_getc;
+receive_getbuf = bdat_getbuf;
+receive_hasc = bdat_hasc;
+receive_ungetc = bdat_ungetc;
+}
 
-/*************************************************
-*          SMTP version of ungetc()              *
-*************************************************/
-
-/* Puts a character back in the input buffer. Only ever
-called once.
-
-Arguments:
-  ch           the character
-
-Returns:       the character
-*/
-
-int
-smtp_ungetc(int ch)
+static inline void
+bdat_pop_receive_functions(void)
 {
-*--smtp_inptr = ch;
-return ch;
-}
+if (!lwr_receive_getc)
+  {
+  DEBUG(D_receive) debug_printf("chunking double-pop receive functions\n");
+  return;
+  }
+receive_getc = lwr_receive_getc;
+receive_getbuf = lwr_receive_getbuf;
+receive_hasc = lwr_receive_hasc;
+receive_ungetc = lwr_receive_ungetc;
 
+lwr_receive_getc = NULL;
+lwr_receive_getbuf = NULL;
+lwr_receive_hasc = NULL;
+lwr_receive_ungetc = NULL;
+}
 
 int
 bdat_ungetc(int ch)
 {
 chunking_data_left++;
+bdat_push_receive_functions();  /* we're not done yet, calling push is safe, because it checks the state before pushing anything */
 return lwr_receive_ungetc(ch);
 }
 
 
 
-/*************************************************
-*          SMTP version of feof()                *
-*************************************************/
-
-/* Tests for a previous EOF
-
-Arguments:     none
-Returns:       non-zero if the eof flag is set
-*/
-
-int
-smtp_feof(void)
-{
-return smtp_had_eof;
-}
-
-
-
-
-/*************************************************
-*          SMTP version of ferror()              *
-*************************************************/
-
-/* Tests for a previous read error, and returns with errno
-restored to what it was when the error was detected.
-
-Arguments:     none
-Returns:       non-zero if the error flag is set
-*/
-
-int
-smtp_ferror(void)
-{
-errno = smtp_had_error;
-return smtp_had_error;
-}
-
-
-
-/*************************************************
-*      Test for characters in the SMTP buffer    *
-*************************************************/
-
-/* Used at the end of a message
-
-Arguments:     none
-Returns:       TRUE/FALSE
-*/
-
-BOOL
-smtp_buffered(void)
-{
-return smtp_inptr < smtp_inend;
-}
-
-
+/******************************************************************************/
 
 /*************************************************
 *     Write formatted string to SMTP channel     *
@@ -888,6 +913,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().
 
+This function is exposed to the local_scan API; do not change the signature.
+
 Arguments:
   format      format string
   more       further data expected
@@ -908,7 +935,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
-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)
@@ -916,26 +947,24 @@ smtp_vprintf(const char *format, BOOL more, va_list ap)
 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)
-  {
-  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);
-  store_reset(reset_point);
-  }
+DEBUG(D_receive) for (const uschar * t, * s = gs.s;
+                     s && (t = Ustrchr(s, '\r'));
+                     s = t + 2)                                /* \r\n */
+    debug_printf("%s %.*s\n",
+                 s == gs.s ? "SMTP>>" : "      ",
+                 (int)(t - s), s);
 
 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
@@ -946,7 +975,7 @@ which sometimes uses smtp_printf() and sometimes smtp_respond(). */
 
 if (fl.rcpt_in_progress)
   {
-  if (rcpt_smtp_response == NULL)
+  if (!rcpt_smtp_response)
     rcpt_smtp_response = string_copy(big_buffer);
   else if (fl.rcpt_smtp_response_same &&
            Ustrcmp(rcpt_smtp_response, big_buffer) != 0)
@@ -956,16 +985,13 @@ if (fl.rcpt_in_progress)
 
 /* 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
-
-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;
 }
 
 
@@ -976,8 +1002,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().
-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
@@ -987,11 +1012,49 @@ int
 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;
 }
 
 
 
+/* If there's input waiting (and we're doing pipelineing) then we can pipeline
+a reponse with the one following. */
+
+static BOOL
+pipeline_response(void)
+{
+if (  !smtp_enforce_sync || !sender_host_address
+   || f.sender_host_notsocket || !f.smtp_in_pipelining_advertised)
+  return FALSE;
+
+if (wouldblock_reading()) return FALSE;
+f.smtp_in_pipelining_used = TRUE;
+return TRUE;
+}
+
+
+#ifndef DISABLE_PIPE_CONNECT
+static BOOL
+pipeline_connect_sends(void)
+{
+if (!sender_host_address || f.sender_host_notsocket || !fl.pipe_connect_acceptable)
+  return FALSE;
+
+if (wouldblock_reading()) return FALSE;
+f.smtp_in_early_pipe_used = TRUE;
+return TRUE;
+}
+#endif
+
 /*************************************************
 *          SMTP command read timeout             *
 *************************************************/
@@ -1031,25 +1094,6 @@ had_command_sigterm = sig;
 
 
 #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     *
 *************************************************/
@@ -1126,7 +1170,7 @@ if (cr != NULL)
 
 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++;
@@ -1230,20 +1274,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 */
-struct timeval tv;
-struct timeval tvtmp;
 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
   {
@@ -1251,9 +1286,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. */
-  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;
@@ -1267,8 +1302,8 @@ if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
   /* 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;
@@ -1304,8 +1339,8 @@ if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
     {
     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;
@@ -1533,7 +1568,8 @@ done:
 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)
@@ -1549,6 +1585,7 @@ bad:
       debug_printf("Failure to extract proxied host, only QUIT allowed\n");
     }
 
+ALARM(0);
 return;
 }
 #endif
@@ -1581,7 +1618,6 @@ smtp_read_command(BOOL check_sync, unsigned buffer_lim)
 {
 int c;
 int ptr = 0;
-smtp_cmd_list *p;
 BOOL hadnull = FALSE;
 
 had_command_timeout = 0;
@@ -1626,7 +1662,7 @@ if (hadnull) return BADCHAR_CMD;
 to the start of the actual data characters. Check for SMTP synchronization
 if required. */
 
-for (p = cmd_list; p < cmd_list_end; p++)
+for (smtp_cmd_list * p = cmd_list; p < cmd_list_end; p++)
   {
 #ifdef SUPPORT_PROXY
   /* Only allow QUIT command if Proxy Protocol parsing failed */
@@ -1640,12 +1676,13 @@ for (p = cmd_list; p < cmd_list_end; p++)
        || smtp_cmd_buffer[p->len] == ' '
      )  )
     {
-    if (smtp_inptr < smtp_inend &&                     /* Outstanding input */
-        p->cmd < sync_cmd_limit &&                     /* Command should sync */
-        check_sync &&                                  /* Local flag set */
-        smtp_enforce_sync &&                           /* Global flag set */
-        sender_host_address != NULL &&                 /* Not local input */
-        !f.sender_host_notsocket)                        /* Really is a socket */
+    if (   smtp_inptr < smtp_inend             /* Outstanding input */
+       &&  p->cmd < sync_cmd_limit             /* Command should sync */
+       &&  check_sync                          /* Local flag set */
+       &&  smtp_enforce_sync                   /* Global flag set */
+       &&  sender_host_address != NULL         /* Not local input */
+       &&  !f.sender_host_notsocket            /* Really is a socket */
+       )
       return BADSYN_CMD;
 
     /* The variables $smtp_command and $smtp_command_argument point into the
@@ -1694,7 +1731,8 @@ 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 */
+   && !f.sender_host_notsocket         /* Really is a socket */
+   )
   return BADSYN_CMD;
 
 return OTHER_CMD;
@@ -1720,7 +1758,7 @@ Returns:    nothing
 */
 
 void
-smtp_closedown(uschar *message)
+smtp_closedown(uschar * message)
 {
 if (!smtp_in || smtp_batched_input) return;
 receive_swallow_smtp();
@@ -1732,6 +1770,7 @@ for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
     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;
@@ -1786,7 +1825,7 @@ return string_sprintf("SMTP connection from %s", hostname);
 
 
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 /* Append TLS-related information to a log line
 
 Arguments:
@@ -1798,17 +1837,43 @@ static gstring *
 s_tlslog(gstring * g)
 {
 if (LOGGING(tls_cipher) && 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)
-  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
 
+
+
+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         *
 *************************************************/
@@ -1824,8 +1889,7 @@ Returns:     nothing
 void
 smtp_log_no_mail(void)
 {
-int i;
-uschar * sep, * s;
+uschar * s;
 gstring * g = NULL;
 
 if (smtp_mailcmd_count > 0 || !LOGGING(smtp_no_mail))
@@ -1837,24 +1901,11 @@ if (sender_host_authenticated)
   if (authenticated_id) g = string_append(g, 2, US":", authenticated_id);
   }
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 g = s_tlslog(g);
 #endif
 
-sep = smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE ?  US" C=..." : US" C=";
-
-for (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 (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"";
 
@@ -1869,15 +1920,14 @@ log_write(0, LOG_MAIN, "no MAIL in %sSMTP connection from %s D=%s%s",
 uschar *
 smtp_cmd_hist(void)
 {
-int  i;
 gstring * list = NULL;
 uschar * s;
 
-for (i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
+for (int i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
   if (smtp_connection_had[i] != SCH_NONE)
     list = string_append_listele(list, ',', smtp_names[smtp_connection_had[i]]);
 
-for (i = 0; i < smtp_ch_index; i++)
+for (int i = 0; i < smtp_ch_index; i++)
   list = string_append_listele(list, ',', smtp_names[smtp_connection_had[i]]);
 
 s = string_from_gstring(list);
@@ -1914,11 +1964,7 @@ BOOL yield = fl.helo_accept_junk;
 
 /* 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. */
 
@@ -1957,7 +2003,7 @@ if (!yield)
 
 /* 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;
 }
 
@@ -1984,29 +2030,35 @@ static BOOL
 extract_option(uschar **name, uschar **value)
 {
 uschar *n;
-uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
-while (isspace(*v)) v--;
+uschar *v;
+if (Ustrlen(smtp_cmd_data) <= 0) return FALSE;
+v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
+while (v > smtp_cmd_data && isspace(*v)) v--;
 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 */
-
-  if (*v == '"') do v--; while (*v != '"' && v > smtp_cmd_data+1);
+  if (*v == '"')
+    {
+    do v--; while (v > smtp_cmd_data && *v != '"');
+    if (v <= smtp_cmd_data) return FALSE;
+    }
   v--;
   }
+if (v <= smtp_cmd_data) return FALSE;
 
 n = v;
 if (*v == '=')
   {
-  while(isalpha(n[-1])) n--;
+  while (n > smtp_cmd_data && isalpha(n[-1])) n--;
   /* RFC says SP, but TAB seen in wild and other major MTAs accept it */
-  if (!isspace(n[-1])) return FALSE;
+  if (n <= smtp_cmd_data || !isspace(n[-1])) return FALSE;
   n[-1] = 0;
   }
 else
   {
   n++;
-  if (v == smtp_cmd_data) return FALSE;
   }
 *v++ = 0;
 *name = n;
@@ -2029,7 +2081,7 @@ Argument:   the stacking pool storage reset point
 Returns:    nothing
 */
 
-void
+void *
 smtp_reset(void *reset_point)
 {
 recipients_list = NULL;
@@ -2037,29 +2089,32 @@ rcpt_count = rcpt_defer_count = rcpt_fail_count =
   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;
-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
-f.no_mbox_unspool = FALSE;                             /* Can be set by ACL */
+f.no_mbox_unspool = FALSE;                             /* Can be set by ACL */
 #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.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;
-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;
-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 */
@@ -2085,13 +2140,14 @@ dkim_collect_input = 0;
 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;
+arc_received_instance = 0;
 #endif
 dsn_ret = 0;
 dsn_envid = NULL;
@@ -2102,8 +2158,12 @@ prdr_requested = FALSE;
 #ifdef SUPPORT_I18N
 message_smtputf8 = FALSE;
 #endif
+#ifdef WITH_CONTENT_SCAN
+regex_vars_clear();
+#endif
 body_linecount = body_zerocount = 0;
 
+lookup_value = NULL;                           /* Can be set by ACL */
 sender_rate = sender_rate_limit = sender_rate_period = NULL;
 ratelimiters_mail = NULL;           /* Updated by ratelimit ACL condition */
                    /* Note that ratelimiters_conn persists across resets. */
@@ -2112,23 +2172,7 @@ ratelimiters_mail = NULL;           /* Updated by ratelimit ACL condition */
 
 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. */
 
@@ -2138,7 +2182,12 @@ while (acl_warn_logged)
   acl_warn_logged = acl_warn_logged->next;
   store_free(this);
   }
+
+message_tidyup();
 store_reset(reset_point);
+
+message_start();
+return store_mark();
 }
 
 
@@ -2166,7 +2215,7 @@ static int
 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. */
@@ -2176,7 +2225,7 @@ bsmtp_transaction_linecount = receive_linecount;
 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. */
@@ -2201,7 +2250,7 @@ while (done <= 0)
 
     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;
 
@@ -2214,7 +2263,7 @@ while (done <= 0)
 
     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");
 
@@ -2225,13 +2274,15 @@ while (done <= 0)
       /* 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 */
 
-      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 */
 
@@ -2251,7 +2302,8 @@ while (done <= 0)
          && 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);
          }
@@ -2290,7 +2342,8 @@ while (done <= 0)
       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;
 
@@ -2309,7 +2362,8 @@ while (done <= 0)
          {
          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
@@ -2352,8 +2406,9 @@ while (done <= 0)
       break;
 
 
-    case EOF_CMD:
     case QUIT_CMD:
+      f.smtp_in_quit = TRUE;
+    case EOF_CMD:
       done = 2;
       break;
 
@@ -2383,11 +2438,11 @@ return done - 2;  /* Convert yield values */
 
 
 
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 static BOOL
-smtp_log_tls_fail(uschar * errstr)
+smtp_log_tls_fail(const uschar * errstr)
 {
-uschar * conn_info = smtp_get_connection_info();
+const uschar * conn_info = smtp_get_connection_info();
 
 if (Ustrncmp(conn_info, US"SMTP ", 5) == 0) conn_info += 5;
 /* I'd like to get separated H= here, but too hard for now */
@@ -2404,24 +2459,47 @@ return FALSE;
 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)
-#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)
     {
-    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
-#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;
     }
+else DEBUG(D_receive)
+  debug_printf("TCP_INFO getsockopt: %s\n", strerror(errno));
 # endif
 }
 #endif
@@ -2474,15 +2552,12 @@ if (!host_checking && !f.sender_host_notsocket)
   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;
-# ifdef EXPERIMENTAL_REQUIRETLS
-fl.requiretls_advertised = FALSE;
-# endif
 #endif
 fl.dsn_advertised = FALSE;
 #ifdef SUPPORT_I18N
@@ -2493,11 +2568,9 @@ fl.smtputf8_advertised = FALSE;
 
 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, GET_TAINTED);
 
 smtp_cmd_buffer[0] = 0;
 smtp_data_buffer = smtp_cmd_buffer + SMTP_CMD_BUFFER_SIZE + 1;
@@ -2518,22 +2591,21 @@ 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.  Place a NUL at the
-end of the buffer to safety-stop C-string reads from it. */
+call the local functions instead of the standard C ones. */
 
-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';
+smtp_buf_init();
 
 receive_getc = smtp_getc;
 receive_getbuf = smtp_getbuf;
 receive_get_cache = smtp_get_cache;
+receive_hasc = smtp_hasc;
 receive_ungetc = smtp_ungetc;
 receive_feof = smtp_feof;
 receive_ferror = smtp_ferror;
-receive_smtp_buffered = smtp_buffered;
-smtp_inptr = smtp_inend = smtp_inbuffer;
-smtp_had_eof = smtp_had_error = 0;
+lwr_receive_getc = NULL;
+lwr_receive_getbuf = NULL;
+lwr_receive_hasc = NULL;
+lwr_receive_ungetc = NULL;
 
 /* Set up the message size limit; this may be host-specific */
 
@@ -2608,7 +2680,7 @@ if (!f.sender_host_unknown)
     {
     #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, GET_UNTAINTED);
     #elif OPTSTYLE == 2
     struct ip_opts ipoptblock;
     struct ip_opts *ipopt = &ipoptblock;
@@ -2649,7 +2721,7 @@ if (!f.sender_host_unknown)
       {
       uschar *p = big_buffer;
       uschar *pend = big_buffer + big_buffer_size;
-      uschar *opt, *adptr;
+      uschar *adptr;
       int optcount;
       struct in_addr addr;
 
@@ -2666,9 +2738,7 @@ if (!f.sender_host_unknown)
       Ustrcpy(p, "IP options on incoming call:");
       p += Ustrlen(p);
 
-      for (opt = optstart; opt != NULL &&
-           opt < US (ipopt) + optlen;)
-        {
+      for (uschar * opt = optstart; opt && opt < US (ipopt) + optlen; )
         switch (*opt)
           {
           case IPOPT_EOL:
@@ -2716,18 +2786,16 @@ if (!f.sender_host_unknown)
 
           default:
             {
-            int i;
             if (pend - p < 4 + 3*opt[1]) { opt = NULL; break; }
             Ustrcat(p, "[ ");
             p += 2;
-            for (i = 0; i < opt[1]; i++)
+            for (int i = 0; i < opt[1]; i++)
               p += sprintf(CS p, "%2.2x ", opt[i]);
             *p++ = ']';
             }
           opt += opt[1];
           break;
           }
-        }
 
       *p = 0;
       log_write(0, LOG_MAIN, "%s", big_buffer);
@@ -2891,8 +2959,8 @@ if (!f.sender_host_unknown)
   /* Determine whether HELO/EHLO is required for this host. The requirement
   can be hard or soft. */
 
-  fl.helo_required = verify_check_host(&helo_verify_hosts) == OK;
-  if (!fl.helo_required)
+  fl.helo_verify_required = verify_check_host(&helo_verify_hosts) == OK;
+  if (!fl.helo_verify_required)
     fl.helo_verify = verify_check_host(&helo_try_verify_hosts) == OK;
 
   /* Determine whether this hosts is permitted to send syntactic junk
@@ -2906,7 +2974,7 @@ if (!f.sender_host_unknown)
 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;
@@ -2915,16 +2983,16 @@ if (check_proxy_protocol_host())
   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 */
@@ -2970,7 +3038,7 @@ else
 
 p = s + Ustrlen(s);
 while (p > s && isspace(p[-1])) p--;
-*p = 0;
+s = string_copyn(s, p-s);
 
 /* It seems that CC:Mail is braindead, and assumes that the greeting message
 is all contained in a single IP packet. The original code wrote out the
@@ -3010,7 +3078,7 @@ while (*p);
 /* 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;
 
@@ -3023,7 +3091,7 @@ if (!check_sync())
 #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): "
@@ -3037,7 +3105,7 @@ if (!check_sync())
 /*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,
@@ -3048,7 +3116,7 @@ smtp_printf("%s",
 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;
@@ -3084,15 +3152,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",
-  (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 "
-    "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)
@@ -3148,7 +3218,7 @@ which sometimes uses smtp_printf() and sometimes smtp_respond(). */
 
 if (fl.rcpt_in_progress)
   {
-  if (rcpt_smtp_response == NULL)
+  if (!rcpt_smtp_response)
     rcpt_smtp_response = string_copy(msg);
   else if (fl.rcpt_smtp_response_same &&
            Ustrcmp(rcpt_smtp_response, msg) != 0)
@@ -3163,7 +3233,7 @@ not the whole MAIL/RCPT/DATA response set. */
 for (;;)
   {
   uschar *nl = Ustrchr(msg, '\n');
-  if (nl == NULL)
+  if (!nl)
     {
     smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg);
     return;
@@ -3178,7 +3248,7 @@ for (;;)
     {
     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);
     }
   }
 }
@@ -3219,27 +3289,26 @@ void
 smtp_message_code(uschar **code, int *codelen, uschar **msg, uschar **log_msg,
   BOOL check_valid)
 {
-int n;
-int ovector[3];
-
-if (!msg || !*msg) return;
+uschar * match;
+int len;
 
-if ((n = pcre_exec(regex_smtp_code, NULL, CS *msg, Ustrlen(*msg), 0,
-  PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int))) < 0) return;
+if (!msg || !*msg || !regex_match(regex_smtp_code, *msg, -1, &match))
+  return;
 
+len = Ustrlen(match);
 if (check_valid && (*msg)[0] != (*code)[0])
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "configured error code starts with "
     "incorrect digit (expected %c) in \"%s\"", (*code)[0], *msg);
-  if (log_msg != NULL && *log_msg == *msg)
-    *log_msg = string_sprintf("%s %s", *code, *log_msg + ovector[1]);
+  if (log_msg && *log_msg == *msg)
+    *log_msg = string_sprintf("%s %s", *code, *log_msg + len);
   }
 else
   {
   *code = *msg;
-  *codelen = ovector[1];    /* Includes final space */
+  *codelen = len;    /* Includes final space */
   }
-*msg += ovector[1];         /* Chop the code off the message */
+*msg += len;         /* Chop the code off the message */
 return;
 }
 
@@ -3290,18 +3359,7 @@ int codelen = 3;
 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;
 
@@ -3317,19 +3375,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. */
 
-#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
+  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
@@ -3419,7 +3503,7 @@ is closing if required and return 2.  */
 
 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"";
@@ -3521,7 +3605,7 @@ if (code && 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));
     }
@@ -3628,33 +3712,25 @@ else
   if (!f.helo_verified)
     {
     int rc;
-    host_item h;
-    dnssec_domains d;
-    host_item *hh;
-
-    h.name = sender_helo_name;
-    h.address = NULL;
-    h.mx = MX_NONE;
-    h.next = NULL;
-    d.request = US"*";
-    d.require = US"";
+    host_item h =
+      {.name = sender_helo_name, .address = NULL, .mx = MX_NONE, .next = NULL};
+    dnssec_domains d =
+      {.request = US"*", .require = US""};
 
     HDEBUG(D_receive) debug_printf("getting IP address for %s\n",
       sender_helo_name);
     rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA,
                          NULL, NULL, NULL, &d, NULL, NULL);
     if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
-      for (hh = &h; hh; hh = hh->next)
+      for (host_item * hh = &h; hh; hh = hh->next)
         if (Ustrcmp(hh->address, sender_host_address) == 0)
           {
           f.helo_verified = TRUE;
          if (h.dnssec == DS_YES) sender_helo_dnssec = TRUE;
           HDEBUG(D_receive)
-           {
             debug_printf("IP address for %s matches calling address\n"
              "Forward DNS security status: %sverified\n",
               sender_helo_name, sender_helo_dnssec ? "" : "un");
-           }
           break;
           }
     }
@@ -3694,10 +3770,16 @@ smtp_respond(code, len, TRUE, user_msg);
 
 
 static int
-smtp_in_auth(auth_instance *au, uschar ** s, uschar ** ss)
+smtp_in_auth(auth_instance *au, uschar ** smtp_resp, uschar ** errmsg)
 {
 const uschar *set_id = NULL;
-int rc, i;
+int rc;
+
+/* Set up globals for error messages */
+
+authenticator_name = au->name;
+driver_srcfile = au->srcfile;
+driver_srcline = au->srcline;
 
 /* Run the checking code, passing the remainder of the command line as
 data. Initials the $auth<n> variables as empty. Initialize $0 empty and set
@@ -3711,14 +3793,15 @@ userid. On success, require set_id to expand and exist, and put it in
 authenticated_id. Save this in permanent store, as the working store gets
 reset at HELO, RSET, etc. */
 
-for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
+for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
 expand_nmax = 0;
 expand_nlength[0] = 0;   /* $0 contains nothing */
 
 rc = (au->info->servercode)(au, smtp_cmd_data);
 if (au->set_id) set_id = expand_string(au->set_id);
 expand_nmax = -1;        /* Reset numeric variables */
-for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;   /* Reset $auth<n> */
+for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;   /* Reset $auth<n> */
+driver_srcfile = authenticator_name = NULL; driver_srcline = 0;
 
 /* The value of authenticated_id is stored in the spool file and printed in
 log lines. It must not contain binary zeros or newline characters. In
@@ -3740,60 +3823,60 @@ if (rc != 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? */
+
+      received_protocol =
+       (sender_host_address ? protocols : protocols_local)
+         [pextend + pauthed + (tls_in.active.sock >= 0 ? pcrpted:0)];
+      *smtp_resp = *errmsg = US"235 Authentication succeeded";
+      authenticated_by = au;
+      break;
+      }
 
-  /* Authentication succeeded, but we failed to expand the set_id string.
-  Treat this as a temporary error. */
+    /* 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 */
+    auth_defer_msg = expand_string_message;
+    /* Fall through */
 
   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);
+    *smtp_resp = string_sprintf("435 Unable to authenticate at present%s",
+      auth_defer_user_msg);
+    *errmsg = string_sprintf("435 Unable to authenticate at present%s: %s",
+      set_id, auth_defer_msg);
+    break;
 
   case BAD64:
-  *s = *ss = US"501 Invalid base64 data";
-  break;
+    *smtp_resp = *errmsg = US"501 Invalid base64 data";
+    break;
 
   case CANCELLED:
-  *s = *ss = US"501 Authentication cancelled";
-  break;
+    *smtp_resp = *errmsg = US"501 Authentication cancelled";
+    break;
 
   case UNEXPECTED:
-  *s = *ss = US"553 Initial data not expected";
-  break;
+    *smtp_resp = *errmsg = US"553 Initial data not expected";
+    break;
 
   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);
+    *smtp_resp = US"535 Incorrect authentication data";
+    *errmsg = string_sprintf("535 Incorrect authentication data%s", set_id);
+    break;
 
   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);
+    *smtp_resp = US"435 Internal error";
+    *errmsg = string_sprintf("435 Internal error%s: return %d from authentication "
+      "check", set_id, rc);
+    break;
   }
 
 return rc;
@@ -3812,7 +3895,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;
-  *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,
@@ -3830,25 +3914,44 @@ static void
 smtp_quit_handler(uschar ** user_msgp, uschar ** log_msgp)
 {
 HAD(SCH_QUIT);
+f.smtp_in_quit = TRUE;
 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);
-  }
+
+#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);
 
-#ifdef SUPPORT_TLS
+#ifdef SERVERSIDE_CLOSE_NOWAIT
+# ifndef DISABLE_TLS
 tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
-#endif
+# endif
+
+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) */
+
+(void) poll_one_fd(fileno(smtp_in), POLLIN, 200);
+#endif /*!SERVERSIDE_CLOSE_NOWAIT*/
 }
 
 
@@ -3859,9 +3962,18 @@ HAD(SCH_RSET);
 incomplete_transaction_log(US"RSET");
 smtp_printf("250 Reset OK\r\n", FALSE);
 cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
+if (chunking_state > CHUNKING_OFFERED)
+  chunking_state = CHUNKING_OFFERED;
 }
 
 
+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     *
@@ -3896,7 +4008,7 @@ 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");
 
@@ -3906,7 +4018,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. */
 
-smtp_reset(reset_point);
+reset_point = smtp_reset(reset_point);
 message_ended = END_NOTSTARTED;
 
 chunking_state = f.chunking_offered ? CHUNKING_OFFERED : CHUNKING_NOT_OFFERED;
@@ -3914,10 +4026,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;
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
 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;
@@ -3927,6 +4047,12 @@ os_non_restarting_signal(SIGTERM, command_sigterm_handler);
 
 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. */
 
@@ -3948,7 +4074,6 @@ while (done <= 0)
   int start, end, sender_domain, recipient_domain;
   int rc;
   int c;
-  auth_instance *au;
   uschar *orcpt = NULL;
   int dsn_flags;
   gstring * g;
@@ -3963,7 +4088,7 @@ while (done <= 0)
     {
     cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = FALSE;
 
-    for (au = auths; au; au = au->next)
+    for (auth_instance * au = auths; au; au = au->next)
       if (strcmpic(US"tls", au->driver_name) == 0)
        {
        if (  acl_smtp_auth
@@ -3978,21 +4103,26 @@ while (done <= 0)
          if (smtp_in_auth(au, &s, &ss) == OK)
            { DEBUG(D_auth) debug_printf("tls auth succeeded\n"); }
          else
-           { DEBUG(D_auth) debug_printf("tls auth not succeeded\n"); }
+           {
+           DEBUG(D_auth) debug_printf("tls auth not succeeded\n");
+#ifndef DISABLE_EVENT
+            {
+             uschar * save_name = sender_host_authenticated, * logmsg;
+             sender_host_authenticated = au->name;
+             if ((logmsg = event_raise(event_action, US"auth:fail", s, NULL)))
+               log_write(0, LOG_MAIN, "%s", logmsg);
+             sender_host_authenticated = save_name;
+            }
+#endif
+           }
          }
        break;
        }
     }
 #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(
-#ifdef EXPERIMENTAL_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
          !fl.pipe_connect_acceptable,
 #else
          TRUE,
@@ -4051,21 +4181,18 @@ while (done <= 0)
       /* 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;
          }
-       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 (*smtp_cmd_data != 0)
+      if (*smtp_cmd_data)
        {
        *smtp_cmd_data++ = 0;
        while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
@@ -4075,23 +4202,41 @@ while (done <= 0)
       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 || f.allow_auth_unadvertised))
-         break;
-
-      if (au)
        {
-       c = smtp_in_auth(au, &s, &ss);
+       auth_instance * au;
+       uschar * smtp_resp, * errmsg;
 
-       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);
+       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)
+         {
+         int rc = smtp_in_auth(au, &smtp_resp, &errmsg);
+
+         smtp_printf("%s\r\n", FALSE, smtp_resp);
+         if (rc != OK)
+           {
+           uschar * logmsg = NULL;
+#ifndef DISABLE_EVENT
+            {uschar * save_name = sender_host_authenticated;
+             sender_host_authenticated = au->name;
+             logmsg = event_raise(event_action, US"auth:fail", smtp_resp, NULL);
+             sender_host_authenticated = save_name;
+            }
+#endif
+           if (logmsg)
+             log_write(0, LOG_MAIN|LOG_REJECT, "%s", logmsg);
+           else
+             log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
+               au->name, host_and_ident(FALSE), errmsg);
+           }
+         }
+       else
+         done = synprot_error(L_smtp_protocol_error, 504, NULL,
+           string_sprintf("%s authentication mechanism not supported", s));
        }
-      else
-       done = synprot_error(L_smtp_protocol_error, 504, NULL,
-         string_sprintf("%s authentication mechanism not supported", s));
 
       break;  /* AUTH_CMD */
 
@@ -4141,8 +4286,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 "
-           "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;
          }
 
@@ -4152,7 +4299,7 @@ while (done <= 0)
       /* 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
+      host and we haven't done a reverse lookup, force one now. If helo_verify_required
       is set, ensure that the HELO name matches the actual host. If helo_verify
       is set, do the same check, but softly. */
 
@@ -4180,19 +4327,19 @@ while (done <= 0)
          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,
+       make some people happy to be able to do it. If helo_verify_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. */
 
        f.helo_verified = f.helo_verify_failed = sender_helo_dnssec = FALSE;
-       if (fl.helo_required || fl.helo_verify)
+       if (fl.helo_verify_required || fl.helo_verify)
          {
          BOOL tempfail = !smtp_verify_helo();
          if (!f.helo_verified)
            {
-           if (fl.helo_required)
+           if (fl.helo_verify_required)
              {
              smtp_printf("%d %s argument does not match calling host\r\n", FALSE,
                tempfail? 451 : 550, hello);
@@ -4210,7 +4357,7 @@ while (done <= 0)
 
 #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
@@ -4221,15 +4368,11 @@ while (done <= 0)
                  &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;
          }
-#ifdef EXPERIMENTAL_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
        else if (!fl.pipe_connect_acceptable && !check_sync())
 #else
        else if (!check_sync())
@@ -4244,21 +4387,23 @@ while (done <= 0)
 
       fl.auth_advertised = FALSE;
       f.smtp_in_pipelining_advertised = FALSE;
-#ifdef SUPPORT_TLS
+#ifndef DISABLE_TLS
       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
 
+      /* Expand the per-connection message count limit option */
+      smtp_mailcmd_max = expand_mailmax(smtp_accept_max_per_connection);
+
       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, GET_TAINTED);
+       g = string_fmt_append(g, "%.3s %s Hello %s%s%s",
          smtp_code,
          smtp_active_hostname,
          sender_ident ? sender_ident : US"",
@@ -4275,7 +4420,7 @@ while (done <= 0)
 
       else
        {
-       char *ss;
+       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);
@@ -4312,6 +4457,19 @@ while (done <= 0)
          g = string_catn(g, US"-SIZE\r\n", 7);
          }
 
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+       if (  (smtp_mailcmd_max > 0 || recipients_max)
+          && verify_check_host(&limits_advertise_hosts) == OK)
+         {
+         g = string_fmt_append(g, "%.3s-LIMITS", smtp_code);
+         if (smtp_mailcmd_max > 0)
+           g = string_fmt_append(g, " MAILMAX=%d", smtp_mailcmd_max);
+         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.
@@ -4363,7 +4521,7 @@ while (done <= 0)
          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;
@@ -4391,16 +4549,15 @@ while (done <= 0)
           && verify_check_host(&auth_advertise_hosts) == OK
           )
          {
-         auth_instance *au;
          BOOL first = TRUE;
-         for (au = auths; au; au = au->next)
+         for (auth_instance * au = auths; au; au = au->next)
            {
            au->advertised = FALSE;
            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")
@@ -4441,7 +4598,7 @@ while (done <= 0)
        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)
          {
@@ -4449,17 +4606,6 @@ while (done <= 0)
          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
@@ -4490,29 +4636,24 @@ while (done <= 0)
       /* 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,
-# ifdef EXPERIMENTAL_PIPE_CONNECT
+# ifndef DISABLE_PIPE_CONNECT
                        fl.pipe_connect_acceptable && pipeline_connect_sends());
 # else
                        FALSE);
 # endif
       else
 #endif
-
-       {
-       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);
-       }
+       (void) fwrite(g->s, 1, g->ptr, smtp_out);
+
+      DEBUG(D_receive) for (const uschar * t, * s = string_from_gstring(g);
+                           s && (t = Ustrchr(s, '\r'));
+                           s = t + 2)                          /* \r\n */
+         debug_printf("%s %.*s\n",
+                       s == g->s ? "SMTP>>" : "      ",
+                       (int)(t - s), s);
       fl.helo_seen = TRUE;
 
       /* Reset the protocol and the state, abandoning any previous message. */
@@ -4524,7 +4665,7 @@ while (done <= 0)
          + (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 */
 
@@ -4538,16 +4679,21 @@ while (done <= 0)
     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 */
 
-      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_verify_required
+          || verify_check_host(&hosts_require_helo) == OK)
+         {
+         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 (smtp_mailcmd_max < 0)
+         smtp_mailcmd_max = expand_mailmax(smtp_accept_max_per_connection);
 
       if (sender_address)
        {
@@ -4566,8 +4712,7 @@ while (done <= 0)
       /* 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 (smtp_mailcmd_max > 0 && smtp_mailcmd_count > smtp_mailcmd_max)
        {
        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 "
@@ -4579,7 +4724,7 @@ while (done <= 0)
       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;
 
@@ -4784,28 +4929,6 @@ while (done <= 0)
            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.
@@ -4823,17 +4946,6 @@ while (done <= 0)
        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. */
 
@@ -4852,7 +4964,8 @@ while (done <= 0)
       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;
 
@@ -4896,8 +5009,8 @@ while (done <= 0)
       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;
@@ -4914,7 +5027,8 @@ while (done <= 0)
        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);
          }
@@ -4984,6 +5098,10 @@ while (done <= 0)
 
     case RCPT_CMD:
       HAD(SCH_RCPT);
+      /* We got really to many recipients. A check against configured
+      limits is done later */
+      if (rcpt_count < 0 || rcpt_count >= INT_MAX/2)
+        log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Too many recipients: %d", rcpt_count);
       rcpt_count++;
       was_rcpt = fl.rcpt_in_progress = TRUE;
 
@@ -4992,7 +5110,7 @@ while (done <= 0)
       count this as a protocol error. Reset was_rej_mail so that further RCPTs
       get the same treatment. */
 
-      if (sender_address == NULL)
+      if (!sender_address)
        {
        if (f.smtp_in_pipelining_advertised && last_was_rej_mail)
          {
@@ -5011,7 +5129,7 @@ while (done <= 0)
 
       /* Check for an operand */
 
-      if (smtp_cmd_data[0] == 0)
+      if (!smtp_cmd_data[0])
        {
        done = synprot_error(L_smtp_syntax_error, 501, NULL,
          US"RCPT must have an address operand");
@@ -5106,7 +5224,8 @@ while (done <= 0)
       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;
 
@@ -5139,7 +5258,7 @@ while (done <= 0)
 
       /* Check maximum allowed */
 
-      if (rcpt_count > recipients_max && recipients_max > 0)
+      if (rcpt_count+1 < 0 || rcpt_count > recipients_max && recipients_max > 0)
        {
        if (recipients_max_reject)
          {
@@ -5206,9 +5325,9 @@ while (done <= 0)
        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].dsn_flags);
+         recipients_list[recipients_count-1].dsn_flags); */
        }
 
       /* The recipient was discarded */
@@ -5223,8 +5342,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),
-         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"");
        }
 
@@ -5284,16 +5403,7 @@ while (done <= 0)
       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; /* FIXME: redundant vs chunking_state? */
       f.dot_ends = FALSE;
 
       goto DATA_BDAT;
@@ -5302,14 +5412,15 @@ while (done <= 0)
     case DATA_CMD:
       HAD(SCH_DATA);
       f.dot_ends = TRUE;
+      f.bdat_readers_wanted = FALSE;
 
     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)
        {
-       if (fl.rcpt_smtp_response_same && rcpt_smtp_response != NULL)
+       if (fl.rcpt_smtp_response_same && rcpt_smtp_response)
          {
          uschar *code = US"503";
          int len = Ustrlen(rcpt_smtp_response);
@@ -5322,15 +5433,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,
-           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,
-           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)
+         {
+         bdat_push_receive_functions();
          bdat_flush_data();
+         }
        break;
        }
 
@@ -5339,6 +5453,12 @@ while (done <= 0)
        sender_address = NULL;  /* This will allow a new MAIL without RSET */
        sender_address_unrewritten = NULL;
        smtp_printf("554 Too many recipients\r\n", FALSE);
+
+       if (chunking_state > CHUNKING_OFFERED)
+         {
+         bdat_push_receive_functions();
+         bdat_flush_data();
+         }
        break;
        }
 
@@ -5350,7 +5470,7 @@ while (done <= 0)
        ACL may have delayed.  To handle cutthrough delivery enforce a dummy call
        to get the DATA command sent. */
 
-       if (acl_smtp_predata == NULL && cutthrough.cctx.sock < 0)
+       if (!acl_smtp_predata && cutthrough.cctx.sock < 0)
          rc = OK;
        else
          {
@@ -5376,6 +5496,9 @@ while (done <= 0)
            "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,
@@ -5459,7 +5582,7 @@ while (done <= 0)
       break;
 
 
-    #ifdef SUPPORT_TLS
+    #ifndef DISABLE_TLS
 
     case STARTTLS_CMD:
       HAD(SCH_STARTTLS);
@@ -5488,7 +5611,7 @@ while (done <= 0)
 
       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;
 
@@ -5500,7 +5623,7 @@ while (done <= 0)
       Pipelining sync checks will normally have protected us too, unless disabled
       by configuration. */
 
-      if (receive_smtp_buffered())
+      if (receive_hasc())
        {
        DEBUG(D_any)
          debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n");
@@ -5527,7 +5650,7 @@ while (done <= 0)
       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;
@@ -5536,7 +5659,6 @@ while (done <= 0)
        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",
@@ -5590,6 +5712,7 @@ while (done <= 0)
        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,
@@ -5627,7 +5750,7 @@ while (done <= 0)
     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;
 
@@ -5649,17 +5772,17 @@ while (done <= 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)
-         Ustrcat(buffer, " STARTTLS");
+         Ustrcat(buffer, US" 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");
+       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;
@@ -5734,7 +5857,7 @@ while (done <= 0)
        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);
+         FALSE, US"ETRN processing", &error);
        deliver_domain = NULL;
        if (!rc)
          {
@@ -5795,7 +5918,7 @@ while (done <= 0)
 
       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. */
@@ -5806,10 +5929,12 @@ while (done <= 0)
        /* 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 */
+         /* argv[0] should be untainted, from child_exec_exim() */
          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));
@@ -5835,7 +5960,7 @@ while (done <= 0)
          }
 
        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
@@ -5849,10 +5974,10 @@ while (done <= 0)
        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;
@@ -5938,7 +6063,6 @@ while (done <= 0)
   COMMAND_LOOP:
   last_was_rej_mail = was_rej_mail;     /* Remember some last commands for */
   last_was_rcpt = was_rcpt;             /* protocol error handling */
-  continue;
   }
 
 return done - 2;  /* Convert yield values */
@@ -5954,12 +6078,14 @@ if (!sender_host_authenticated)
 
 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
-  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);