ARC: add $arc_oldest_pass variable, for verify
[exim.git] / src / src / smtp_in.c
index 48437c3808994c78182fdc68ad958d35c4c99760..fb874e13257f207cbb2bc86664d4adca3be9b7e0 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2017 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
@@ -187,7 +187,7 @@ static smtp_cmd_list cmd_list[] = {
   { "auth",       sizeof("auth")-1,       AUTH_CMD, TRUE,  TRUE  },
   #ifdef SUPPORT_TLS
   { "starttls",   sizeof("starttls")-1,   STARTTLS_CMD, FALSE, FALSE },
-  { "tls_auth",   0,                      TLS_AUTH_CMD, FALSE, TRUE },
+  { "tls_auth",   0,                      TLS_AUTH_CMD, FALSE, FALSE },
   #endif
 
 /* If you change anything above here, also fix the definitions below. */
@@ -339,8 +339,10 @@ int fd, rc;
 fd_set fds;
 struct timeval tzero;
 
+#ifdef SUPPORT_TLS
 if (tls_in.active >= 0)
  return !tls_could_read();
+#endif
 
 if (smtp_inptr < smtp_inend)
   return FALSE;
@@ -357,16 +359,13 @@ rc = smtp_getc(GETC_BUFFER_UNLIMITED);
 if (rc < 0) return TRUE;      /* End of file or error */
 
 smtp_ungetc(rc);
-rc = smtp_inend - smtp_inptr;
-if (rc > 150) rc = 150;
-smtp_inptr[rc] = 0;
 return FALSE;
 }
 
 static BOOL
 check_sync(void)
 {
-if (!smtp_enforce_sync || sender_host_address == NULL || sender_host_notsocket)
+if (!smtp_enforce_sync || !sender_host_address || sender_host_notsocket)
   return TRUE;
 
 return wouldblock_reading();
@@ -425,6 +424,53 @@ log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS,
 
 
 
+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 >= 0 ? " TLS" : "", host_and_ident(FALSE));
+if (smtp_batched_input)
+  moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */
+smtp_notquit_exit(US"command-timeout", US"421",
+  US"%s: SMTP command timeout - closing connection",
+  smtp_active_hostname);
+exim_exit(EXIT_FAILURE, US"receiving");
+}
+
+void
+smtp_command_sigterm_exit(void)
+{
+log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info());
+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");
+}
+
+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);
+receive_bomb_out(US"data-timeout", US"SMTP incoming data timeout");
+/* Does not return */
+}
+
+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");
+receive_bomb_out(US"signal-exit",
+  US"Service not available - SIGTERM or SIGINT received");
+/* Does not return */
+}
+
+
+
 /* Refill the buffer, and notify DKIM verification code.
 Return false for error or EOF.
 */
@@ -437,22 +483,33 @@ if (!smtp_out) return FALSE;
 fflush(smtp_out);
 if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
 
-/* Limit amount read, so non-message data is not fed to DKIM */
+/* Limit amount read, so non-message data is not fed to DKIM.
+Take care to not touch the safety NUL at the end of the buffer. */
 
-rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE, lim));
+rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE-1, lim));
 save_errno = errno;
-alarm(0);
+if (smtp_receive_timeout > 0) alarm(0);
 if (rc <= 0)
   {
   /* Must put the error text in fixed store, because this might be during
   header reading, where it releases unused store above the header. */
   if (rc < 0)
     {
+    if (had_command_timeout)           /* set by signal handler */
+      smtp_command_timeout_exit();     /* does not return */
+    if (had_command_sigterm)
+      smtp_command_sigterm_exit();
+    if (had_data_timeout)
+      smtp_data_timeout_exit();
+    if (had_data_sigint)
+      smtp_data_sigint_exit();
+
     smtp_had_error = save_errno;
     smtp_read_error = string_copy_malloc(
       string_sprintf(" (error: %s)", strerror(save_errno)));
     }
-  else smtp_had_eof = 1;
+  else
+    smtp_had_eof = 1;
   return FALSE;
   }
 #ifndef DISABLE_DKIM
@@ -538,7 +595,7 @@ uschar * log_msg;
 for(;;)
   {
 #ifndef DISABLE_DKIM
-  BOOL dkim_save;
+  unsigned dkim_save;
 #endif
 
   if (chunking_data_left > 0)
@@ -549,7 +606,7 @@ for(;;)
   receive_ungetc = lwr_receive_ungetc;
 #ifndef DISABLE_DKIM
   dkim_save = dkim_collect_input;
-  dkim_collect_input = FALSE;
+  dkim_collect_input = 0;
 #endif
 
   /* Unless PIPELINING was offered, there should be no next command
@@ -563,11 +620,12 @@ for(;;)
     incomplete_transaction_log(US"sync failure");
     log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
       "(next input sent too soon: pipelining was not advertised): "
-      "rejected \"%s\" %s next input=\"%s\"",
+      "rejected \"%s\" %s next input=\"%s\"%s",
       smtp_cmd_buffer, host_and_ident(TRUE),
-      string_printing(string_copyn(smtp_inptr, n)));
-      (void) synprot_error(L_smtp_protocol_error, 554, NULL,
-       US"SMTP synchronization error");
+      string_printing(string_copyn(smtp_inptr, n)),
+      smtp_inend - smtp_inptr > n ? "..." : "");
+    (void) synprot_error(L_smtp_protocol_error, 554, NULL,
+      US"SMTP synchronization error");
     goto repeat_until_rset;
     }
 
@@ -650,7 +708,7 @@ next_cmd:
          }
 
       receive_getc = bdat_getc;
-      receive_getbuf = bdat_getbuf;
+      receive_getbuf = bdat_getbuf;    /* r~getbuf is never actually used */
       receive_ungetc = bdat_ungetc;
 #ifndef DISABLE_DKIM
       dkim_collect_input = dkim_save;
@@ -678,8 +736,11 @@ return buf;
 void
 bdat_flush_data(void)
 {
-unsigned n = chunking_data_left;
-(void) bdat_getbuf(&n);
+while (chunking_data_left)
+  {
+  unsigned n = chunking_data_left;
+  if (!bdat_getbuf(&n)) break;
+  }
 
 receive_getc = lwr_receive_getc;
 receive_getbuf = lwr_receive_getbuf;
@@ -839,7 +900,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);
+  exim_exit(EXIT_FAILURE, NULL);
   }
 
 /* If this is the first output for a (non-batch) RCPT command, see if all RCPTs
@@ -910,16 +971,7 @@ Returns:  nothing
 static void
 command_timeout_handler(int sig)
 {
-sig = sig;    /* Keep picky compilers happy */
-log_write(L_lost_incoming_connection,
-          LOG_MAIN, "SMTP command timeout on%s connection from %s",
-          (tls_in.active >= 0)? " TLS" : "",
-          host_and_ident(FALSE));
-if (smtp_batched_input)
-  moan_smtp_batch(NULL, "421 SMTP command timeout");  /* Does not return */
-smtp_notquit_exit(US"command-timeout", US"421",
-  US"%s: SMTP command timeout - closing connection", smtp_active_hostname);
-exim_exit(EXIT_FAILURE);
+had_command_timeout = sig;
 }
 
 
