Fix logging of max-size log line
[exim.git] / src / src / smtp_in.c
index 5888b8037036691c5f24d575103d49f5d83f05b4..6c043d4346aa62e5c9cc9effb52ad13b663e1e66 100644 (file)
@@ -2,9 +2,10 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 */
 /* 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. */
 
@@ -139,7 +140,7 @@ static struct {
 #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;
@@ -153,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,
 };
@@ -319,96 +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 = {.tv_sec = 0, .tv_usec = 0};
-
-#ifndef DISABLE_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);
-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;
-}
-
-
-#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
-
 /*************************************************
 *          Log incomplete transactions           *
 *************************************************/
@@ -432,7 +343,7 @@ if (!sender_address                         /* No transaction in progress */
 
 if (recipients_count > 0)
   {
-  raw_recipients = store_get(recipients_count * sizeof(uschar *), FALSE);
+  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;
@@ -491,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.
@@ -500,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);
@@ -541,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.
@@ -557,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;
@@ -580,19 +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 (chunking_state == CHUNKING_LAST && chunking_data_left < n)
-  n = chunking_data_left;
+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
@@ -624,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;
@@ -658,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;
     }
@@ -730,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
@@ -742,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)
 {
@@ -765,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     *
@@ -918,15 +953,12 @@ that we'll never expand it. */
 yield = !! string_vformat(&gs, SVFMT_TAINT_NOCHK, format, ap);
 string_from_gstring(&gs);
 
-DEBUG(D_receive)
-  {
-  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);
-  }
+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)
   {
@@ -943,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)
@@ -994,6 +1026,35 @@ 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             *
 *************************************************/
@@ -1615,12 +1676,13 @@ for (smtp_cmd_list * 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
@@ -1669,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;
@@ -1695,7 +1758,7 @@ Returns:    nothing
 */
 
 void
-smtp_closedown(uschar *message)
+smtp_closedown(uschar * message)
 {
 if (!smtp_in || smtp_batched_input) return;
 receive_swallow_smtp();
@@ -1967,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;
@@ -2089,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. */
@@ -2205,9 +2278,11 @@ while (done <= 0)
 
       /* 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 */
 
@@ -2227,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);
          }
@@ -2266,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;
 
@@ -2285,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
@@ -2362,9 +2440,9 @@ return done - 2;  /* Convert yield values */
 
 #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 */
@@ -2427,6 +2505,22 @@ else DEBUG(D_receive)
 #endif
 
 
+static void
+log_connect_tls_drop(const uschar * what, const uschar * log_msg)
+{
+gstring * g = s_tlslog(NULL);
+uschar * tls = string_from_gstring(g);
+
+log_write(L_connection_reject,
+  log_reject_target, "%s%s%s dropped by %s%s%s",
+  LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"",
+  host_and_ident(TRUE),
+  tls ? tls : US"",
+  what,
+  log_msg ? US": " : US"", log_msg);
+}
+
+
 /*************************************************
 *          Start an SMTP session                 *
 *************************************************/
@@ -2492,7 +2586,7 @@ acl_var_c = NULL;
 
 /* Allow for trailing 0 in the command and data buffers.  Tainted. */
 
-smtp_cmd_buffer = store_get_perm(2*SMTP_CMD_BUFFER_SIZE + 2, TRUE);
+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;
@@ -2513,22 +2607,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 */
 
@@ -2603,7 +2696,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, FALSE);
+    struct ip_options *ipopt = store_get(optlen, GET_UNTAINTED);
     #elif OPTSTYLE == 2
     struct ip_opts ipoptblock;
     struct ip_opts *ipopt = &ipoptblock;
@@ -2780,7 +2873,10 @@ if (!f.sender_host_unknown)
     {
     log_write(L_connection_reject, LOG_MAIN|LOG_REJECT, "refused connection "
       "from %s (host_reject_connection)", host_and_ident(FALSE));
-    smtp_printf("554 SMTP service not available\r\n", FALSE);
+#ifndef DISABLE_TLS
+    if (!tls_in.on_connect)
+#endif
+      smtp_printf("554 SMTP service not available\r\n", FALSE);
     return FALSE;
     }
 
@@ -2882,8 +2978,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,18 +3002,6 @@ 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. */
-
-#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 */
 
 user_msg = NULL;
