select() -> poll(). Bug 2831
[exim.git] / src / src / smtp_in.c
index 69eae3cb4bd1016307f9fa577d5f469a0e2bebcf..7cb966f24afa5a8bbecd567af28d29f1d4264afe 100644 (file)
@@ -3,7 +3,7 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 */
+/* Copyright (c) The Exim Maintainers 2020 - 2021 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
@@ -139,7 +139,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 +153,7 @@ static struct {
   BOOL smtputf8_advertised             :1;
 #endif
 } fl = {
-  .helo_required = FALSE,
+  .helo_verify_required = FALSE,
   .helo_verify = FALSE,
   .smtp_exit_function_called = FALSE,
 };
@@ -346,8 +346,6 @@ static BOOL
 wouldblock_reading(void)
 {
 int fd, rc;
-fd_set fds;
-struct timeval tzero;
 
 #ifndef DISABLE_TLS
 if (tls_in.active.sock >= 0)
@@ -358,11 +356,7 @@ if (smtp_inptr < smtp_inend)
   return FALSE;
 
 fd = fileno(smtp_in);
-FD_ZERO(&fds);
-FD_SET(fd, &fds);
-tzero.tv_sec = 0;
-tzero.tv_usec = 0;
-rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero);
+rc = poll_one_fd(fd, POLLIN, 0);
 
 if (rc <= 0) return TRUE;     /* Not ready to read */
 rc = smtp_getc(GETC_BUFFER_UNLIMITED);
@@ -565,6 +559,12 @@ if (smtp_inptr >= smtp_inend)
 return *smtp_inptr++;
 }
 
+BOOL
+smtp_hasc(void)
+{
+return smtp_inptr < smtp_inend;
+}
+
 uschar *
 smtp_getbuf(unsigned * len)
 {
@@ -583,16 +583,23 @@ return buf;
 }
 
 void
-smtp_get_cache(void)
+smtp_get_cache(unsigned lim)
 {
 #ifndef DISABLE_DKIM
 int n = smtp_inend - smtp_inptr;
+if (n > lim)
+  n = lim;
 if (n > 0)
   dkim_exim_verify_feed(smtp_inptr, n);
 #endif
 }
 
 
+/* 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 +631,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 +663,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 +737,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 +747,14 @@ next_cmd:
   }
 }
 
+BOOL
+bdat_hasc(void)
+{
+if (chunking_data_left > 0)
+  return lwr_receive_hasc();
+return TRUE;
+}
+
 uschar *
 bdat_getbuf(unsigned * len)
 {
@@ -765,9 +778,7 @@ while (chunking_data_left)
   if (!bdat_getbuf(&n)) break;
   }
 
-receive_getc = lwr_receive_getc;
-receive_getbuf = lwr_receive_getbuf;
-receive_ungetc = lwr_receive_ungetc;
+bdat_pop_receive_functions();
 
 if (chunking_state != CHUNKING_LAST)
   {
@@ -777,7 +788,48 @@ if (chunking_state != CHUNKING_LAST)
 }
 
 
+static inline void
+bdat_push_receive_functions(void)
+{
+/* push the current receive_* function on the "stack", and
+replace them by bdat_getc(), which in turn will use the lwr_receive_*
+functions to do the dirty work. */
+if (!lwr_receive_getc)
+  {
+  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;
+}
+
+static inline void
+bdat_pop_receive_functions(void)
+{
+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;
+}
 
 /*************************************************
 *          SMTP version of ungetc()              *
@@ -795,6 +847,9 @@ 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;
 }
@@ -804,6 +859,7 @@ 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);
 }
 
@@ -1213,13 +1269,11 @@ int size, ret;
 int fd = fileno(smtp_in);
 const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
 uschar * iptype;  /* To display debug info */
-struct timeval tv;
-struct timeval tvtmp;
 socklen_t vslen = sizeof(struct timeval);
 BOOL yield = FALSE;
 
 os_non_restarting_signal(SIGALRM, command_timeout_handler);
-ALARM(PROXY_NEGOTIATION_TIMEOUT_SEC);
+ALARM(proxy_protocol_timeout);
 
 do
   {
@@ -1969,29 +2023,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;
@@ -2111,7 +2171,11 @@ while (acl_warn_logged)
   acl_warn_logged = acl_warn_logged->next;
   store_free(this);
   }