@@ -937,13 +989,7 @@ Returns:  nothing
 static void
 command_sigterm_handler(int sig)
 {
-sig = sig;    /* Keep picky compilers happy */
-log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info());
-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);
+had_command_sigterm = sig;
 }
 
 
@@ -1503,6 +1549,7 @@ int ptr = 0;
 smtp_cmd_list *p;
 BOOL hadnull = FALSE;
 
+had_command_timeout = 0;
 os_non_restarting_signal(SIGALRM, command_timeout_handler);
 
 while ((c = (receive_getc)(buffer_lim)) != '\n' && c != EOF)
@@ -1608,11 +1655,11 @@ if (proxy_session && proxy_session_failed)
 
 /* Enforce synchronization for unknown commands */
 
-if (smtp_inptr < smtp_inend &&                     /* Outstanding input */
-    check_sync &&                                  /* Local flag set */
-    smtp_enforce_sync &&                           /* Global flag set */
-    sender_host_address != NULL &&                 /* Not local input */
-    !sender_host_notsocket)                        /* Really is a socket */
+if (  smtp_inptr < smtp_inend          /* Outstanding input */
+   && check_sync                       /* Local flag set */
+   && smtp_enforce_sync                        /* Global flag set */
+   && sender_host_address              /* Not local input */
+   && !sender_host_notsocket)          /* Really is a socket */
   return BADSYN_CMD;
 
 return OTHER_CMD;
@@ -1640,27 +1687,27 @@ Returns:    nothing
 void
 smtp_closedown(uschar *message)
 {
-if (smtp_in == NULL || smtp_batched_input) return;
+if (!smtp_in || smtp_batched_input) return;
 receive_swallow_smtp();
 smtp_printf("421 %s\r\n", FALSE, message);
 
 for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
   {
   case EOF_CMD:
-  return;
+    return;
 
   case QUIT_CMD:
-  smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
-  mac_smtp_fflush();
-  return;
+    smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
+    mac_smtp_fflush();
+    return;
 
   case RSET_CMD:
-  smtp_printf("250 Reset OK\r\n", FALSE);
-  break;
+    smtp_printf("250 Reset OK\r\n", FALSE);
+    break;
 
   default:
-  smtp_printf("421 %s\r\n", FALSE, message);
-  break;
+    smtp_printf("421 %s\r\n", FALSE, message);
+    break;
   }
 }
 
@@ -1708,37 +1755,22 @@ return string_sprintf("SMTP connection from %s", hostname);
 /* Append TLS-related information to a log line
 
 Arguments:
-  s            String under construction: allocated string to extend, or NULL
-  sizep                Pointer to current allocation size (update on return), or NULL
-  ptrp         Pointer to index for new entries in string (update on return), or NULL
+  g            String under construction: allocated string to extend, or NULL
 
 Returns:       Allocated string or NULL
 */
-static uschar *
-s_tlslog(uschar * s, int * sizep, int * ptrp)
+static gstring *
+s_tlslog(gstring * g)
 {
-  int size = sizep ? *sizep : 0;
-  int ptr = ptrp ? *ptrp : 0;
-
-  if (LOGGING(tls_cipher) && tls_in.cipher != NULL)
-    s = string_append(s, &size, &ptr, 2, US" X=", tls_in.cipher);
-  if (LOGGING(tls_certificate_verified) && tls_in.cipher != NULL)
-    s = string_append(s, &size, &ptr, 2, US" CV=",
-      tls_in.certificate_verified? "yes":"no");
-  if (LOGGING(tls_peerdn) && tls_in.peerdn != NULL)
-    s = string_append(s, &size, &ptr, 3, US" DN=\"",
-      string_printing(tls_in.peerdn), US"\"");
-  if (LOGGING(tls_sni) && tls_in.sni != NULL)
-    s = string_append(s, &size, &ptr, 3, US" SNI=\"",
-      string_printing(tls_in.sni), US"\"");
-
-  if (s)
-    {
-    s[ptr] = '\0';
-    if (sizep) *sizep = size;
-    if (ptrp) *ptrp = ptr;
-    }
-  return s;
+if (LOGGING(tls_cipher) && tls_in.cipher)
+  g = string_append(g, 2, US" X=", tls_in.cipher);
+if (LOGGING(tls_certificate_verified) && tls_in.cipher)
+  g = string_append(g, 2, US" CV=", tls_in.certificate_verified? "yes":"no");
+if (LOGGING(tls_peerdn) && tls_in.peerdn)
+  g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\"");
+if (LOGGING(tls_sni) && tls_in.sni)
+  g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\"");
+return g;
 }
 #endif
 
