Tidying
[exim.git] / src / src / smtp_in.c
index 824178e4d6f7a7c6bddefdd12a7a43795441aff6..5b2df7805c1edad65d43ae9184f5670b6aa301f5 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 - 2021 */
 /* 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. */
 
@@ -188,16 +189,22 @@ count of non-mail commands and possibly provoke an error.
 tls_auth is a pseudo-command, never expected in input.  It is activated
 on TLS startup and looks for a tls authenticator. */
 
+enum { CL_RSET, CL_HELO, CL_EHLO, CL_AUTH,
+#ifndef DISABLE_TLS
+       CL_STLS, CL_TLAU,
+#endif
+};
+
 static smtp_cmd_list cmd_list[] = {
   /* name         len                     cmd     has_arg is_mail_cmd */
 
-  { "rset",       sizeof("rset")-1,       RSET_CMD, FALSE, FALSE },  /* First */
-  { "helo",       sizeof("helo")-1,       HELO_CMD, TRUE,  FALSE },
-  { "ehlo",       sizeof("ehlo")-1,       EHLO_CMD, TRUE,  FALSE },
-  { "auth",       sizeof("auth")-1,       AUTH_CMD, TRUE,  TRUE  },
+  [CL_RSET] = { "rset",       sizeof("rset")-1,       RSET_CMD,        FALSE, FALSE },  /* First */
+  [CL_HELO] = { "helo",       sizeof("helo")-1,       HELO_CMD, TRUE,  FALSE },
+  [CL_EHLO] = { "ehlo",       sizeof("ehlo")-1,       EHLO_CMD, TRUE,  FALSE },
+  [CL_AUTH] = { "auth",       sizeof("auth")-1,       AUTH_CMD,     TRUE,  TRUE  },
 #ifndef DISABLE_TLS
-  { "starttls",   sizeof("starttls")-1,   STARTTLS_CMD, FALSE, FALSE },
-  { "tls_auth",   0,                      TLS_AUTH_CMD, FALSE, FALSE },
+  [CL_STLS] = { "starttls",   sizeof("starttls")-1,   STARTTLS_CMD, FALSE, FALSE },
+  [CL_TLAU] = { "tls_auth",   0,                      TLS_AUTH_CMD, FALSE, FALSE },
 #endif
 
 /* If you change anything above here, also fix the definitions below. */
@@ -214,24 +221,27 @@ static smtp_cmd_list cmd_list[] = {
   { "help",       sizeof("help")-1,       HELP_CMD, TRUE,  FALSE }
 };
 
-static smtp_cmd_list *cmd_list_end =
-  cmd_list + sizeof(cmd_list)/sizeof(smtp_cmd_list);
-
-#define CMD_LIST_RSET      0
-#define CMD_LIST_HELO      1
-#define CMD_LIST_EHLO      2
-#define CMD_LIST_AUTH      3
-#define CMD_LIST_STARTTLS  4
-#define CMD_LIST_TLS_AUTH  5
-
-/* This list of names is used for performing the smtp_no_mail logging action.
-It must be kept in step with the SCH_xxx enumerations. */
+/* This list of names is used for performing the smtp_no_mail logging action. */
 
 uschar * smtp_names[] =
   {
-  US"NONE", US"AUTH", US"DATA", US"BDAT", US"EHLO", US"ETRN", US"EXPN",
-  US"HELO", US"HELP", US"MAIL", US"NOOP", US"QUIT", US"RCPT", US"RSET",
-  US"STARTTLS", US"VRFY" };
+  [SCH_NONE] = US"NONE",
+  [SCH_AUTH] = US"AUTH",
+  [SCH_DATA] = US"DATA",
+  [SCH_BDAT] = US"BDAT",
+  [SCH_EHLO] = US"EHLO",
+  [SCH_ETRN] = US"ETRN",
+  [SCH_EXPN] = US"EXPN",
+  [SCH_HELO] = US"HELO",
+  [SCH_HELP] = US"HELP",
+  [SCH_MAIL] = US"MAIL",
+  [SCH_NOOP] = US"NOOP",
+  [SCH_QUIT] = US"QUIT",
+  [SCH_RCPT] = US"RCPT",
+  [SCH_RSET] = US"RSET",
+  [SCH_STARTTLS] = US"STARTTLS",
+  [SCH_VRFY] = US"VRFY",
+  };
 
 static uschar *protocols_local[] = {
   US"local-smtp",        /* HELO */
@@ -319,96 +329,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           *
 *************************************************/
@@ -422,7 +342,7 @@ Returns:    nothing
 */
 
 static void
-incomplete_transaction_log(uschar *what)
+incomplete_transaction_log(uschar * what)
 {
 if (!sender_address                            /* No transaction in progress */
    || !LOGGING(smtp_incomplete_transaction))
@@ -432,7 +352,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;
@@ -444,13 +364,21 @@ log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS,
 
 
 
+static void
+log_close_event(const uschar * reason)
+{
+log_write(L_smtp_connection, LOG_MAIN, "%s D=%s closed %s",
+  smtp_get_connection_info(), string_timesince(&smtp_connection_start), reason);
+}
+
 
 void
 smtp_command_timeout_exit(void)
 {
 log_write(L_lost_incoming_connection,
-         LOG_MAIN, "SMTP command timeout on%s connection from %s",
-         tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE));
+         LOG_MAIN, "SMTP command timeout on%s connection from %s D=%s",
+         tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE),
+         string_timesince(&smtp_connection_start));
 if (smtp_batched_input)
   moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */
 smtp_notquit_exit(US"command-timeout", US"421",
@@ -462,7 +390,7 @@ exim_exit(EXIT_FAILURE);
 void
 smtp_command_sigterm_exit(void)
 {
-log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info());
+log_close_event(US"after SIGTERM");
 if (smtp_batched_input)
   moan_smtp_batch(NULL, "421 SIGTERM received");  /* Does not return */
 smtp_notquit_exit(US"signal-exit", US"421",
@@ -473,9 +401,10 @@ exim_exit(EXIT_FAILURE);
 void
 smtp_data_timeout_exit(void)
 {
-log_write(L_lost_incoming_connection,
-  LOG_MAIN, "SMTP data timeout (message abandoned) on connection from %s F=<%s>",
-  sender_fullhost ? sender_fullhost : US"local process", sender_address);
+log_write(L_lost_incoming_connection, LOG_MAIN,
+  "SMTP data timeout (message abandoned) on connection from %s F=<%s> D=%s",
+  sender_fullhost ? sender_fullhost : US"local process", sender_address,
+  string_timesince(&smtp_connection_start));
 receive_bomb_out(US"data-timeout", US"SMTP incoming data timeout");
 /* Does not return */
 }
@@ -483,14 +412,32 @@ receive_bomb_out(US"data-timeout", US"SMTP incoming data timeout");
 void
 smtp_data_sigint_exit(void)
 {
-log_write(0, LOG_MAIN, "%s closed after %s",
-  smtp_get_connection_info(), had_data_sigint == SIGTERM ? "SIGTERM":"SIGINT");
+log_close_event(had_data_sigint == SIGTERM ? US"SIGTERM":US"SIGINT");
 receive_bomb_out(US"signal-exit",
   US"Service not available - SIGTERM or SIGINT received");
 /* Does not return */
 }
 
 
+/******************************************************************************/
+/* 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 +447,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 +489,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,17 +512,11 @@ 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++;
 }
 
-BOOL
-smtp_hasc(void)
-{
-return smtp_inptr < smtp_inend;
-}
+/* Get many bytes, refilling buffer if needed */
 
 uschar *
 smtp_getbuf(unsigned * len)
@@ -575,9 +524,8 @@ 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;
@@ -586,6 +534,9 @@ 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(unsigned lim)
 {
@@ -599,6 +550,130 @@ if (n > 0)
 }
 
 
+/* 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);
@@ -783,12 +858,8 @@ while (chunking_data_left)
   }
 
 bdat_pop_receive_functions();
-
-if (chunking_state != CHUNKING_LAST)
-  {
-  chunking_state = CHUNKING_OFFERED;
-  DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
-  }
+chunking_state = CHUNKING_OFFERED;
+DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
 }
 
 
@@ -835,30 +906,6 @@ lwr_receive_hasc = NULL;
 lwr_receive_ungetc = NULL;
 }
 
-/*************************************************
-*          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)
-  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in smtp_ungetc");
-
-*--smtp_inptr = ch;
-return ch;
-}
-
-
 int
 bdat_ungetc(int ch)
 {
@@ -869,62 +916,7 @@ 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     *
@@ -978,15 +970,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)
   {
@@ -1003,7 +992,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)
@@ -1054,6 +1043,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             *
 *************************************************/
@@ -1186,6 +1204,16 @@ errno = EOVERFLOW;
 return -1;
 }
 
+
+static void
+proxy_debug(uschar * buf, unsigned start, unsigned end)
+{
+debug_printf("PROXY<<");
+while (start < end) debug_printf(" %02x", buf[start++]);
+debug_printf("\n");
+}
+
+
 /*************************************************
 *         Setup host for proxy protocol          *
 *************************************************/
@@ -1262,11 +1290,11 @@ So to safely handle v1 and v2, with client-sent-first supported correctly,
 we have to do a minimum of 3 read calls, not 1.  Eww.
 */
 
-#define PROXY_INITIAL_READ 14
-#define PROXY_V2_HEADER_SIZE 16
-#if PROXY_INITIAL_READ > PROXY_V2_HEADER_SIZE
-# error Code bug in sizes of data to read for proxy usage
-#endif
+# define PROXY_INITIAL_READ 14
+# define PROXY_V2_HEADER_SIZE 16
+# if PROXY_INITIAL_READ > PROXY_V2_HEADER_SIZE
+#  error Code bug in sizes of data to read for proxy usage
+# endif
 
 int get_ok = 0;
 int size, ret;
@@ -1286,11 +1314,11 @@ do
   "safe". Can't take it all because TLS-on-connect clients follow
   immediately with TLS handshake. */
   ret = read(fd, &hdr, PROXY_INITIAL_READ);
-  }
-  while (ret == -1 && errno == EINTR && !had_command_timeout);
+  } while (ret == -1 && errno == EINTR && !had_command_timeout);
 
 if (ret == -1)
   goto proxyfail;