@@ -2927,11 +3011,28 @@ if (acl_smtp_connect)
   if ((rc = acl_check(ACL_WHERE_CONNECT, NULL, acl_smtp_connect, &user_msg,
                      &log_msg)) != OK)
     {
-    (void) smtp_handle_acl_fail(ACL_WHERE_CONNECT, rc, user_msg, log_msg);
+#ifndef DISABLE_TLS
+    if (tls_in.on_connect)
+      log_connect_tls_drop(US"'connect' ACL", log_msg);
+    else
+#endif
+      (void) smtp_handle_acl_fail(ACL_WHERE_CONNECT, rc, user_msg, log_msg);
     return FALSE;
     }
   }
 
+/* Start up TLS if tls_on_connect is set. This is for supporting the legacy
+smtps port for use with older style SSL MTAs. */
+
+#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
+
 /* Output the initial message for a two-way SMTP connection. It may contain
 newlines, which then cause a multi-line response to be given. */
 
@@ -2939,13 +3040,7 @@ code = US"220";   /* Default status code */
 esc = US"";       /* Default extended status code */
 esclen = 0;       /* Length of esc */
 
-if (!user_msg)
-  {
-  if (!(s = expand_string(smtp_banner)))
-    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" (smtp_banner) "
-      "failed: %s", smtp_banner, expand_string_message);
-  }
-else
+if (user_msg)
   {
   int codelen = 3;
   s = user_msg;
@@ -2956,12 +3051,23 @@ else
     esclen = codelen - 4;
     }
   }
+else if (!(s = expand_string(smtp_banner)))
+  {
+  log_write(0, f.expand_string_forcedfail ? LOG_MAIN : LOG_MAIN|LOG_PANIC_DIE,
+    "Expansion of \"%s\" (smtp_banner) failed: %s",
+    smtp_banner, expand_string_message);
+  /* for force-fail */
+#ifndef DISABLE_TLS
+  if (tls_in.on_connect) tls_close(NULL, TLS_SHUTDOWN_WAIT);
+#endif
+  return FALSE;
+  }
 
 /* Remove any terminating newlines; might as well remove trailing space too */
 
 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
@@ -3141,7 +3247,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)
@@ -3156,7 +3262,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;
@@ -3212,27 +3318,26 @@ void
 smtp_message_code(uschar **code, int *codelen, uschar **msg, uschar **log_msg,
   BOOL check_valid)
 {
-int n;
-int ovector[3];
+uschar * match;
+int len;
 
-if (!msg || !*msg) return;
-
-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;
 }
 
@@ -3694,11 +3799,17 @@ 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;
 
+/* 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
 it as the only set numerical variable. The authenticator may set $auth<n>
@@ -3719,6 +3830,7 @@ 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 (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
@@ -3751,7 +3863,7 @@ switch(rc)
       received_protocol =
        (sender_host_address ? protocols : protocols_local)
          [pextend + pauthed + (tls_in.active.sock >= 0 ? pcrpted:0)];
-      *s = *ss = US"235 Authentication succeeded";
+      *smtp_resp = *errmsg = US"235 Authentication succeeded";
       authenticated_by = au;
       break;
       }
@@ -3764,34 +3876,34 @@ switch(rc)
 
   case DEFER:
     if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE);
-    *s = string_sprintf("435 Unable to authenticate at present%s",
+    *smtp_resp = string_sprintf("435 Unable to authenticate at present%s",
       auth_defer_user_msg);
-    *ss = string_sprintf("435 Unable to authenticate at present%s: %s",
+    *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";
+    *smtp_resp = *errmsg = US"501 Invalid base64 data";
     break;
 
   case CANCELLED:
-    *s = *ss = US"501 Authentication cancelled";
+    *smtp_resp = *errmsg = US"501 Authentication cancelled";
     break;
 
   case UNEXPECTED:
-    *s = *ss = US"553 Initial data not expected";
+    *smtp_resp = *errmsg = US"553 Initial data not expected";
     break;
 
   case FAIL:
     if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE);
-    *s = US"535 Incorrect authentication data";
-    *ss = string_sprintf("535 Incorrect authentication data%s", set_id);
+    *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_perm(set_id, TRUE);
-    *s = US"435 Internal error";
-    *ss = string_sprintf("435 Internal error%s: return %d from authentication "
+    *smtp_resp = US"435 Internal error";
+    *errmsg = string_sprintf("435 Internal error%s: return %d from authentication "
       "check", set_id, rc);
     break;
   }
@@ -3812,7 +3924,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,
@@ -3847,9 +3960,18 @@ if (*user_msgp)
 else
   smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
 
-#ifndef DISABLE_TLS
+#ifdef SERVERSIDE_CLOSE_NOWAIT
+# ifndef DISABLE_TLS
+tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
+# 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
+# endif
 
 log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
   smtp_get_connection_info());
@@ -3857,15 +3979,8 @@ log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
 /* Pause, hoping client will FIN first so that they get the TIME_WAIT.
 The socket should become readble (though with no data) */
 