+
+message_tidyup();
 store_reset(reset_point);
+
+message_start();
 return store_mark();
 }
 
@@ -2188,7 +2252,7 @@ while (done <= 0)
 
     case MAIL_CMD:
       smtp_mailcmd_count++;              /* Count for no-mail log */
-      if (sender_address != NULL)
+      if (sender_address)
        /* The function moan_smtp_batch() does not return. */
        moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given");
 
@@ -2203,9 +2267,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 */
 
@@ -2225,7 +2291,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);
          }
@@ -2264,7 +2331,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;
 
@@ -2283,7 +2351,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
@@ -2521,10 +2590,15 @@ smtp_inbuffer[IN_BUFFER_SIZE-1] = '\0';
 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;
+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;
 
@@ -2880,8 +2954,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
@@ -2895,7 +2969,7 @@ if (!f.sender_host_unknown)
 if (smtp_batched_input) return TRUE;
 
 /* If valid Proxy Protocol source is connecting, set up session.
- * Failure will not allow any SMTP function other than QUIT. */
+Failure will not allow any SMTP function other than QUIT. */
 
 #ifdef SUPPORT_PROXY
 proxy_session = FALSE;
@@ -2904,16 +2978,16 @@ if (check_proxy_protocol_host())
   setup_proxy_protocol_host();
 #endif
 
-  /* Start up TLS if tls_on_connect is set. This is for supporting the legacy
-  smtps port for use with older style SSL MTAs. */
+/* Start up TLS if tls_on_connect is set. This is for supporting the legacy
+smtps port for use with older style SSL MTAs. */
 
 #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;
-    }
+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 */
@@ -2959,7 +3033,7 @@ else
 
 p = s + Ustrlen(s);
 while (p > s && isspace(p[-1])) p--;
-*p = 0;
+s = string_copyn(s, p-s);
 
 /* It seems that CC:Mail is braindead, and assumes that the greeting message
 is all contained in a single IP packet. The original code wrote out the
@@ -3139,7 +3213,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)
@@ -3154,7 +3228,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;
@@ -3210,27 +3284,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;
 }
 
@@ -3281,18 +3354,7 @@ int codelen = 3;
 uschar *smtp_code;
 uschar *lognl;
 uschar *sender_info = US"";
-uschar *what =
-#ifdef WITH_CONTENT_SCAN
-  where == ACL_WHERE_MIME ? US"during MIME ACL checks" :
-#endif
-  where == ACL_WHERE_PREDATA ? US"DATA" :
-  where == ACL_WHERE_DATA ? US"after DATA" :
-#ifndef DISABLE_PRDR
-  where == ACL_WHERE_PRDR ? US"after DATA PRDR" :
-#endif
-  smtp_cmd_data ?
-    string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data) :
-    string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]);
+uschar *what;
 
 if (drop) rc = FAIL;
 
@@ -3308,19 +3370,45 @@ fixed, sender_address at this point became the rewritten address. I'm not sure
 this is what should be logged, so I've changed to logging the unrewritten
 address to retain backward compatibility. */
 