@@ -1757,46 +1789,42 @@ Returns:     nothing
 void
 smtp_log_no_mail(void)
 {
-int size, ptr, i;
-uschar *s, *sep;
+int i;
+uschar * sep, * s;
+gstring * g = NULL;
 
 if (smtp_mailcmd_count > 0 || !LOGGING(smtp_no_mail))
   return;
 
-s = NULL;
-size = ptr = 0;
-
-if (sender_host_authenticated != NULL)
+if (sender_host_authenticated)
   {
-  s = string_append(s, &size, &ptr, 2, US" A=", sender_host_authenticated);
-  if (authenticated_id != NULL)
-    s = string_append(s, &size, &ptr, 2, US":", authenticated_id);
+  g = string_append(g, 2, US" A=", sender_host_authenticated);
+  if (authenticated_id) g = string_append(g, 2, US":", authenticated_id);
   }
 
 #ifdef SUPPORT_TLS
-s = s_tlslog(s, &size, &ptr);
+g = s_tlslog(g);
 #endif
 
-sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)?
-  US" C=..." : US" C=";
+sep = smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE ?  US" C=..." : US" C=";
+
 for (i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
-  {
   if (smtp_connection_had[i] != SCH_NONE)
     {
-    s = string_append(s, &size, &ptr, 2, sep,
-      smtp_names[smtp_connection_had[i]]);
+    g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
     sep = US",";
     }
-  }
 
 for (i = 0; i < smtp_ch_index; i++)
   {
-  s = string_append(s, &size, &ptr, 2, sep, smtp_names[smtp_connection_had[i]]);
+  g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
   sep = US",";
   }
 
-if (s) s[ptr] = 0; else s = US"";
-log_write(0, LOG_MAIN, "no MAIL in SMTP connection from %s D=%s%s",
+if (!(s = string_from_gstring(g))) s = US"";
+
+log_write(0, LOG_MAIN, "no MAIL in %sSMTP connection from %s D=%s%s",
+  tcp_in_fastopen ? US"TFO " : US"",
   host_and_ident(FALSE), string_timesince(&smtp_connection_start), s);
 }
 
@@ -1806,17 +1834,19 @@ log_write(0, LOG_MAIN, "no MAIL in SMTP connection from %s D=%s%s",
 uschar *
 smtp_cmd_hist(void)
 {
-uschar * list = NULL;
-int size = 0, len = 0, i;
+int  i;
+gstring * list = NULL;
+uschar * s;
 
 for (i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
   if (smtp_connection_had[i] != SCH_NONE)
-    list = string_append_listele(list, &size, &len, ',',
-      smtp_names[smtp_connection_had[i]]);
+    list = string_append_listele(list, ',', smtp_names[smtp_connection_had[i]]);
+
 for (i = 0; i < smtp_ch_index; i++)
-  list = string_append_listele(list, &size, &len, ',',
-    smtp_names[smtp_connection_had[i]]);
-return list ? list : US"";
+  list = string_append_listele(list, ',', smtp_names[smtp_connection_had[i]]);
+
+s = string_from_gstring(list);
+return s ? s : US"";
 }
 
 
@@ -1849,7 +1879,7 @@ BOOL yield = helo_accept_junk;
 
 /* Discard any previous helo name */
 
-if (sender_helo_name != NULL)
+if (sender_helo_name)
   {
   store_free(sender_helo_name);
   sender_helo_name = NULL;
@@ -1858,7 +1888,7 @@ if (sender_helo_name != NULL)
 /* Skip tests if junk is permitted. */
 
 if (!yield)
-  {
+
   /* Allow the new standard form for IPv6 address literals, namely,
   [IPv6:....], and because someone is bound to use it, allow an equivalent
   IPv4 form. Allow plain addresses as well. */
@@ -1881,21 +1911,14 @@ if (!yield)
   /* Non-literals must be alpha, dot, hyphen, plus any non-valid chars
   that have been configured (usually underscore - sigh). */
 
-  else if (*s != 0)
-    {
-    yield = TRUE;
-    while (*s != 0)
-      {
+  else if (*s)
+    for (yield = TRUE; *s; s++)
       if (!isalnum(*s) && *s != '.' && *s != '-' &&
           Ustrchr(helo_allow_chars, *s) == NULL)
         {
         yield = FALSE;
         break;
         }
-      s++;
-      }
-    }
-  }
 
 /* Save argument if OK */
 
@@ -1939,17 +1962,17 @@ while (v > smtp_cmd_data && *v != '=' && !isspace(*v))
 
 n = v;
 if (*v == '=')
-{
+  {
   while(isalpha(n[-1])) n--;
   /* RFC says SP, but TAB seen in wild and other major MTAs accept it */
   if (!isspace(n[-1])) return FALSE;
   n[-1] = 0;
-}
+  }
 else
-{
+  {
   n++;
   if (v == smtp_cmd_data) return FALSE;
-}
+  }
 *v++ = 0;
 *name = n;
 *value = v;
@@ -1965,13 +1988,13 @@ return TRUE;
 *************************************************/
 
 /* This function is called whenever the SMTP session is reset from
-within either of the setup functions.
+within either of the setup functions; also from the daemon loop.
 
 Argument:   the stacking pool storage reset point
 Returns:    nothing
 */
 
-static void
+void
 smtp_reset(void *reset_point)
 {
 recipients_list = NULL;
@@ -1998,8 +2021,8 @@ 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_orig = NULL;
-deliver_domain_orig = NULL;
+deliver_localpart_parent = deliver_localpart_orig = NULL;
+deliver_domain_parent = deliver_domain_orig = NULL;
 callout_address = NULL;
 submission_name = NULL;                              /* Can be set by ACL */
 raw_sender = NULL;                  /* After SMTP rewrite, before qualifying */
@@ -2014,10 +2037,28 @@ bmi_run = 0;
 bmi_verdicts = NULL;
 #endif
 dnslist_domain = dnslist_matched = NULL;
+#ifdef SUPPORT_SPF
+spf_header_comment = spf_received = spf_result = spf_smtp_comment = NULL;
+spf_result_guessed = FALSE;
+#endif
 #ifndef DISABLE_DKIM
-dkim_signers = NULL;
+dkim_cur_signer = dkim_signers =
+dkim_signing_domain = dkim_signing_selector = dkim_signatures = NULL;
+dkim_cur_signer = dkim_signers = dkim_signing_domain = dkim_signing_selector = NULL;
 dkim_disable_verify = FALSE;
-dkim_collect_input = FALSE;
+dkim_collect_input = 0;
+dkim_verify_overall = dkim_verify_status = dkim_verify_reason = NULL;
+dkim_key_length = 0;
+dkim_verify_signers = US"$dkim_signers";
+#endif
+#ifdef EXPERIMENTAL_DMARC
+dmarc_has_been_checked = dmarc_disable_verify = dmarc_enable_forensic = FALSE;
+dmarc_domain_policy = dmarc_forensic_sender =
+dmarc_history_file = dmarc_status = dmarc_status_text = dmarc_tld_file =
+dmarc_used_domain = NULL;
+#endif
+#ifdef EXPERIMENTAL_ARC
+arc_state = arc_state_reason = NULL;
 #endif
 dsn_ret = 0;
 dsn_envid = NULL;
@@ -2025,12 +2066,6 @@ deliver_host = deliver_host_address = NULL;      /* Can be set by ACL */
 #ifndef DISABLE_PRDR
 prdr_requested = FALSE;
 #endif
-#ifdef EXPERIMENTAL_SPF
-spf_header_comment = NULL;
-spf_received = NULL;
-spf_result = NULL;
-spf_smtp_comment = NULL;
-#endif
 #ifdef SUPPORT_I18N
 message_smtputf8 = FALSE;
 #endif
@@ -2128,14 +2163,14 @@ while (done <= 0)
     case HELO_CMD:
     case EHLO_CMD:
 
-    check_helo(smtp_cmd_data);
-    /* Fall through */
+      check_helo(smtp_cmd_data);
+      /* Fall through */
 
     case RSET_CMD:
-    cancel_cutthrough_connection(TRUE, US"RSET received");
-    smtp_reset(reset_point);
-    bsmtp_transaction_linecount = receive_linecount;
-    break;
+      cancel_cutthrough_connection(TRUE, US"RSET received");
+      smtp_reset(reset_point);
+      bsmtp_transaction_linecount = receive_linecount;
+      break;
 
 
     /* The MAIL FROM command requires an address as an operand. All we
@@ -2145,53 +2180,53 @@ while (done <= 0)
     it is the canonical extracted address which is all that is kept. */
 
     case MAIL_CMD:
-    smtp_mailcmd_count++;              /* Count for no-mail log */
-    if (sender_address != NULL)
-      /* The function moan_smtp_batch() does not return. */
-      moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given");
+      smtp_mailcmd_count++;              /* Count for no-mail log */
+      if (sender_address != NULL)
+       /* The function moan_smtp_batch() does not return. */
+       moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given");
 
-    if (smtp_cmd_data[0] == 0)
-      /* The function moan_smtp_batch() does not return. */
-      moan_smtp_batch(smtp_cmd_buffer, "501 MAIL FROM must have an address operand");
+      if (smtp_cmd_data[0] == 0)
+       /* The function moan_smtp_batch() does not return. */
+       moan_smtp_batch(smtp_cmd_buffer, "501 MAIL FROM must have an address operand");
 
-    /* Reset to start of message */
+      /* Reset to start of message */
 
-    cancel_cutthrough_connection(TRUE, US"MAIL received");
-    smtp_reset(reset_point);
+      cancel_cutthrough_connection(TRUE, US"MAIL received");
+      smtp_reset(reset_point);
 
-    /* Apply SMTP rewrite */
+      /* 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) != 0)?
+       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 */
+      /* Extract the address; the TRUE flag allows <> as valid */
 
-    raw_sender =
-      parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
-        TRUE);
+      raw_sender =
+       parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
+         TRUE);
 
-    if (raw_sender == NULL)
-      /* The function moan_smtp_batch() does not return. */
-      moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
+      if (!raw_sender)
+       /* The function moan_smtp_batch() does not return. */
+       moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
 
-    sender_address = string_copy(raw_sender);
+      sender_address = string_copy(raw_sender);
 
-    /* Qualify unqualified sender addresses if permitted to do so. */
+      /* Qualify unqualified sender addresses if permitted to do so. */
 
-    if (sender_domain == 0 && sender_address[0] != 0 && sender_address[0] != '@')
-      {
-      if (allow_unqualified_sender)
-        {
-        sender_address = rewrite_address_qualify(sender_address, FALSE);
-        DEBUG(D_receive) debug_printf("unqualified address %s accepted "
-          "and rewritten\n", raw_sender);
-        }
-      /* The function moan_smtp_batch() does not return. */
-      else moan_smtp_batch(smtp_cmd_buffer, "501 sender address must contain "
-        "a domain");
-      }
-    break;
+      if (  !sender_domain
+         && sender_address[0] != 0 && sender_address[0] != '@')
+       if (allow_unqualified_sender)
+         {
+         sender_address = rewrite_address_qualify(sender_address, FALSE);
+         DEBUG(D_receive) debug_printf("unqualified address %s accepted "
+           "and rewritten\n", raw_sender);
+         }
+       /* The function moan_smtp_batch() does not return. */
+       else
+         moan_smtp_batch(smtp_cmd_buffer, "501 sender address must contain "
+           "a domain");
+      break;
 
 
     /* The RCPT TO command requires an address as an operand. All we do
@@ -2202,53 +2237,54 @@ while (done <= 0)
     extracted address. */
 
     case RCPT_CMD:
-    if (sender_address == NULL)
-      /* The function moan_smtp_batch() does not return. */
-      moan_smtp_batch(smtp_cmd_buffer, "503 No sender yet given");
+      if (!sender_address)
+       /* The function moan_smtp_batch() does not return. */
+       moan_smtp_batch(smtp_cmd_buffer, "503 No sender yet given");
 
-    if (smtp_cmd_data[0] == 0)
-      /* The function moan_smtp_batch() does not return. */
-      moan_smtp_batch(smtp_cmd_buffer, "501 RCPT TO must have an address operand");
+      if (smtp_cmd_data[0] == 0)
+       /* The function moan_smtp_batch() does not return. */
+       moan_smtp_batch(smtp_cmd_buffer,
+         "501 RCPT TO must have an address operand");
 
-    /* Check maximum number allowed */
+      /* Check maximum number allowed */
 
-    if (recipients_max > 0 && recipients_count + 1 > recipients_max)
-      /* The function moan_smtp_batch() does not return. */
-      moan_smtp_batch(smtp_cmd_buffer, "%s too many recipients",
-        recipients_max_reject? "552": "452");
+      if (recipients_max > 0 && recipients_count + 1 > recipients_max)
+       /* The function moan_smtp_batch() does not return. */
+       moan_smtp_batch(smtp_cmd_buffer, "%s too many recipients",
+         recipients_max_reject? "552": "452");
 
-    /* Apply SMTP rewrite, then extract address. Don't allow "<>" as a
-    recipient address */
+      /* Apply SMTP rewrite, then extract address. Don't allow "<>" as a
+      recipient address */
 
-    recipient = rewrite_existflags & rewrite_smtp
-      ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
-                   global_rewrite_rules)
-      : smtp_cmd_data;
+      recipient = rewrite_existflags & rewrite_smtp
+       ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+                     global_rewrite_rules)
+       : smtp_cmd_data;
 
-    recipient = parse_extract_address(recipient, &errmess, &start, &end,
-      &recipient_domain, FALSE);
+      recipient = parse_extract_address(recipient, &errmess, &start, &end,
+       &recipient_domain, FALSE);
 
-    if (!recipient)
-      /* The function moan_smtp_batch() does not return. */
-      moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
+      if (!recipient)
+       /* The function moan_smtp_batch() does not return. */
+       moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
 
-    /* If the recipient address is unqualified, qualify it if permitted. Then
-    add it to the list of recipients. */
+      /* If the recipient address is unqualified, qualify it if permitted. Then
+      add it to the list of recipients. */
 
-    if (recipient_domain == 0)
-      {
-      if (allow_unqualified_recipient)
-        {
-        DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
-          recipient);
-        recipient = rewrite_address_qualify(recipient, TRUE);
-        }
-      /* The function moan_smtp_batch() does not return. */
-      else moan_smtp_batch(smtp_cmd_buffer, "501 recipient address must contain "
-        "a domain");
-      }
-    receive_add_recipient(recipient, -1);
-    break;
+      if (!recipient_domain)
+       if (allow_unqualified_recipient)
+         {
+         DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
+           recipient);
+         recipient = rewrite_address_qualify(recipient, TRUE);
+         }
+       /* The function moan_smtp_batch() does not return. */
+       else
+         moan_smtp_batch(smtp_cmd_buffer,
+           "501 recipient address must contain a domain");
+
+      receive_add_recipient(recipient, -1);
+      break;
 
 
     /* The DATA command is legal only if it follows successful MAIL FROM
@@ -2256,22 +2292,20 @@ while (done <= 0)
     command is encountered. */
 
     case DATA_CMD:
