CVE-2020-28024: Heap buffer underflow in smtp_ungetc()
[exim.git] / src / src / smtp_in.c
index f37b2f6e0f1b5f7a9a8509593925040421c2de31..258ec03e4d03e78890e8a5aa2cd85a5e2adf31f7 100644 (file)
@@ -3,6 +3,7 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
@@ -142,7 +143,7 @@ static struct {
   BOOL helo_verify                     :1;
   BOOL helo_seen                       :1;
   BOOL helo_accept_junk                        :1;
-#ifdef SUPPORT_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
   BOOL pipe_connect_acceptable         :1;
 #endif
   BOOL rcpt_smtp_response_same         :1;
@@ -397,7 +398,7 @@ return TRUE;
 }
 
 
-#ifdef SUPPORT_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
 static BOOL
 pipeline_connect_sends(void)
 {
@@ -457,7 +458,7 @@ if (smtp_batched_input)
 smtp_notquit_exit(US"command-timeout", US"421",
   US"%s: SMTP command timeout - closing connection",
   smtp_active_hostname);
-exim_exit(EXIT_FAILURE, US"receiving");
+exim_exit(EXIT_FAILURE);
 }
 
 void
@@ -468,7 +469,7 @@ if (smtp_batched_input)
   moan_smtp_batch(NULL, "421 SIGTERM received");  /* Does not return */
 smtp_notquit_exit(US"signal-exit", US"421",
   US"%s: Service not available - closing connection", smtp_active_hostname);
-exim_exit(EXIT_FAILURE, US"receiving");
+exim_exit(EXIT_FAILURE);
 }
 
 void
@@ -592,6 +593,11 @@ if (n > 0)
 }
 
 
+/* 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
@@ -623,9 +629,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;
@@ -729,9 +733,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
@@ -764,9 +766,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)
   {
@@ -776,7 +776,44 @@ if (chunking_state != CHUNKING_LAST)
 }
 
 
+static inline void
+bdat_push_receive_functions(void)
+{
+/* push the current receive_* function on the "stack", and
+replace them by bdat_getc(), which in turn will use the lwr_receive_*
+functions to do the dirty work. */
+if (lwr_receive_getc == NULL)
+  {
+  lwr_receive_getc = receive_getc;
+  lwr_receive_getbuf = receive_getbuf;
+  lwr_receive_ungetc = receive_ungetc;
+  }
+else
+  {
+  DEBUG(D_receive) debug_printf("chunking double-push receive functions\n");
+  }
+
+receive_getc = bdat_getc;
+receive_getbuf = bdat_getbuf;
+receive_ungetc = bdat_ungetc;
+}
+
+static inline void
+bdat_pop_receive_functions(void)
+{
+if (lwr_receive_getc == NULL)
+  {
+  DEBUG(D_receive) debug_printf("chunking double-pop receive functions\n");
+  return;
+  }
+receive_getc = lwr_receive_getc;
+receive_getbuf = lwr_receive_getbuf;
+receive_ungetc = lwr_receive_ungetc;
 
+lwr_receive_getc = NULL;
+lwr_receive_getbuf = NULL;
+lwr_receive_ungetc = NULL;
+}
 
 /*************************************************
 *          SMTP version of ungetc()              *
@@ -794,6 +831,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;
 }
@@ -931,7 +971,7 @@ if (!yield)
   {
   log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()");
   smtp_closedown(US"Unexpected error");
-  exim_exit(EXIT_FAILURE, NULL);
+  exim_exit(EXIT_FAILURE);
   }
 
 /* If this is the first output for a (non-batch) RCPT command, see if all RCPTs
@@ -952,16 +992,13 @@ if (fl.rcpt_in_progress)
 
 /* Now write the string */
 
+if (
 #ifndef DISABLE_TLS
-if (tls_in.active.sock >= 0)
-  {
-  if (tls_write(NULL, gs.s, gs.ptr, more) < 0)
-    smtp_write_error = -1;
-  }
-else
+    tls_in.active.sock >= 0 ? (tls_write(NULL, gs.s, gs.ptr, more) < 0) :
 #endif
-
-if (fprintf(smtp_out, "%s", gs.s) < 0) smtp_write_error = -1;
+    (fwrite(gs.s, gs.ptr, 1, smtp_out) == 0)
+   )
+    smtp_write_error = -1;
 }
 
 