+DEBUG(D_receive) proxy_debug(US &hdr, 0, ret);
 
 /* For v2, handle reading the length, and then the rest. */
 if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
@@ -1298,6 +1326,8 @@ if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
   int retmore;
   uint8_t ver;
 
+  DEBUG(D_receive) debug_printf("v2\n");
+
   /* First get the length fields. */
   do
     {
@@ -1305,6 +1335,8 @@ if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
     } while (retmore == -1 && errno == EINTR && !had_command_timeout);
   if (retmore == -1)
     goto proxyfail;
+  DEBUG(D_receive) proxy_debug(US &hdr, ret, ret + retmore);
+
   ret += retmore;
 
   ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
@@ -1342,6 +1374,7 @@ if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
       } while (retmore == -1 && errno == EINTR && !had_command_timeout);
     if (retmore == -1)
       goto proxyfail;
+    DEBUG(D_receive) proxy_debug(US &hdr, ret, ret + retmore);
     ret += retmore;
     DEBUG(D_receive) debug_printf("PROXYv2: have %d/%d required octets\n", ret, size);
     } while (ret < size);
@@ -1587,7 +1620,7 @@ bad:
 ALARM(0);
 return;
 }
-#endif
+#endif /*SUPPORT_PROXY*/
 
 /*************************************************
 *           Read one command line                *
@@ -1661,7 +1694,7 @@ if (hadnull) return BADCHAR_CMD;
 to the start of the actual data characters. Check for SMTP synchronization
 if required. */
 