-    if (sender_address == NULL || recipients_count <= 0)
-      {
-      /* The function moan_smtp_batch() does not return. */
-      if (sender_address == NULL)
-        moan_smtp_batch(smtp_cmd_buffer,
-          "503 MAIL FROM:<sender> command must precede DATA");
+      if (!sender_address || recipients_count <= 0)
+       /* The function moan_smtp_batch() does not return. */
+       if (!sender_address)
+         moan_smtp_batch(smtp_cmd_buffer,
+           "503 MAIL FROM:<sender> command must precede DATA");
+       else
+         moan_smtp_batch(smtp_cmd_buffer,
+           "503 RCPT TO:<recipient> must precede DATA");
       else
-        moan_smtp_batch(smtp_cmd_buffer,
-          "503 RCPT TO:<recipient> must precede DATA");
-      }
-    else
-      {
-      done = 3;                      /* DATA successfully achieved */
-      message_ended = END_NOTENDED;  /* Indicate in middle of message */
-      }
-    break;
+       {
+       done = 3;                      /* DATA successfully achieved */
+       message_ended = END_NOTENDED;  /* Indicate in middle of message */
+       }
+      break;
 
 
     /* The VRFY, EXPN, HELP, ETRN, and NOOP commands are ignored. */
@@ -2281,32 +2315,32 @@ while (done <= 0)
     case HELP_CMD:
     case NOOP_CMD:
     case ETRN_CMD:
-    bsmtp_transaction_linecount = receive_linecount;
-    break;
+      bsmtp_transaction_linecount = receive_linecount;
+      break;
 
 
     case EOF_CMD:
     case QUIT_CMD:
-    done = 2;
-    break;
+      done = 2;
+      break;
 
 
     case BADARG_CMD:
-    /* The function moan_smtp_batch() does not return. */
-    moan_smtp_batch(smtp_cmd_buffer, "501 Unexpected argument data");
-    break;
+      /* The function moan_smtp_batch() does not return. */
+      moan_smtp_batch(smtp_cmd_buffer, "501 Unexpected argument data");
+      break;
 
 
     case BADCHAR_CMD:
-    /* The function moan_smtp_batch() does not return. */
-    moan_smtp_batch(smtp_cmd_buffer, "501 Unexpected NULL in SMTP command");
-    break;
+      /* The function moan_smtp_batch() does not return. */
+      moan_smtp_batch(smtp_cmd_buffer, "501 Unexpected NULL in SMTP command");
+      break;
 
 
     default:
-    /* The function moan_smtp_batch() does not return. */
-    moan_smtp_batch(smtp_cmd_buffer, "500 Command unrecognized");
-    break;
+      /* The function moan_smtp_batch() does not return. */
+      moan_smtp_batch(smtp_cmd_buffer, "500 Command unrecognized");
+      break;
     }
   }
 
@@ -2329,6 +2363,28 @@ return FALSE;
 }
 
 
+
+
+#ifdef TCP_FASTOPEN
+static void
+tfo_in_check(void)
+{
+# ifdef TCP_INFO
+struct tcp_info tinfo;
+socklen_t len = sizeof(tinfo);
+
+if (  getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0
+   && tinfo.tcpi_state == TCP_SYN_RECV
+   )
+  {
+  DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (state TCP_SYN_RECV)\n");
+  tcp_in_fastopen = TRUE;
+  }
+# endif
+}
+#endif
+
+
 /*************************************************
 *          Start an SMTP session                 *
 *************************************************/
@@ -2345,11 +2401,11 @@ Returns:       FALSE if the session can not continue; something has
 BOOL
 smtp_start_session(void)
 {
-int size = 256;
-int ptr, esclen;
+int esclen;
 uschar *user_msg, *log_msg;
 uschar *code, *esc;
-uschar *p, *s, *ss;
+uschar *p, *s;
+gstring * ss;
 
 gettimeofday(&smtp_connection_start, NULL);
 for (smtp_ch_index = 0; smtp_ch_index < SMTP_HBUFF_SIZE; smtp_ch_index++)
@@ -2372,7 +2428,8 @@ smtp_exit_function_called = FALSE;    /* For avoiding loop in not-quit exit */
 /* If receiving by -bs from a trusted user, or testing with -bh, we allow
 authentication settings from -oMaa to remain in force. */
 
-if (!host_checking && !sender_host_notsocket) sender_host_authenticated = NULL;
+if (!host_checking && !sender_host_notsocket)
+  sender_host_auth_pubname = sender_host_authenticated = NULL;
 authenticated_by = NULL;
 
 #ifdef SUPPORT_TLS
@@ -2416,10 +2473,12 @@ 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. */
+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 = (uschar *)malloc(IN_BUFFER_SIZE)))
+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';
 
 receive_getc = smtp_getc;
 receive_getbuf = smtp_getbuf;
@@ -2434,7 +2493,7 @@ smtp_had_eof = smtp_had_error = 0;
 /* Set up the message size limit; this may be host-specific */
 
 thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