@@ -972,8 +1009,7 @@ if (fprintf(smtp_out, "%s", gs.s) < 0) smtp_write_error = -1;
 
 /* This function isn't currently used within Exim (it detects errors when it
 tries to read the next SMTP input), but is available for use in local_scan().
-For non-TLS connections, it flushes the output and checks for errors. For
-TLS-connections, it checks for a previously-detected TLS write error.
+It flushes the output and checks for errors.
 
 Arguments:  none
 Returns:    0 for no error; -1 after an error
@@ -983,6 +1019,15 @@ int
 smtp_fflush(void)
 {
 if (tls_in.active.sock < 0 && fflush(smtp_out) != 0) smtp_write_error = -1;
+
+if (
+#ifndef DISABLE_TLS
+    tls_in.active.sock >= 0 ? (tls_write(NULL, NULL, 0, FALSE) < 0) :
+#endif
+    (fflush(smtp_out) != 0)
+   )
+    smtp_write_error = -1;
+
 return smtp_write_error;
 }
 
@@ -1805,11 +1850,34 @@ if (LOGGING(tls_certificate_verified) && tls_in.cipher)
 if (LOGGING(tls_peerdn) && tls_in.peerdn)
   g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\"");
 if (LOGGING(tls_sni) && tls_in.sni)
-  g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\"");
+  g = string_append(g, 2, US" SNI=", string_printing2(tls_in.sni, SP_TAB|SP_SPACE));
 return g;
 }
 #endif
 
+
+
+static gstring *
+s_connhad_log(gstring * g)
+{
+uschar * sep = smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE
+  ? US" C=..." : US" C=";
+
+for (int i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
+  if (smtp_connection_had[i] != SCH_NONE)
+    {
+    g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
+    sep = US",";
+    }
+for (int i = 0; i < smtp_ch_index; i++)
+  {
+  g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
+  sep = US",";
+  }
+return g;
+}
+
+
 /*************************************************
 *      Log lack of MAIL if so configured         *
 *************************************************/
@@ -1825,7 +1893,7 @@ Returns:     nothing
 void
 smtp_log_no_mail(void)
 {
-uschar * sep, * s;
+uschar * s;
 gstring * g = NULL;
 
 if (smtp_mailcmd_count > 0 || !LOGGING(smtp_no_mail))
@@ -1841,20 +1909,7 @@ if (sender_host_authenticated)
 g = s_tlslog(g);
 #endif
 
-sep = smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE ?  US" C=..." : US" C=";
-
-for (int i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
-  if (smtp_connection_had[i] != SCH_NONE)
-    {
-    g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
-    sep = US",";
-    }
-
-for (int i = 0; i < smtp_ch_index; i++)
-  {
-  g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
-  sep = US",";
-  }
+g = s_connhad_log(g);
 
 if (!(s = string_from_gstring(g))) s = US"";
 
@@ -1979,29 +2034,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;
@@ -2050,7 +2111,8 @@ f.active_local_from_check = local_from_check;          /* Can be set by ACL */
 f.active_local_sender_retain = local_sender_retain;    /* Can be set by ACL */
 sending_ip_address = NULL;
 return_path = sender_address = NULL;
-sender_data = NULL;                                 /* Can be set by ACL */
+deliver_localpart_data = deliver_domain_data =
+recipient_data = sender_data = NULL;                   /* Can be set by ACL */
 deliver_localpart_parent = deliver_localpart_orig = NULL;
 deliver_domain_parent = deliver_domain_orig = NULL;
 callout_address = NULL;
@@ -2087,6 +2149,7 @@ dmarc_used_domain = NULL;
 #endif
 #ifdef EXPERIMENTAL_ARC
 arc_state = arc_state_reason = NULL;
+arc_received_instance = 0;
 #endif
 dsn_ret = 0;
 dsn_envid = NULL;
@@ -2225,9 +2288,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 */
 
@@ -2247,7 +2312,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);
          }
@@ -2286,7 +2352,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;
 
@@ -2305,7 +2372,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
@@ -2400,24 +2468,47 @@ return FALSE;
 static void
 tfo_in_check(void)
 {
-# ifdef TCP_INFO
+# ifdef __FreeBSD__
+int is_fastopen;
+socklen_t len = sizeof(is_fastopen);
+
+/* The tinfo TCPOPT_FAST_OPEN bit seems unreliable, and we don't see state
+TCP_SYN_RCV (as of 12.1) so no idea about data-use. */
+
+if (getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_FASTOPEN, &is_fastopen, &len) == 0)
+  {
+  if (is_fastopen) 
+    {
+    DEBUG(D_receive)
+      debug_printf("TFO mode connection (TCP_FASTOPEN getsockopt)\n");
+    f.tcp_in_fastopen = TRUE;
+    }
+  }
+else DEBUG(D_receive)
+  debug_printf("TCP_INFO getsockopt: %s\n", strerror(errno));
+
+# elif defined(TCP_INFO)
 struct tcp_info tinfo;
 socklen_t len = sizeof(tinfo);
 
 if (getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0)