-#ifndef WITH_CONTENT_SCAN
-if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA)
-#else
-if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA || where == ACL_WHERE_MIME)
+switch (where)
+  {
+#ifdef WITH_CONTENT_SCAN
+  case ACL_WHERE_MIME:         what = US"during MIME ACL checks";      break;
+#endif
+  case ACL_WHERE_PREDATA:      what = US"DATA";                        break;
+  case ACL_WHERE_DATA:         what = US"after DATA";                  break;
+#ifndef DISABLE_PRDR
+  case ACL_WHERE_PRDR:         what = US"after DATA PRDR";             break;
 #endif
+  default:
+    {
+    uschar * place = smtp_cmd_data ? smtp_cmd_data : US"in \"connect\" ACL";
+    int lim = 100;
+
+    if (where == ACL_WHERE_AUTH)       /* avoid logging auth creds */
+      {
+      uschar * s;
+      for (s = smtp_cmd_data; *s && !isspace(*s); ) s++;
+      lim = s - smtp_cmd_data; /* atop after method */
+      }
+    what = string_sprintf("%s %.*s", acl_wherenames[where], lim, place);
+    }
+  }
+switch (where)
   {
-  sender_info = string_sprintf("F=<%s>%s%s%s%s ",
-    sender_address_unrewritten ? sender_address_unrewritten : sender_address,
-    sender_host_authenticated ? US" A="                                    : US"",
-    sender_host_authenticated ? sender_host_authenticated                  : US"",
-    sender_host_authenticated && authenticated_id ? US":"                  : US"",
-    sender_host_authenticated && authenticated_id ? authenticated_id       : US""
-    );
+  case ACL_WHERE_RCPT:
+  case ACL_WHERE_DATA:
+#ifdef WITH_CONTENT_SCAN
+  case ACL_WHERE_MIME:
+#endif
+    sender_info = string_sprintf("F=<%s>%s%s%s%s ",
+      sender_address_unrewritten ? sender_address_unrewritten : sender_address,
+      sender_host_authenticated ? US" A="                                    : US"",
+      sender_host_authenticated ? sender_host_authenticated                  : US"",
+      sender_host_authenticated && authenticated_id ? US":"                  : US"",
+      sender_host_authenticated && authenticated_id ? authenticated_id       : US""
+      );
+  break;
   }
 
 /* If there's been a sender verification failure with a specific message, and
@@ -3795,7 +3883,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,
@@ -3821,8 +3910,8 @@ if (  acl_smtp_quit
     log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
       *log_msgp);
 
-#ifdef TCP_CORK
-(void) setsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_CORK, US &on, sizeof(on));
+#ifdef EXIM_TCP_CORK
+(void) setsockopt(fileno(smtp_out), IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
 #endif
 
 if (*user_msgp)
@@ -3830,12 +3919,27 @@ 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
+# endif
 
 log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
   smtp_get_connection_info());
+#else
+
+# ifndef DISABLE_TLS
+tls_close(NULL, TLS_SHUTDOWN_WAIT);
+# endif
+
+log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
+  smtp_get_connection_info());
+
+/* Pause, hoping client will FIN first so that they get the TIME_WAIT.
+The socket should become readble (though with no data) */
+
+(void) poll_one_fd(fileno(smtp_in), POLLIN, 200);
+#endif /*!SERVERSIDE_CLOSE_NOWAIT*/
 }
 
 
@@ -3849,6 +3953,13 @@ cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
 }
 
 
+static int
+expand_mailmax(const uschar * s)
+{
+if (!(s = expand_cstring(s)))
+  log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand smtp_accept_max_per_connection");
+return *s ? Uatoi(s) : 0;
+}
 
 /*************************************************
 *       Initialize for SMTP incoming message     *
@@ -3905,6 +4016,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;
@@ -3914,6 +4033,12 @@ os_non_restarting_signal(SIGTERM, command_sigterm_handler);
 
 if (smtp_batched_input) return smtp_setup_batch_msg();
 
+#ifdef TCP_QUICKACK
+if (smtp_in)           /* Avoid pure-ACKs while in cmd pingpong phase */
+  (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
+         US &off, sizeof(off));
+#endif
+
 /* Deal with SMTP commands. This loop is exited by setting done to a POSITIVE
 value. The values are 2 larger than the required yield of the function. */
 
@@ -3971,12 +4096,6 @@ while (done <= 0)
     }
 #endif
 