-  {
-  int fd = fileno(smtp_in);
-  fd_set fds;
-  struct timeval t_limit = {.tv_sec = 0, .tv_usec = 200*1000};
-
-  FD_ZERO(&fds);
-  FD_SET(fd, &fds);
-  (void) select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &t_limit);
-  }
+(void) poll_one_fd(fileno(smtp_in), POLLIN, 200);
+#endif /*!SERVERSIDE_CLOSE_NOWAIT*/
 }
 
 
@@ -3876,6 +3991,8 @@ 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;
 }
 
 
@@ -3916,7 +4033,6 @@ int
 smtp_setup_msg(void)
 {
 int done = 0;
-int mailmax = -1;
 BOOL toomany = FALSE;
 BOOL discarded = FALSE;
 BOOL last_was_rej_mail = FALSE;
@@ -3943,6 +4059,14 @@ cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
 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;
@@ -4008,7 +4132,18 @@ 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;
        }
@@ -4098,6 +4233,8 @@ while (done <= 0)
 
        {
        auth_instance * au;
+       uschar * smtp_resp, * errmsg;
+
        for (au = auths; au; au = au->next)
          if (strcmpic(s, au->public_name) == 0 && au->server &&
              (au->advertised || f.allow_auth_unadvertised))
@@ -4105,12 +4242,25 @@ while (done <= 0)
 
        if (au)
          {
-         c = smtp_in_auth(au, &s, &ss);
+         int rc = smtp_in_auth(au, &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);
+         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,
@@ -4178,7 +4328,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. */
 
@@ -4206,19 +4356,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);
@@ -4275,13 +4425,13 @@ while (done <= 0)
 #endif
 
       /* Expand the per-connection message count limit option */
-      mailmax = expand_mailmax(smtp_accept_max_per_connection);
+      smtp_mailcmd_max = expand_mailmax(smtp_accept_max_per_connection);
 
       smtp_code = US"250 ";        /* Default response code plus space*/
       if (!user_msg)
        {
        /* sender_host_name below will be tainted, so save on copy when we hit it */
-       g = string_get_tainted(24, TRUE);
+       g = string_get_tainted(24, GET_TAINTED);
        g = string_fmt_append(g, "%.3s %s Hello %s%s%s",
          smtp_code,
          smtp_active_hostname,
@@ -4299,7 +4449,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);
@@ -4336,6 +4486,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.
@@ -4514,15 +4677,12 @@ while (done <= 0)
 #endif
        (void) fwrite(g->s, 1, g->ptr, smtp_out);
 
-      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);
-       }
+      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. */
@@ -4553,15 +4713,16 @@ while (done <= 0)
       env_mail_type_t * mail_args;       /* Sanity check & validate args */
 
       if (!fl.helo_seen)
-       if (fl.helo_required)
+       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 (mailmax < 0)
-         mailmax = expand_mailmax(smtp_accept_max_per_connection);
+       else if (smtp_mailcmd_max < 0)
+         smtp_mailcmd_max = expand_mailmax(smtp_accept_max_per_connection);
 
       if (sender_address)
        {
@@ -4580,7 +4741,7 @@ while (done <= 0)
       /* Check to see if the limit for messages per connection would be
       exceeded by accepting further messages. */
 
-      if (mailmax > 0 && smtp_mailcmd_count > mailmax)
+      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 "
@@ -4832,7 +4993,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;
 
@@ -4894,7 +5056,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);
          }
@@ -4964,6 +5127,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;
 
@@ -4972,7 +5139,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)
          {
@@ -4991,7 +5158,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");
@@ -5086,7 +5253,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;
 
@@ -5119,7 +5287,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)
          {
@@ -5264,16 +5432,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;
@@ -5282,6 +5441,7 @@ 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 */
 #ifndef DISABLE_PIPE_CONNECT
@@ -5289,7 +5449,7 @@ while (done <= 0)
 #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);
@@ -5310,7 +5470,10 @@ while (done <= 0)
            : US"valid RCPT command must precede BDAT");
 
        if (chunking_state > CHUNKING_OFFERED)
+         {
+         bdat_push_receive_functions();
          bdat_flush_data();
+         }
        break;
        }
 
@@ -5319,6 +5482,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;
        }
 
@@ -5330,7 +5499,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
          {
@@ -5356,6 +5525,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,
@@ -5480,7 +5652,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");
@@ -5714,7 +5886,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)
          {
@@ -5791,6 +5963,7 @@ while (done <= 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));
@@ -5919,7 +6092,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 */