-#ifdef TCPI_OPT_SYN_DATA       /* FreeBSD 11 does not seem to have this yet */
+#  ifdef TCPI_OPT_SYN_DATA     /* FreeBSD 11,12 do not seem to have this yet */
   if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA)
     {
-    DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (ACKd data-on-SYN)\n");
+    DEBUG(D_receive)
+      debug_printf("TFO mode connection (ACKd data-on-SYN)\n");
     f.tcp_in_fastopen_data = f.tcp_in_fastopen = TRUE;
     }
   else
-#endif
-    if (tinfo.tcpi_state == TCP_SYN_RECV)
+#  endif
+    if (tinfo.tcpi_state == TCP_SYN_RECV)      /* Not seen on FreeBSD 12.1 */
     {
-    DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (state TCP_SYN_RECV)\n");
+    DEBUG(D_receive)
+      debug_printf("TFO mode connection (state TCP_SYN_RECV)\n");
     f.tcp_in_fastopen = TRUE;
     }
+else DEBUG(D_receive)
+  debug_printf("TCP_INFO getsockopt: %s\n", strerror(errno));
 # endif
 }
 #endif
@@ -2523,6 +2614,9 @@ 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_ungetc = NULL;
 smtp_inptr = smtp_inend = smtp_inbuffer;
 smtp_had_eof = smtp_had_error = 0;
 
@@ -2997,7 +3091,7 @@ while (*p);
 /* Before we write the banner, check that there is no input pending, unless
 this synchronisation check is disabled. */
 
-#ifdef SUPPORT_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
 fl.pipe_connect_acceptable =
   sender_host_address && verify_check_host(&pipe_connect_advertise_hosts) == OK;
 
@@ -3010,7 +3104,7 @@ if (!check_sync())
 #endif
     {
     unsigned n = smtp_inend - smtp_inptr;
-    if (n > 32) n = 32;
+    if (n > 128) n = 128;
 
     log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
       "synchronization error (input sent without waiting for greeting): "
@@ -3024,7 +3118,7 @@ if (!check_sync())
 /*XXX the ehlo-resp code does its own tls/nontls bit.  Maybe subroutine that? */
 
 smtp_printf("%s",
-#ifdef SUPPORT_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
   fl.pipe_connect_acceptable && pipeline_connect_sends(),
 #else
   FALSE,
@@ -3035,7 +3129,7 @@ smtp_printf("%s",
 handshake arrived.  If so we must have managed a TFO. */
 
 #ifdef TCP_FASTOPEN
-tfo_in_check();
+if (sender_host_address && !f.sender_host_notsocket) tfo_in_check();
 #endif
 
 return TRUE;
@@ -3071,15 +3165,17 @@ synprot_error(int type, int code, uschar *data, uschar *errmess)
 int yield = -1;
 
 log_write(type, LOG_MAIN, "SMTP %s error in \"%s\" %s %s",
-  (type == L_smtp_syntax_error)? "syntax" : "protocol",
+  type == L_smtp_syntax_error ? "syntax" : "protocol",
   string_printing(smtp_cmd_buffer), host_and_ident(TRUE), errmess);
 
 if (++synprot_error_count > smtp_max_synprot_errors)
   {
   yield = 1;
   log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
-    "syntax or protocol errors (last command was \"%s\")",
-    host_and_ident(FALSE), string_printing(smtp_cmd_buffer));
+    "syntax or protocol errors (last command was \"%s\", %s)",
+    host_and_ident(FALSE), string_printing(smtp_cmd_buffer),
+    string_from_gstring(s_connhad_log(NULL))
+    );
   }
 
 if (code > 0)
@@ -3165,7 +3261,7 @@ for (;;)
     {
     smtp_printf("%.3s-%.*s%.*s\r\n", TRUE, code, esclen, esc, (int)(nl - msg), msg);
     msg = nl + 1;
-    while (isspace(*msg)) msg++;
+    Uskip_whitespace(&msg);
     }
   }
 }