-for (smtp_cmd_list * p = cmd_list; p < cmd_list_end; p++)
+for (smtp_cmd_list * p = cmd_list; p < cmd_list + nelem(cmd_list); p++)
   {
 #ifdef SUPPORT_PROXY
   /* Only allow QUIT command if Proxy Protocol parsing failed */
@@ -1675,12 +1708,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
@@ -1729,7 +1763,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;
@@ -1755,7 +1790,7 @@ Returns:    nothing
 */
 
 void
-smtp_closedown(uschar *message)
+smtp_closedown(uschar * message)
 {
 if (!smtp_in || smtp_batched_input) return;
 receive_swallow_smtp();
@@ -2155,8 +2190,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. */
@@ -2433,9 +2472,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 */
@@ -2498,6 +2537,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                 *
 *************************************************/
@@ -2563,7 +2618,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;
@@ -2584,12 +2639,9 @@ 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;
@@ -2598,13 +2650,10 @@ receive_hasc = smtp_hasc;
 receive_ungetc = smtp_ungetc;
 receive_feof = smtp_feof;
 receive_ferror = smtp_ferror;
-receive_smtp_buffered = smtp_buffered;
 lwr_receive_getc = NULL;
 lwr_receive_getbuf = NULL;
 lwr_receive_hasc = NULL;
 lwr_receive_ungetc = NULL;
-smtp_inptr = smtp_inend = smtp_inbuffer;
-smtp_had_eof = smtp_had_error = 0;
 
 /* Set up the message size limit; this may be host-specific */
 
@@ -2663,32 +2712,32 @@ if (!f.sender_host_unknown)
 
 #if !HAVE_IPV6 && !defined(NO_IP_OPTIONS)
 
-  #ifdef GLIBC_IP_OPTIONS
-    #if (!defined __GLIBC__) || (__GLIBC__ < 2)
-    #define OPTSTYLE 1
-    #else
-    #define OPTSTYLE 2
-    #endif
-  #elif defined DARWIN_IP_OPTIONS
-    #define OPTSTYLE 2
-  #else
-    #define OPTSTYLE 3
-  #endif
+ifdef GLIBC_IP_OPTIONS
+#  if (!defined __GLIBC__) || (__GLIBC__ < 2)
+#   define OPTSTYLE 1
+#  else
+#   define OPTSTYLE 2
+#  endif
+elif defined DARWIN_IP_OPTIONS
+define OPTSTYLE 2
+else
+define OPTSTYLE 3
+endif
 
   if (!host_checking && !f.sender_host_notsocket)
     {
-    #if OPTSTYLE == 1
+if OPTSTYLE == 1
     EXIM_SOCKLEN_T optlen = sizeof(struct ip_options) + MAX_IPOPTLEN;
-    struct ip_options *ipopt = store_get(optlen, FALSE);
-    #elif OPTSTYLE == 2
+    struct ip_options *ipopt = store_get(optlen, GET_UNTAINTED);
+elif OPTSTYLE == 2
     struct ip_opts ipoptblock;
     struct ip_opts *ipopt = &ipoptblock;
     EXIM_SOCKLEN_T optlen = sizeof(ipoptblock);
-    #else
+else
     struct ipoption ipoptblock;
     struct ipoption *ipopt = &ipoptblock;
     EXIM_SOCKLEN_T optlen = sizeof(ipoptblock);
-    #endif
+endif
 
     /* Occasional genuine failures of getsockopt() have been seen - for
     example, "reset by peer". Therefore, just log and give up on this
@@ -2718,19 +2767,19 @@ if (!f.sender_host_unknown)
 
     else if (optlen > 0)
       {
-      uschar *p = big_buffer;
-      uschar *pend = big_buffer + big_buffer_size;
-      uschar *adptr;
+      uschar * p = big_buffer;
+      uschar * pend = big_buffer + big_buffer_size;
+      uschar * adptr;
       int optcount;
       struct in_addr addr;
 
-      #if OPTSTYLE == 1
-      uschar *optstart = US (ipopt->__data);
-      #elif OPTSTYLE == 2
-      uschar *optstart = US (ipopt->ip_opts);
-      #else
-      uschar *optstart = US (ipopt->ipopt_list);
-      #endif
+if OPTSTYLE == 1
+      uschar * optstart = US (ipopt->__data);
+elif OPTSTYLE == 2
+      uschar * optstart = US (ipopt->ip_opts);
+else
+      uschar * optstart = US (ipopt->ipopt_list);
+endif
 
       DEBUG(D_receive) debug_printf("IP options exist\n");
 
@@ -2741,59 +2790,65 @@ if (!f.sender_host_unknown)
         switch (*opt)
           {
           case IPOPT_EOL:
-          opt = NULL;
-          break;
+           opt = NULL;
+           break;
 
           case IPOPT_NOP:
-          opt++;
-          break;
+           opt++;
+           break;
 
           case IPOPT_SSRR:
           case IPOPT_LSRR:
-          if (!string_format(p, pend-p, " %s [@%s",
-               (*opt == IPOPT_SSRR)? "SSRR" : "LSRR",
-               #if OPTSTYLE == 1
-               inet_ntoa(*((struct in_addr *)(&(ipopt->faddr))))))
-               #elif OPTSTYLE == 2
-               inet_ntoa(ipopt->ip_dst)))
-               #else
-               inet_ntoa(ipopt->ipopt_dst)))
-               #endif
-            {
-            opt = NULL;
-            break;
-            }
+           if (!
+# if OPTSTYLE == 1
+                string_format(p, pend-p, " %s [@%s",
+                (*opt == IPOPT_SSRR)? "SSRR" : "LSRR",
+                inet_ntoa(*((struct in_addr *)(&(ipopt->faddr)))))
+# elif OPTSTYLE == 2
+                string_format(p, pend-p, " %s [@%s",
+                (*opt == IPOPT_SSRR)? "SSRR" : "LSRR",
+                inet_ntoa(ipopt->ip_dst))
+# else
+                string_format(p, pend-p, " %s [@%s",
+                (*opt == IPOPT_SSRR)? "SSRR" : "LSRR",
+                inet_ntoa(ipopt->ipopt_dst))
+# endif
+             )
+             {
+             opt = NULL;
+             break;
+             }
 
-          p += Ustrlen(p);
-          optcount = (opt[1] - 3) / sizeof(struct in_addr);
-          adptr = opt + 3;
-          while (optcount-- > 0)
-            {
-            memcpy(&addr, adptr, sizeof(addr));
-            if (!string_format(p, pend - p - 1, "%s%s",
-                  (optcount == 0)? ":" : "@", inet_ntoa(addr)))
-              {
-              opt = NULL;
-              break;
-              }
-            p += Ustrlen(p);
-            adptr += sizeof(struct in_addr);
-            }
-          *p++ = ']';
-          opt += opt[1];
-          break;
+           p += Ustrlen(p);
+           optcount = (opt[1] - 3) / sizeof(struct in_addr);
+           adptr = opt + 3;
+           while (optcount-- > 0)
+             {
+             memcpy(&addr, adptr, sizeof(addr));
+             if (!string_format(p, pend - p - 1, "%s%s",
+                   (optcount == 0)? ":" : "@", inet_ntoa(addr)))
+               {
+               opt = NULL;
+               break;
+               }
+             p += Ustrlen(p);
+             adptr += sizeof(struct in_addr);
+             }
+           *p++ = ']';
+           opt += opt[1];
+           break;
 
           default:
-            {
-            if (pend - p < 4 + 3*opt[1]) { opt = NULL; break; }
-            Ustrcat(p, "[ ");
-            p += 2;
-            for (int i = 0; i < opt[1]; i++)
-              p += sprintf(CS p, "%2.2x ", opt[i]);
-            *p++ = ']';
-            }
-          opt += opt[1];
-          break;
+             {
+             if (pend - p < 4 + 3*opt[1]) { opt = NULL; break; }
+             Ustrcat(p, "[ ");
+             p += 2;
+             for (int i = 0; i < opt[1]; i++)
+               p += sprintf(CS p, "%2.2x ", opt[i]);
+             *p++ = ']';
+             }
+           opt += opt[1];
+           break;
           }
 
       *p = 0;
@@ -2856,7 +2911,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;
     }
 
@@ -2982,18 +3040,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;
@@ -3003,11 +3049,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[CL_TLAU].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. */
 
@@ -3015,13 +3078,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;
@@ -3032,6 +3089,17 @@ 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 */
 
@@ -3521,14 +3589,26 @@ if (log_reject_target != 0)
 
 if (!drop) return 0;
 
-log_write(L_smtp_connection, LOG_MAIN, "%s closed by DROP in ACL",
-  smtp_get_connection_info());
+log_close_event(US"by DROP in ACL");
 
 /* Run the not-quit ACL, but without any custom messages. This should not be a
 problem, because we get here only if some other ACL has issued "drop", and
 in that case, *its* custom messages will have been used above. */
 
 smtp_notquit_exit(US"acl-drop", NULL, NULL);
+
+/* An overenthusiastic fail2ban/iptables implimentation has been seen to result
+in the TCP conn staying open, and retrying, despite this process exiting. A
+malicious client could possibly do the same, tying up server netowrking
+resources. Close the socket explicitly to try to avoid that (there's a note in
+the Linux socket(7) manpage, SO_LINGER para, to the effect that exim() without
+close() results in the socket always lingering). */
+
+(void) poll_one_fd(fileno(smtp_in), POLLIN, 200);
+DEBUG(D_any) debug_printf_indent("SMTP(close)>>\n");
+(void) fclose(smtp_in);
+(void) fclose(smtp_out);
+
 return 2;
 }
 
@@ -3769,11 +3849,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>
@@ -3794,6 +3880,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
@@ -3826,7 +3913,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;
       }
@@ -3839,34 +3926,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;
   }