-if (expand_string_message != NULL)
+if (expand_string_message)
   {
   if (thismessage_size_limit == -1)
     log_write(0, LOG_MAIN|LOG_PANIC, "unable to expand message_size_limit: "
@@ -2524,7 +2583,7 @@ if (!sender_host_unknown)
 
     DEBUG(D_receive) debug_printf("checking for IP options\n");
 
-    if (getsockopt(fileno(smtp_out), IPPROTO_IP, IP_OPTIONS, (uschar *)(ipopt),
+    if (getsockopt(fileno(smtp_out), IPPROTO_IP, IP_OPTIONS, US (ipopt),
           &optlen) < 0)
       {
       if (errno != ENOPROTOOPT)
@@ -2550,11 +2609,11 @@ if (!sender_host_unknown)
       struct in_addr addr;
 
       #if OPTSTYLE == 1
-      uschar *optstart = (uschar *)(ipopt->__data);
+      uschar *optstart = US (ipopt->__data);
       #elif OPTSTYLE == 2
-      uschar *optstart = (uschar *)(ipopt->ip_opts);
+      uschar *optstart = US (ipopt->ip_opts);
       #else
-      uschar *optstart = (uschar *)(ipopt->ipopt_list);
+      uschar *optstart = US (ipopt->ipopt_list);
       #endif
 
       DEBUG(D_receive) debug_printf("IP options exist\n");
@@ -2563,7 +2622,7 @@ if (!sender_host_unknown)
       p += Ustrlen(p);
 
       for (opt = optstart; opt != NULL &&
-           opt < (uschar *)(ipopt) + optlen;)
+           opt < US (ipopt) + optlen;)
         {
         switch (*opt)
           {
@@ -2815,8 +2874,12 @@ if (check_proxy_protocol_host())
   smtps port for use with older style SSL MTAs. */
 
 #ifdef SUPPORT_TLS
-  if (tls_in.on_connect && tls_server_start(tls_require_ciphers, &user_msg) != OK)
-    return smtp_log_tls_fail(user_msg);
+  if (tls_in.on_connect)
+    {
+    if (tls_server_start(tls_require_ciphers, &user_msg) != OK)
+      return smtp_log_tls_fail(user_msg);
+    cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
+    }
 #endif
 
 /* Run the connect ACL if it exists */
@@ -2873,34 +2936,31 @@ command. Sigh. To try to avoid this, build the complete greeting message
 first, and output it in one fell swoop. This gives a better chance of it
 ending up as a single packet. */
 
-ss = store_get(size);
-ptr = 0;
+ss = string_get(256);
 
 p = s;
 do       /* At least once, in case we have an empty string */
   {
   int len;
   uschar *linebreak = Ustrchr(p, '\n');
-  ss = string_catn(ss, &size, &ptr, code, 3);
-  if (linebreak == NULL)
+  ss = string_catn(ss, code, 3);
+  if (!linebreak)
     {
     len = Ustrlen(p);
-    ss = string_catn(ss, &size, &ptr, US" ", 1);
+    ss = string_catn(ss, US" ", 1);
     }
   else
     {
     len = linebreak - p;
-    ss = string_catn(ss, &size, &ptr, US"-", 1);
+    ss = string_catn(ss, US"-", 1);
     }
-  ss = string_catn(ss, &size, &ptr, esc, esclen);
-  ss = string_catn(ss, &size, &ptr, p, len);
-  ss = string_catn(ss, &size, &ptr, US"\r\n", 2);
+  ss = string_catn(ss, esc, esclen);
+  ss = string_catn(ss, p, len);
+  ss = string_catn(ss, US"\r\n", 2);
   p += len;
-  if (linebreak != NULL) p++;
+  if (linebreak) p++;
   }
-while (*p != 0);
-
-ss[ptr] = 0;  /* string_cat leaves room for this */
+while (*p);
 
 /* Before we write the banner, check that there is no input pending, unless
 this synchronisation check is disabled. */
@@ -2920,7 +2980,15 @@ if (!check_sync())
 
 /* Now output the banner */
 
-smtp_printf("%s", FALSE, ss);
+smtp_printf("%s", FALSE, string_from_gstring(ss));
+
+/* Attempt to see if we sent the banner before the last ACK of the 3-way
+handshake arrived.  If so we must have managed a TFO. */
+
+#ifdef TCP_FASTOPEN
+tfo_in_check();
+#endif
+
 return TRUE;
 }
 
@@ -3122,7 +3190,7 @@ return;
 
 /* This function is called when acl_check() fails. As well as calls from within
 this module, it is called from receive.c for an ACL after DATA. It sorts out
-logging the incident, and sets up the error response. A message containing
+logging the incident, and sends the error response. A message containing
 newlines is turned into a multiline SMTP response, but for logging, only the
 first line is used.
 
@@ -3290,7 +3358,8 @@ is closing if required and return 2.  */
 if (log_reject_target != 0)
   {
 #ifdef SUPPORT_TLS
-  uschar * tls = s_tlslog(NULL, NULL, NULL);
+  gstring * g = s_tlslog(NULL);
+  uschar * tls = string_from_gstring(g);
   if (!tls) tls = US"";
 #else
   uschar * tls = US"";
@@ -3509,7 +3578,7 @@ else
 
     HDEBUG(D_receive) debug_printf("getting IP address for %s\n",
       sender_helo_name);
-    rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A,
+    rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA,
                          NULL, NULL, NULL, &d, NULL, NULL);
     if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
       for (hh = &h; hh; hh = hh->next)
@@ -3612,6 +3681,7 @@ switch(rc)
     {
     if (set_id) authenticated_id = string_copy_malloc(set_id);
     sender_host_authenticated = au->name;
+    sender_host_auth_pubname  = au->public_name;
     authentication_failed = FALSE;
     authenticated_fail_id = NULL;   /* Impossible to already be set? */
 
@@ -3711,7 +3781,7 @@ else
   smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
 
 #ifdef SUPPORT_TLS
-tls_close(TRUE, TRUE);
+tls_close(TRUE, TLS_SHUTDOWN_NOWAIT);
 #endif
 
 log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
@@ -3783,11 +3853,11 @@ cmd_list[CMD_LIST_HELO].is_mail_cmd = TRUE;
 cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
 #ifdef SUPPORT_TLS
 cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE;
-cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
 #endif
 
 /* Set the local signal handler for SIGTERM - it tries to end off tidily */
 
+had_command_sigterm = 0;
 os_non_restarting_signal(SIGTERM, command_sigterm_handler);
 
 /* Batched SMTP is handled in a different function. */
@@ -3813,11 +3883,12 @@ while (done <= 0)
   void (*oldsignal)(int);
   pid_t pid;
   int start, end, sender_domain, recipient_domain;
-  int ptr, size, rc;
+  int rc;
   int c;
   auth_instance *au;
   uschar *orcpt = NULL;
   int flags;
+  gstring * g;
 
 #ifdef AUTH_TLS
   /* Check once per STARTTLS or SSL-on-connect for a TLS AUTH */
@@ -3828,24 +3899,24 @@ while (done <= 0)
      )
     {
     cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = FALSE;
-    if (  acl_smtp_auth
-       && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
-                 &user_msg, &log_msg)) != OK
-       )
-      {
-      done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
-      continue;
-      }
 
     for (au = auths; au; au = au->next)
       if (strcmpic(US"tls", au->driver_name) == 0)
        {
-       smtp_cmd_data = NULL;
-
-       if (smtp_in_auth(au, &s, &ss) == OK)
-         { DEBUG(D_auth) debug_printf("tls auth succeeded\n"); }
+       if (  acl_smtp_auth
+          && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
+                     &user_msg, &log_msg)) != OK
+          )
+         done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
        else
-         { DEBUG(D_auth) debug_printf("tls auth not succeeded\n"); }
+         {
+         smtp_cmd_data = NULL;
+
+         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"); }
+         }
        break;
        }
     }
@@ -4027,9 +4098,9 @@ while (done <= 0)
       /* Force a reverse lookup if HELO quoted something in helo_lookup_domains
       because otherwise the log can be confusing. */
 
-      if (sender_host_name == NULL &&
-           (deliver_domain = sender_helo_name,  /* set $domain */
-            match_isinlist(sender_helo_name, CUSS &helo_lookup_domains, 0,
+      if (  !sender_host_name
+         && (deliver_domain = sender_helo_name,  /* set $domain */
+             match_isinlist(sender_helo_name, CUSS &helo_lookup_domains, 0,
               &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL)) == OK)
         (void)host_name_lookup();
 
@@ -4069,7 +4140,7 @@ while (done <= 0)
         }
       }
 
-#ifdef EXPERIMENTAL_SPF
+#ifdef SUPPORT_SPF
     /* set up SPF context */
     spf_init(sender_helo_name, sender_host_address);
 #endif
@@ -4082,7 +4153,11 @@ while (done <= 0)
                &user_msg, &log_msg)) != OK)
         {
         done = smtp_handle_acl_fail(ACL_WHERE_HELO, rc, user_msg, log_msg);
-        sender_helo_name = NULL;
+       if (sender_helo_name)
+         {
+         store_free(sender_helo_name);
+         sender_helo_name = NULL;
+         }
         host_build_sender_fullhost();  /* Rebuild */
         break;
         }
@@ -4113,15 +4188,13 @@ while (done <= 0)
         sender_ident ? sender_ident : US"",
         sender_ident ? US" at " : US"",
         sender_host_name ? sender_host_name : sender_helo_name);