@@ -3719,60 +3815,60 @@ if (rc != OK)
 switch(rc)
   {
   case OK:
-  if (!au->set_id || set_id)    /* Complete success */
-    {
-    if (set_id) authenticated_id = string_copy_perm(set_id, TRUE);
-    sender_host_authenticated = au->name;
-    sender_host_auth_pubname  = au->public_name;
-    authentication_failed = FALSE;
-    authenticated_fail_id = NULL;   /* Impossible to already be set? */
-
-    received_protocol =
-      (sender_host_address ? protocols : protocols_local)
-       [pextend + pauthed + (tls_in.active.sock >= 0 ? pcrpted:0)];
-    *s = *ss = US"235 Authentication succeeded";
-    authenticated_by = au;
-    break;
-    }
+    if (!au->set_id || set_id)    /* Complete success */
+      {
+      if (set_id) authenticated_id = string_copy_perm(set_id, TRUE);
+      sender_host_authenticated = au->name;
+      sender_host_auth_pubname  = au->public_name;
+      authentication_failed = FALSE;
+      authenticated_fail_id = NULL;   /* Impossible to already be set? */
 
-  /* Authentication succeeded, but we failed to expand the set_id string.
-  Treat this as a temporary error. */
+      received_protocol =
+       (sender_host_address ? protocols : protocols_local)
+         [pextend + pauthed + (tls_in.active.sock >= 0 ? pcrpted:0)];
+      *s = *ss = US"235 Authentication succeeded";
+      authenticated_by = au;
+      break;
+      }
+
+    /* Authentication succeeded, but we failed to expand the set_id string.
+    Treat this as a temporary error. */
 
-  auth_defer_msg = expand_string_message;
-  /* Fall through */
+    auth_defer_msg = expand_string_message;
+    /* Fall through */
 
   case DEFER:
-  if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE);
-  *s = string_sprintf("435 Unable to authenticate at present%s",
-    auth_defer_user_msg);
-  *ss = string_sprintf("435 Unable to authenticate at present%s: %s",
-    set_id, auth_defer_msg);
-  break;
+    if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE);
+    *s = string_sprintf("435 Unable to authenticate at present%s",
+      auth_defer_user_msg);
+    *ss = string_sprintf("435 Unable to authenticate at present%s: %s",
+      set_id, auth_defer_msg);
+    break;
 
   case BAD64:
-  *s = *ss = US"501 Invalid base64 data";
-  break;
+    *s = *ss = US"501 Invalid base64 data";
+    break;
 
   case CANCELLED:
-  *s = *ss = US"501 Authentication cancelled";
-  break;
+    *s = *ss = US"501 Authentication cancelled";
+    break;
 
   case UNEXPECTED:
-  *s = *ss = US"553 Initial data not expected";
-  break;
+    *s = *ss = 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);
-  break;
+    if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE);
+    *s = US"535 Incorrect authentication data";
+    *ss = string_sprintf("535 Incorrect authentication data%s", set_id);
+    break;
 
   default:
-  if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE);
-  *s = US"435 Internal error";
-  *ss = string_sprintf("435 Internal error%s: return %d from authentication "
-    "check", set_id, rc);
-  break;
+    if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE);
+    *s = US"435 Internal error";
+    *ss = string_sprintf("435 Internal error%s: return %d from authentication "
+      "check", set_id, rc);
+    break;
   }
 
 return rc;
@@ -3791,7 +3887,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,
@@ -3902,6 +3999,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;
@@ -3975,7 +4080,7 @@ while (done <= 0)
 #endif
 
   switch(smtp_read_command(
-#ifdef SUPPORT_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
          !fl.pipe_connect_acceptable,
 #else
          TRUE,
@@ -4127,8 +4232,10 @@ while (done <= 0)
        if (++synprot_error_count > smtp_max_synprot_errors)
          {
          log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
-           "syntax or protocol errors (last command was \"%s\")",
-           host_and_ident(FALSE), string_printing(smtp_cmd_buffer));
+           "syntax or protocol errors (last command was \"%s\", %s)",
+           host_and_ident(FALSE), string_printing(smtp_cmd_buffer),
+           string_from_gstring(s_connhad_log(NULL))
+           );
          done = 1;
          }
 
@@ -4211,7 +4318,7 @@ while (done <= 0)
          host_build_sender_fullhost();  /* Rebuild */
          break;
          }
-#ifdef SUPPORT_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
        else if (!fl.pipe_connect_acceptable && !check_sync())
 #else
        else if (!check_sync())
@@ -4344,7 +4451,7 @@ while (done <= 0)
          sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
          f.smtp_in_pipelining_advertised = TRUE;
 