@@ -3928,30 +4015,20 @@ else
 tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
 # endif
 
-log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
-  smtp_get_connection_info());
+log_close_event(US"by QUIT");
 #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());
+log_close_event(US"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);
-  }
-#endif /*!DAEMON_CLOSE_NOWAIT*/
+(void) poll_one_fd(fileno(smtp_in), POLLIN, 200);
+#endif /*!SERVERSIDE_CLOSE_NOWAIT*/
 }
 
 
@@ -3961,7 +4038,9 @@ smtp_rset_handler(void)
 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;
+cmd_list[CL_RSET].is_mail_cmd = FALSE;
+if (chunking_state > CHUNKING_OFFERED)
+  chunking_state = CHUNKING_OFFERED;
 }
 
 
@@ -4021,11 +4100,11 @@ message_ended = END_NOTSTARTED;
 
 chunking_state = f.chunking_offered ? CHUNKING_OFFERED : CHUNKING_NOT_OFFERED;
 
-cmd_list[CMD_LIST_RSET].is_mail_cmd = TRUE;
-cmd_list[CMD_LIST_HELO].is_mail_cmd = TRUE;
-cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
+cmd_list[CL_RSET].is_mail_cmd = TRUE;
+cmd_list[CL_HELO].is_mail_cmd = TRUE;
+cmd_list[CL_EHLO].is_mail_cmd = TRUE;
 #ifndef DISABLE_TLS
-cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE;
+cmd_list[CL_STLS].is_mail_cmd = TRUE;
 #endif
 
 if (lwr_receive_getc != NULL)
@@ -4081,10 +4160,10 @@ while (done <= 0)
   if (  tls_in.active.sock >= 0
      && tls_in.peercert
      && tls_in.certificate_verified
-     && cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd
+     && cmd_list[CL_TLAU].is_mail_cmd
      )
     {
-    cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = FALSE;
+    cmd_list[CL_TLAU].is_mail_cmd = FALSE;
 
     for (auth_instance * au = auths; au; au = au->next)
       if (strcmpic(US"tls", au->driver_name) == 0)
@@ -4101,7 +4180,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;
        }
@@ -4133,7 +4223,7 @@ while (done <= 0)
     case AUTH_CMD:
       HAD(SCH_AUTH);
       authentication_failed = TRUE;
-      cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE;
+      cmd_list[CL_AUTH].is_mail_cmd = FALSE;
 
       if (!fl.auth_advertised && !f.allow_auth_unadvertised)
        {
@@ -4191,6 +4281,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))
@@ -4198,12 +4290,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,
@@ -4240,8 +4345,8 @@ while (done <= 0)
       fl.esmtp = TRUE;
 
     HELO_EHLO:      /* Common code for HELO and EHLO */