-
-      ptr = Ustrlen(s);
-      size = ptr + 1;
+      g = string_cat(NULL, s);
 
       if (sender_host_address)
         {
-        s = string_catn(s, &size, &ptr, US" [", 2);
-        s = string_cat (s, &size, &ptr, sender_host_address);
-        s = string_catn(s, &size, &ptr, US"]", 1);
+        g = string_catn(g, US" [", 2);
+        g = string_cat (g, sender_host_address);
+        g = string_catn(g, US"]", 1);
         }
       }
 
@@ -4141,18 +4214,17 @@ while (done <= 0)
           "newlines: message truncated: %s", string_printing(s));
         *ss = 0;
         }
-      ptr = Ustrlen(s);
-      size = ptr + 1;
+      g = string_cat(NULL, s);
       }
 
-    s = string_catn(s, &size, &ptr, US"\r\n", 2);
+    g = string_catn(g, US"\r\n", 2);
 
     /* If we received EHLO, we must create a multiline response which includes
     the functions supported. */
 
     if (esmtp)
       {
-      s[3] = '-';
+      g->s[3] = '-';
 
       /* 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
@@ -4164,12 +4236,12 @@ while (done <= 0)
         {
         sprintf(CS big_buffer, "%.3s-SIZE %d\r\n", smtp_code,
           thismessage_size_limit);
-        s = string_cat(s, &size, &ptr, big_buffer);
+        g = string_cat(g, big_buffer);
         }
       else
         {
-        s = string_catn(s, &size, &ptr, smtp_code, 3);
-        s = string_catn(s, &size, &ptr, US"-SIZE\r\n", 7);
+        g = string_catn(g, smtp_code, 3);
+        g = string_catn(g, US"-SIZE\r\n", 7);
         }
 
       /* Exim does not do protocol conversion or data conversion. It is 8-bit
@@ -4181,15 +4253,15 @@ while (done <= 0)
 
       if (accept_8bitmime)
         {
-        s = string_catn(s, &size, &ptr, smtp_code, 3);
-        s = string_catn(s, &size, &ptr, US"-8BITMIME\r\n", 11);
+        g = string_catn(g, smtp_code, 3);
+        g = string_catn(g, US"-8BITMIME\r\n", 11);
         }
 
       /* Advertise DSN support if configured to do so. */
       if (verify_check_host(&dsn_advertise_hosts) != FAIL)
         {
-        s = string_catn(s, &size, &ptr, smtp_code, 3);
-        s = string_catn(s, &size, &ptr, US"-DSN\r\n", 6);
+        g = string_catn(g, smtp_code, 3);
+        g = string_catn(g, US"-DSN\r\n", 6);
         dsn_advertised = TRUE;
         }
 
@@ -4198,18 +4270,18 @@ while (done <= 0)
 
       if (acl_smtp_etrn)
         {
-        s = string_catn(s, &size, &ptr, smtp_code, 3);
-        s = string_catn(s, &size, &ptr, US"-ETRN\r\n", 7);
+        g = string_catn(g, smtp_code, 3);
+        g = string_catn(g, US"-ETRN\r\n", 7);
         }
       if (acl_smtp_vrfy)
         {
-        s = string_catn(s, &size, &ptr, smtp_code, 3);
-        s = string_catn(s, &size, &ptr, US"-VRFY\r\n", 7);
+        g = string_catn(g, smtp_code, 3);
+        g = string_catn(g, US"-VRFY\r\n", 7);
         }
       if (acl_smtp_expn)
         {
-        s = string_catn(s, &size, &ptr, smtp_code, 3);
-        s = string_catn(s, &size, &ptr, US"-EXPN\r\n", 7);
+        g = string_catn(g, smtp_code, 3);
+        g = string_catn(g, US"-EXPN\r\n", 7);
         }
 
       /* Exim is quite happy with pipelining, so let the other end know that
@@ -4218,8 +4290,8 @@ while (done <= 0)
       if (pipelining_enable &&
           verify_check_host(&pipelining_advertise_hosts) == OK)
         {
-        s = string_catn(s, &size, &ptr, smtp_code, 3);
-        s = string_catn(s, &size, &ptr, US"-PIPELINING\r\n", 13);
+        g = string_catn(g, smtp_code, 3);
+        g = string_catn(g, US"-PIPELINING\r\n", 13);
         sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
         pipelining_advertised = TRUE;
         }
@@ -4260,29 +4332,29 @@ while (done <= 0)
              int saveptr;
              if (first)
                {
-               s = string_catn(s, &size, &ptr, smtp_code, 3);
-               s = string_catn(s, &size, &ptr, US"-AUTH", 5);
+               g = string_catn(g, smtp_code, 3);
+               g = string_catn(g, US"-AUTH", 5);
                first = FALSE;
                auth_advertised = TRUE;
                }
-             saveptr = ptr;
-             s = string_catn(s, &size, &ptr, US" ", 1);
-             s = string_cat (s, &size, &ptr, au->public_name);
-             while (++saveptr < ptr) s[saveptr] = toupper(s[saveptr]);
+             saveptr = g->ptr;
+             g = string_catn(g, US" ", 1);
+             g = string_cat (g, au->public_name);
+             while (++saveptr < g->ptr) g->s[saveptr] = toupper(g->s[saveptr]);
              au->advertised = TRUE;
              }
            }
          }
 
-       if (!first) s = string_catn(s, &size, &ptr, US"\r\n", 2);
+       if (!first) g = string_catn(g, US"\r\n", 2);
        }
 
       /* RFC 3030 CHUNKING */
 
       if (verify_check_host(&chunking_advertise_hosts) != FAIL)
         {
-        s = string_catn(s, &size, &ptr, smtp_code, 3);
-        s = string_catn(s, &size, &ptr, US"-CHUNKING\r\n", 11);
+        g = string_catn(g, smtp_code, 3);
+        g = string_catn(g, US"-CHUNKING\r\n", 11);
        chunking_offered = TRUE;
        chunking_state = CHUNKING_OFFERED;
         }
@@ -4296,8 +4368,8 @@ while (done <= 0)
       if (tls_in.active < 0 &&
           verify_check_host(&tls_advertise_hosts) != FAIL)
         {
-        s = string_catn(s, &size, &ptr, smtp_code, 3);
-        s = string_catn(s, &size, &ptr, US"-STARTTLS\r\n", 11);
+        g = string_catn(g, smtp_code, 3);
+        g = string_catn(g, US"-STARTTLS\r\n", 11);
         tls_advertised = TRUE;
         }
 #endif
@@ -4306,8 +4378,8 @@ while (done <= 0)
       /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */
       if (prdr_enable)
         {
-        s = string_catn(s, &size, &ptr, smtp_code, 3);
-        s = string_catn(s, &size, &ptr, US"-PRDR\r\n", 7);
+        g = string_catn(g, smtp_code, 3);
+        g = string_catn(g, US"-PRDR\r\n", 7);
        }
 #endif
 
@@ -4315,36 +4387,36 @@ while (done <= 0)
       if (  accept_8bitmime
          && verify_check_host(&smtputf8_advertise_hosts) != FAIL)
        {
-        s = string_catn(s, &size, &ptr, smtp_code, 3);
-        s = string_catn(s, &size, &ptr, US"-SMTPUTF8\r\n", 11);
+        g = string_catn(g, smtp_code, 3);
+        g = string_catn(g, US"-SMTPUTF8\r\n", 11);
         smtputf8_advertised = TRUE;
        }
 #endif
 
       /* Finish off the multiline reply with one that is always available. */
 
-      s = string_catn(s, &size, &ptr, smtp_code, 3);
-      s = string_catn(s, &size, &ptr, US" HELP\r\n", 7);
+      g = string_catn(g, smtp_code, 3);
+      g = string_catn(g, US" HELP\r\n", 7);
       }
 
     /* Terminate the string (for debug), write it, and note that HELO/EHLO
     has been seen. */
 