-#ifdef SUPPORT_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
          if (fl.pipe_connect_acceptable)
            {
            f.smtp_in_early_pipe_advertised = TRUE;
@@ -4379,8 +4486,8 @@ while (done <= 0)
            if (au->server)
              {
              DEBUG(D_auth+D_expand) debug_printf_indent(
-               "Evaluating advertise_condition for %s athenticator\n",
-               au->public_name);
+               "Evaluating advertise_condition for %s %s athenticator\n",
+               au->name, au->public_name);
              if (  !au->advertise_condition
                 || expand_check_condition(au->advertise_condition, au->name,
                        US"authenticator")
@@ -4462,7 +4569,7 @@ while (done <= 0)
 #ifndef DISABLE_TLS
       if (tls_in.active.sock >= 0)
        (void)tls_write(NULL, g->s, g->ptr,
-# ifdef SUPPORT_PIPE_CONNECT
+# ifndef DISABLE_PIPE_CONNECT
                        fl.pipe_connect_acceptable && pipeline_connect_sends());
 # else
                        FALSE);
@@ -4788,7 +4895,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;
 
@@ -4832,8 +4940,8 @@ while (done <= 0)
       and EXPN etc. to be used when space is short. */
 
       if (!receive_check_fs(
-          (smtp_check_spool_space && message_size >= 0)?
-             message_size + 5000 : 0))
+          smtp_check_spool_space && message_size >= 0
+             message_size + 5000 : 0))
        {
        smtp_printf("452 Space shortage, please try later\r\n", FALSE);
        sender_address = NULL;
@@ -4850,7 +4958,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);
          }
@@ -4920,6 +5029,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;
 
@@ -5042,7 +5155,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;
 
@@ -5075,7 +5189,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)
          {
@@ -5142,9 +5256,9 @@ while (done <= 0)
        recipients_list[recipients_count-1].orcpt = orcpt;
        recipients_list[recipients_count-1].dsn_flags = dsn_flags;
 
-       DEBUG(D_receive) debug_printf("DSN: orcpt: %s  flags: %d\n",
+       /* DEBUG(D_receive) debug_printf("DSN: orcpt: %s  flags: %d\n",
          recipients_list[recipients_count-1].orcpt,
-         recipients_list[recipients_count-1].dsn_flags);
+         recipients_list[recipients_count-1].dsn_flags); */
        }
 
       /* The recipient was discarded */
@@ -5159,8 +5273,8 @@ while (done <= 0)
        discarded = TRUE;
        log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: "
          "discarded by %s ACL%s%s", host_and_ident(TRUE),
-         sender_address_unrewritten? sender_address_unrewritten : sender_address,
-         smtp_cmd_argument, f.recipients_discarded? "MAIL" : "RCPT",
+         sender_address_unrewritten ? sender_address_unrewritten : sender_address,
+         smtp_cmd_argument, f.recipients_discarded ? "MAIL" : "RCPT",
          log_msg ? US": " : US"", log_msg ? log_msg : US"");
        }
 
@@ -5220,16 +5334,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;
@@ -5238,9 +5343,10 @@ while (done <= 0)
     case DATA_CMD:
       HAD(SCH_DATA);
       f.dot_ends = TRUE;
+      f.bdat_readers_wanted = FALSE;
 
     DATA_BDAT:         /* Common code for DATA and BDAT */
-#ifdef SUPPORT_PIPE_CONNECT
+#ifndef DISABLE_PIPE_CONNECT
       fl.pipe_connect_acceptable = FALSE;
 #endif
       if (!discarded && recipients_count <= 0)
@@ -5266,7 +5372,10 @@ while (done <= 0)
            : US"valid RCPT command must precede BDAT");
 
        if (chunking_state > CHUNKING_OFFERED)
+         {
+         bdat_push_receive_functions();
          bdat_flush_data();
+         }
        break;
        }
 
@@ -5275,6 +5384,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;
        }
 
@@ -5312,6 +5427,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,
@@ -5730,7 +5848,7 @@ while (done <= 0)
 
       oldsignal = signal(SIGCHLD, SIG_IGN);
 
-      if ((pid = fork()) == 0)
+      if ((pid = exim_fork(US"etrn-command")) == 0)
        {
        smtp_input = FALSE;       /* This process is not associated with the */
        (void)fclose(smtp_in);    /* SMTP call any more. */
@@ -5741,7 +5859,8 @@ while (done <= 0)
        /* If not serializing, do the exec right away. Otherwise, fork down
        into another process. */
 
-       if (!smtp_etrn_serialize || (pid = fork()) == 0)
+       if (  !smtp_etrn_serialize 
+          || (pid = exim_fork(US"etrn-serialised-command")) == 0)
          {
          DEBUG(D_exec) debug_print_argv(argv);
          exim_nullstd();                   /* Ensure std{in,out,err} exist */
@@ -5889,12 +6008,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);