-#ifdef TCP_QUICKACK
-  if (smtp_in)         /* Avoid pure-ACKs while in cmd pingpong phase */
-    (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
-           US &off, sizeof(off));
-#endif
-
   switch(smtp_read_command(
 #ifndef DISABLE_PIPE_CONNECT
          !fl.pipe_connect_acceptable,
@@ -4037,21 +4156,18 @@ while (done <= 0)
       /* Find the name of the requested authentication mechanism. */
 
       s = smtp_cmd_data;
-      while ((c = *smtp_cmd_data) != 0 && !isspace(c))
-       {
+      for (; (c = *smtp_cmd_data) && !isspace(c); smtp_cmd_data++)
        if (!isalnum(c) && c != '-' && c != '_')
          {
          done = synprot_error(L_smtp_syntax_error, 501, NULL,
            US"invalid character in authentication mechanism name");
          goto COMMAND_LOOP;
          }
-       smtp_cmd_data++;
-       }
 
       /* If not at the end of the line, we must be at white space. Terminate the
       name and move the pointer on to any data that may be present. */
 
-      if (*smtp_cmd_data != 0)
+      if (*smtp_cmd_data)
        {
        *smtp_cmd_data++ = 0;
        while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
@@ -4143,7 +4259,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. */
 
@@ -4171,19 +4287,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);
@@ -4239,6 +4355,9 @@ while (done <= 0)
       fl.smtputf8_advertised = FALSE;
 #endif
 
+      /* Expand the per-connection message count limit option */
+      smtp_mailcmd_max = expand_mailmax(smtp_accept_max_per_connection);
+
       smtp_code = US"250 ";        /* Default response code plus space*/
       if (!user_msg)
        {
@@ -4298,6 +4417,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.
@@ -4510,16 +4642,21 @@ while (done <= 0)
     case MAIL_CMD:
       HAD(SCH_MAIL);
       smtp_mailcmd_count++;              /* Count for limit and ratelimit */
+      message_start();
       was_rej_mail = TRUE;               /* Reset if accepted */
       env_mail_type_t * mail_args;       /* Sanity check & validate args */
 
-      if (fl.helo_required && !fl.helo_seen)
-       {
-       smtp_printf("503 HELO or EHLO required\r\n", FALSE);
-       log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
-         "HELO/EHLO given", host_and_ident(FALSE));
-       break;
-       }
+      if (!fl.helo_seen)
+       if (  fl.helo_verify_required
+          || verify_check_host(&hosts_require_helo) == OK)
+         {
+         smtp_printf("503 HELO or EHLO required\r\n", FALSE);
+         log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
+           "HELO/EHLO given", host_and_ident(FALSE));
+         break;
+         }
+       else if (smtp_mailcmd_max < 0)
+         smtp_mailcmd_max = expand_mailmax(smtp_accept_max_per_connection);
 
       if (sender_address)
        {
@@ -4538,8 +4675,7 @@ while (done <= 0)
       /* Check to see if the limit for messages per connection would be
       exceeded by accepting further messages. */
 
-      if (smtp_accept_max_per_connection > 0 &&
-         smtp_mailcmd_count > smtp_accept_max_per_connection)
+      if (smtp_mailcmd_max > 0 && smtp_mailcmd_count > smtp_mailcmd_max)
        {
        smtp_printf("421 too many messages in this connection\r\n", FALSE);
        log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many "
@@ -4791,7 +4927,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;
 
@@ -4853,7 +4990,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);
          }
@@ -4923,6 +5061,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;
 
@@ -5045,7 +5187,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;
 
@@ -5078,7 +5221,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)
          {
@@ -5223,16 +5366,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;
@@ -5241,6 +5375,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
@@ -5269,7 +5404,10 @@ while (done <= 0)
            : US"valid RCPT command must precede BDAT");
 
        if (chunking_state > CHUNKING_OFFERED)
+         {
+         bdat_push_receive_functions();
          bdat_flush_data();
+         }
        break;
        }
 
@@ -5278,6 +5416,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;
        }
 
@@ -5315,6 +5459,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,
@@ -5894,12 +6041,14 @@ if (!sender_host_authenticated)
 
 g = string_append(g, 2, US";\n\tauth=pass (", sender_host_auth_pubname);
 
-if (Ustrcmp(sender_host_auth_pubname, "tls") != 0)
-  g = string_append(g, 2, US") smtp.auth=", authenticated_id);
-else if (authenticated_id)
-  g = string_append(g, 2, US") x509.auth=", authenticated_id);
+if (Ustrcmp(sender_host_auth_pubname, "tls") == 0)
+  g = authenticated_id
+    ? string_append(g, 2, US") x509.auth=", authenticated_id)
+    : string_cat(g, US") reason=x509.auth");
 else
-  g = string_catn(g, US") reason=x509.auth", 17);
+  g = authenticated_id
+    ? string_append(g, 2, US") smtp.auth=", authenticated_id)
+    : string_cat(g, US", no id saved)");
 
 if (authenticated_sender)
   g = string_append(g, 2, US" smtp.mailfrom=", authenticated_sender);