-      cmd_list[CMD_LIST_HELO].is_mail_cmd = FALSE;
-      cmd_list[CMD_LIST_EHLO].is_mail_cmd = FALSE;
+      cmd_list[CL_HELO].is_mail_cmd = FALSE;
+      cmd_list[CL_EHLO].is_mail_cmd = FALSE;
 
       /* Reject the HELO if its argument was invalid or non-existent. A
       successful check causes the argument to be saved in malloc store. */
@@ -4374,7 +4479,7 @@ while (done <= 0)
       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,
@@ -4392,7 +4497,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);
@@ -4412,7 +4517,7 @@ while (done <= 0)
 
       if (fl.esmtp)
        {
-       g->s[3] = '-';
+       g->s[3] = '-';  /* overwrite the space after the SMTP response code */
 
        /* I'm not entirely happy with this, as an MTA is supposed to check
        that it has enough room to accept a message of maximum size before
@@ -4543,9 +4648,9 @@ while (done <= 0)
                  first = FALSE;
                  fl.auth_advertised = TRUE;
                  }
-               saveptr = g->ptr;
+               saveptr = gstring_length(g);
                g = string_catn(g, US" ", 1);
-               g = string_cat (g, au->public_name);
+               g = string_cat(g, au->public_name);
                while (++saveptr < g->ptr) g->s[saveptr] = toupper(g->s[saveptr]);
                au->advertised = TRUE;
                }
@@ -4608,28 +4713,29 @@ while (done <= 0)
       /* Terminate the string (for debug), write it, and note that HELO/EHLO
       has been seen. */
 
+       {
+       uschar * ehlo_resp;
+       int len = len_string_from_gstring(g, &ehlo_resp);
 #ifndef DISABLE_TLS
-      if (tls_in.active.sock >= 0)
-       (void)tls_write(NULL, g->s, g->ptr,
+       if (tls_in.active.sock >= 0)
+         (void) tls_write(NULL, ehlo_resp, len,
 # ifndef DISABLE_PIPE_CONNECT
-                       fl.pipe_connect_acceptable && pipeline_connect_sends());
+                         fl.pipe_connect_acceptable && pipeline_connect_sends());
 # else
-                       FALSE);
+                         FALSE);
 # endif