-    s[ptr] = 0;
-
 #ifdef SUPPORT_TLS
-    if (tls_in.active >= 0) (void)tls_write(TRUE, s, ptr, FALSE); else
+    if (tls_in.active >= 0) (void)tls_write(TRUE, g->s, g->ptr, FALSE); else
 #endif
 
       {
-      int i = fwrite(s, 1, ptr, smtp_out); i = i; /* compiler quietening */
+      int i = fwrite(g->s, 1, g->ptr, smtp_out); i = i; /* compiler quietening */
       }
     DEBUG(D_receive)
       {
       uschar *cr;
-      while ((cr = Ustrchr(s, '\r')) != NULL)   /* lose CRs */
-        memmove(cr, cr + 1, (ptr--) - (cr - s));
-      debug_printf("SMTP>> %s", s);
+
+      (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);
       }
     helo_seen = TRUE;
 
@@ -4542,10 +4614,10 @@ while (done <= 0)
                 US"invalid data for AUTH");
               goto COMMAND_LOOP;
               }
-            if (acl_smtp_mailauth == NULL)
+            if (!acl_smtp_mailauth)
               {
               ignore_msg = US"client not authenticated";
-              rc = (sender_host_authenticated != NULL)? OK : FAIL;
+              rc = sender_host_authenticated ? OK : FAIL;
               }
             else
               {
@@ -5079,17 +5151,24 @@ 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;
 
+      dot_ends = FALSE;
+
       goto DATA_BDAT;
       }
 
     case DATA_CMD:
     HAD(SCH_DATA);
+    dot_ends = TRUE;
 
     DATA_BDAT:         /* Common code for DATA and BDAT */
     if (!discarded && recipients_count <= 0)
@@ -5319,7 +5398,7 @@ while (done <= 0)
       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;
-      if (sender_helo_name != NULL)
+      if (sender_helo_name)
         {
         store_free(sender_helo_name);
         sender_helo_name = NULL;
@@ -5335,7 +5414,7 @@ while (done <= 0)
          + (tls_in.active >= 0 ? pcrpted : 0)
          ];
 
-      sender_host_authenticated = NULL;
+      sender_host_auth_pubname = sender_host_authenticated = NULL;
       authenticated_id = NULL;
       sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
       DEBUG(D_tls) debug_printf("TLS active\n");
@@ -5392,7 +5471,7 @@ while (done <= 0)
        smtp_printf("554 Security failure\r\n", FALSE);
        break;
       }
-    tls_close(TRUE, TRUE);
+    tls_close(TRUE, TLS_SHUTDOWN_NOWAIT);
     break;
     #endif
 
@@ -5457,15 +5536,22 @@ while (done <= 0)
     just drop the call rather than sending QUIT, and it clutters up the logs.
     */
 
-    if (sender_address != NULL || recipients_count > 0)
-      log_write(L_lost_incoming_connection,
-          LOG_MAIN,
-          "unexpected %s while reading SMTP command from %s%s",
-          sender_host_unknown? "EOF" : "disconnection",
-          host_and_ident(FALSE), smtp_read_error);
+    if (sender_address || recipients_count > 0)
+      log_write(L_lost_incoming_connection, LOG_MAIN,
+       "unexpected %s while reading SMTP command from %s%s%s D=%s",
+       sender_host_unknown ? "EOF" : "disconnection",
+       tcp_in_fastopen && !tcp_in_fastopen_logged ? US"TFO " : US"",
+       host_and_ident(FALSE), smtp_read_error,
+       string_timesince(&smtp_connection_start)
+       );
 
-    else log_write(L_smtp_connection, LOG_MAIN, "%s lost%s",
-      smtp_get_connection_info(), smtp_read_error);
+    else
+      log_write(L_smtp_connection, LOG_MAIN, "%s %slost%s D=%s",
+        smtp_get_connection_info(),
+       tcp_in_fastopen && !tcp_in_fastopen_logged ? US"TFO " : US"",
+       smtp_read_error,
+       string_timesince(&smtp_connection_start)
+       );
 
     done = 1;
     break;
@@ -5473,7 +5559,7 @@ while (done <= 0)
 
     case ETRN_CMD:
     HAD(SCH_ETRN);
-    if (sender_address != NULL)
+    if (sender_address)
       {
       done = synprot_error(L_smtp_protocol_error, 503, NULL,
         US"ETRN is not permitted inside a transaction");
@@ -5499,7 +5585,7 @@ while (done <= 0)
     since that is strictly the only kind of ETRN that can be implemented
     according to the RFC. */
 
-    if (smtp_etrn_command != NULL)
+    if (smtp_etrn_command)
       {
       uschar *error;
       BOOL rc;
@@ -5640,8 +5726,8 @@ while (done <= 0)
 
     case BADCHAR_CMD:
     done = synprot_error(L_smtp_syntax_error, 0, NULL,       /* Just logs */
-      US"NULL character(s) present (shown as '?')");
-    smtp_printf("501 NULL characters are not allowed in SMTP commands\r\n", FALSE);
+      US"NUL character(s) present (shown as '?')");
+    smtp_printf("501 NUL characters are not allowed in SMTP commands\r\n", FALSE);
     break;
 
 
@@ -5650,7 +5736,7 @@ while (done <= 0)
     if (smtp_inend >= smtp_inbuffer + IN_BUFFER_SIZE)
       smtp_inend = smtp_inbuffer + IN_BUFFER_SIZE - 1;
     c = smtp_inend - smtp_inptr;
-    if (c > 150) c = 150;
+    if (c > 150) c = 150;      /* limit logged amount */
     smtp_inptr[c] = 0;
     incomplete_transaction_log(US"sync failure");
     log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
@@ -5715,6 +5801,30 @@ while (done <= 0)
 return done - 2;  /* Convert yield values */
 }
 
+
+
+gstring *
+authres_smtpauth(gstring * g)
+{
+if (!sender_host_authenticated)
+  return g;
+
+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);
+else
+  g = string_catn(g, US") reason=x509.auth", 17);
+
+if (authenticated_sender)
+  g = string_append(g, 2, US" smtp.mailfrom=", authenticated_sender);
+return g;
+}
+
+
+
 /* vi: aw ai sw=2
 */
 /* End of smtp_in.c */