-      else
+       else
 #endif
-       (void) fwrite(g->s, 1, g->ptr, smtp_out);
+         (void) fwrite(ehlo_resp, 1, len, 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);
-       }
-      fl.helo_seen = TRUE;
+       DEBUG(D_receive) for (const uschar * t, * s = ehlo_resp;
+                             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. */
       received_protocol =
@@ -5085,7 +5191,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)
          {
@@ -5104,7 +5210,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");
@@ -5395,7 +5501,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);
@@ -5445,7 +5551,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
          {
@@ -5588,7 +5694,7 @@ while (done <= 0)
       cancel_cutthrough_connection(TRUE, US"STARTTLS received");
       reset_point = smtp_reset(reset_point);
       toomany = FALSE;
-      cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE;
+      cmd_list[CL_STLS].is_mail_cmd = FALSE;
 
       /* There's an attack where more data is read in past the STARTTLS command
       before TLS is negotiated, then assumed to be part of the secure session
@@ -5598,7 +5704,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");
@@ -5629,9 +5735,9 @@ while (done <= 0)
        {
        if (!tls_remember_esmtp)
          fl.helo_seen = fl.esmtp = fl.auth_advertised = f.smtp_in_pipelining_advertised = FALSE;
-       cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
-       cmd_list[CMD_LIST_AUTH].is_mail_cmd = TRUE;
-       cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
+       cmd_list[CL_EHLO].is_mail_cmd = TRUE;
+       cmd_list[CL_AUTH].is_mail_cmd = TRUE;
+       cmd_list[CL_TLAU].is_mail_cmd = TRUE;
        if (sender_helo_name)
          {
          sender_helo_name = NULL;
@@ -5675,8 +5781,7 @@ while (done <= 0)
       while (done <= 0) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
        {
        case EOF_CMD:
-         log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF",
-           smtp_get_connection_info());
+         log_close_event(US"by EOF");
          smtp_notquit_exit(US"tls-failed", NULL, NULL);
          done = 2;
          break;
@@ -5698,8 +5803,7 @@ while (done <= 0)
            smtp_respond(US"221", 3, TRUE, user_msg);
          else
            smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
-         log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
-           smtp_get_connection_info());
+         log_close_event(US"by QUIT");
          done = 2;
          break;
 
@@ -5743,23 +5847,19 @@ while (done <= 0)
 
     case HELP_CMD:
       HAD(SCH_HELP);
-      smtp_printf("214-Commands supported:\r\n", TRUE);
-       {
-       uschar buffer[256];
-       buffer[0] = 0;
-       Ustrcat(buffer, US" AUTH");
-       #ifndef DISABLE_TLS
-       if (tls_in.active.sock < 0 &&
-           verify_check_host(&tls_advertise_hosts) != FAIL)
-         Ustrcat(buffer, US" STARTTLS");
-       #endif
-       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);
-       }
+      smtp_printf("214-Commands supported:\r\n214", TRUE);
+      smtp_printf(" AUTH", TRUE);
+#ifndef DISABLE_TLS
+      if (tls_in.active.sock < 0 &&
+         verify_check_host(&tls_advertise_hosts) != FAIL)
+       smtp_printf(" STARTTLS", TRUE);
+#endif
+      smtp_printf(" HELO EHLO MAIL RCPT DATA BDAT", TRUE);
+      smtp_printf(" NOOP QUIT RSET HELP", TRUE);
+      if (acl_smtp_etrn) smtp_printf(" ETRN", TRUE);
+      if (acl_smtp_expn) smtp_printf(" EXPN", TRUE);
+      if (acl_smtp_vrfy) smtp_printf(" VRFY", TRUE);
+      smtp_printf("\r\n", FALSE);
       break;
 
 
@@ -5832,7 +5932,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)
          {
@@ -5909,6 +6009,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));
@@ -6037,7 +6138,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 */