TLS: Fix excessive calling of smtp_auth_acl under AUTH_TLS. Bug 2203
[exim.git] / src / src / smtp_in.c
index 01c12caf6f7945f0ce570b439e28c79c758ebeef..1f1e7ddd4ac9e9964068f8cb3100c1c3f72b772b 100644 (file)
@@ -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. */
@@ -314,10 +314,10 @@ static void smtp_rset_handler(void);
 *************************************************/
 
 /* Synchronization checks can never be perfect because a packet may be on its
-way but not arrived when the check is done. Such checks can in any case only be
-done when TLS is not in use. Normally, the checks happen when commands are
-read: Exim ensures that there is no more input in the input buffer. In normal
-cases, the response to the command will be fast, and there is no further check.
+way but not arrived when the check is done.  Normally, the checks happen when
+commands are read: Exim ensures that there is no more input in the input buffer.
+In normal cases, the response to the command will be fast, and there is no
+further check.
 
 However, for some commands an ACL is run, and that can include delays. In those
 cases, it is useful to do another check on the input just before sending the
@@ -333,15 +333,19 @@ Returns:   TRUE if all is well; FALSE if there is input pending
 */
 
 static BOOL
-check_sync(void)
+wouldblock_reading(void)
 {
 int fd, rc;
 fd_set fds;
 struct timeval tzero;
 
-if (!smtp_enforce_sync || sender_host_address == NULL ||
-    sender_host_notsocket || tls_in.active >= 0)
-  return TRUE;
+#ifdef SUPPORT_TLS
+if (tls_in.active >= 0)
+ return !tls_could_read();
+#endif
+
+if (smtp_inptr < smtp_inend)
+  return FALSE;
 
 fd = fileno(smtp_in);
 FD_ZERO(&fds);
@@ -361,6 +365,29 @@ smtp_inptr[rc] = 0;
 return FALSE;
 }
 
+static BOOL
+check_sync(void)
+{
+if (!smtp_enforce_sync || sender_host_address == NULL || sender_host_notsocket)
+  return TRUE;
+
+return wouldblock_reading();
+}
+
+
+/* If there's input waiting (and we're doing pipelineing) then we can pipeline
+a reponse with the one following. */
+
+static BOOL
+pipeline_response(void)
+{
+if (  !smtp_enforce_sync || !sender_host_address
+   || sender_host_notsocket || !pipelining_advertised)
+  return FALSE;
+
+return !wouldblock_reading();
+}
+
 
 
 /*************************************************
@@ -532,14 +559,18 @@ for(;;)
 
   if (!pipelining_advertised && !check_sync())
     {
+    unsigned n = smtp_inend - smtp_inptr;
+    if (n > 32) n = 32;
+
     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(smtp_inptr));
-      (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;
     }
 
@@ -554,7 +585,7 @@ for(;;)
     return EOD;
     }
 
-  smtp_printf("250 %u byte chunk received\r\n", chunking_datasize);
+  smtp_printf("250 %u byte chunk received\r\n", FALSE, chunking_datasize);
   chunking_state = CHUNKING_OFFERED;
   DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
 
@@ -592,7 +623,7 @@ next_cmd:
 
     case NOOP_CMD:
       HAD(SCH_NOOP);
-      smtp_printf("250 OK\r\n");
+      smtp_printf("250 OK\r\n", FALSE);
       goto next_cmd;
 
     case BDAT_CMD:
@@ -650,8 +681,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;
+  (void) bdat_getbuf(&n);
+}
 
 receive_getc = lwr_receive_getc;
 receive_getbuf = lwr_receive_getbuf;
@@ -768,18 +802,19 @@ they are also picked up later by smtp_fflush().
 
 Arguments:
   format      format string
+  more       further data expected
   ...         optional arguments
 
 Returns:      nothing
 */
 
 void
-smtp_printf(const char *format, ...)
+smtp_printf(const char *format, BOOL more, ...)
 {
 va_list ap;
 
-va_start(ap, format);
-smtp_vprintf(format, ap);
+va_start(ap, more);
+smtp_vprintf(format, more, ap);
 va_end(ap);
 }
 
@@ -788,7 +823,7 @@ smtp_printf(), bearing in mind that in C a vararg function can't directly
 call another vararg function, only a function which accepts a va_list. */
 
 void
-smtp_vprintf(const char *format, va_list ap)
+smtp_vprintf(const char *format, BOOL more, va_list ap)
 {
 BOOL yield;
 
@@ -810,7 +845,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
@@ -834,7 +869,7 @@ if (rcpt_in_progress)
 #ifdef SUPPORT_TLS
 if (tls_in.active >= 0)
   {
-  if (tls_write(TRUE, big_buffer, Ustrlen(big_buffer)) < 0)
+  if (tls_write(TRUE, big_buffer, Ustrlen(big_buffer), more) < 0)
     smtp_write_error = -1;
   }
 else
@@ -890,7 +925,7 @@ 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);
+exim_exit(EXIT_FAILURE, US"receiving");
 }
 
 
@@ -914,7 +949,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);
+exim_exit(EXIT_FAILURE, US"receiving");
 }
 
 
@@ -1613,7 +1648,7 @@ smtp_closedown(uschar *message)
 {
 if (smtp_in == NULL || smtp_batched_input) return;
 receive_swallow_smtp();
-smtp_printf("421 %s\r\n", message);
+smtp_printf("421 %s\r\n", FALSE, message);
 
 for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
   {
@@ -1621,16 +1656,16 @@ for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
   return;
 
   case QUIT_CMD:
-  smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
+  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");
+  smtp_printf("250 Reset OK\r\n", FALSE);
   break;
 
   default:
-  smtp_printf("421 %s\r\n", message);
+  smtp_printf("421 %s\r\n", FALSE, message);
   break;
   }
 }
@@ -1679,37 +1714,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
 
@@ -1728,53 +1748,69 @@ 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)
   {
-  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 != NULL) s[ptr] = 0; else s = US"";
-log_write(0, LOG_MAIN, "no MAIL in SMTP connection from %s D=%s%s",
-  host_and_ident(FALSE),
-  readconf_printtime( (int) ((long)time(NULL) - (long)smtp_connection_start)),
-  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);
+}
+
+
+/* Return list of recent smtp commands */
+
+uschar *
+smtp_cmd_hist(void)
+{
+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, ',', smtp_names[smtp_connection_had[i]]);
+
+for (i = 0; i < smtp_ch_index; i++)
+  list = string_append_listele(list, ',', smtp_names[smtp_connection_had[i]]);
+
+s = string_from_gstring(list);
+return s ? s : US"";
 }
 
 
 
+
 /*************************************************
 *   Check HELO line and set sender_helo_name     *
 *************************************************/
@@ -1892,17 +1928,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;
@@ -2282,6 +2318,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                 *
 *************************************************/
@@ -2298,13 +2356,13 @@ 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;
 
-smtp_connection_start = time(NULL);
+gettimeofday(&smtp_connection_start, NULL);
 for (smtp_ch_index = 0; smtp_ch_index < SMTP_HBUFF_SIZE; smtp_ch_index++)
   smtp_connection_had[smtp_ch_index] = SCH_NONE;
 smtp_ch_index = 0;
@@ -2371,7 +2429,7 @@ else
 /* Set up the buffer for inputting using direct read() calls, and arrange to
 call the local functions instead of the standard C ones. */
 
-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");
 
 receive_getc = smtp_getc;
@@ -2477,14 +2535,14 @@ 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)
         {
         log_write(0, LOG_MAIN, "getsockopt() failed from %s: %s",
           host_and_ident(FALSE), strerror(errno));
-        smtp_printf("451 SMTP service not available\r\n");
+        smtp_printf("451 SMTP service not available\r\n", FALSE);
         return FALSE;
         }
       }
@@ -2503,11 +2561,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");
@@ -2516,7 +2574,7 @@ if (!sender_host_unknown)
       p += Ustrlen(p);
 
       for (opt = optstart; opt != NULL &&
-           opt < (uschar *)(ipopt) + optlen;)
+           opt < US (ipopt) + optlen;)
         {
         switch (*opt)
           {
@@ -2570,10 +2628,7 @@ if (!sender_host_unknown)
             Ustrcat(p, "[ ");
             p += 2;
             for (i = 0; i < opt[1]; i++)
-              {
-              sprintf(CS p, "%2.2x ", opt[i]);
-              p += 3;
-              }
+              p += sprintf(CS p, "%2.2x ", opt[i]);
             *p++ = ']';
             }
           opt += opt[1];
@@ -2589,7 +2644,7 @@ if (!sender_host_unknown)
       log_write(0, LOG_MAIN|LOG_REJECT,
         "connection from %s refused (IP options)", host_and_ident(FALSE));
 
-      smtp_printf("554 SMTP service not available\r\n");
+      smtp_printf("554 SMTP service not available\r\n", FALSE);
       return FALSE;
       }
 
@@ -2641,7 +2696,7 @@ if (!sender_host_unknown)
     {
     log_write(L_connection_reject, LOG_MAIN|LOG_REJECT, "refused connection "
       "from %s (host_reject_connection)", host_and_ident(FALSE));
-    smtp_printf("554 SMTP service not available\r\n");
+    smtp_printf("554 SMTP service not available\r\n", FALSE);
     return FALSE;
     }
 
@@ -2672,7 +2727,7 @@ if (!sender_host_unknown)
       log_write(L_connection_reject,
                 LOG_MAIN|LOG_REJECT, "refused connection from %s "
                 "(tcp wrappers)", host_and_ident(FALSE));
-      smtp_printf("554 SMTP service not available\r\n");
+      smtp_printf("554 SMTP service not available\r\n", FALSE);
       }
     else
       {
@@ -2682,7 +2737,7 @@ if (!sender_host_unknown)
       log_write(L_connection_reject,
                 LOG_MAIN|LOG_REJECT, "temporarily refused connection from %s "
                 "(tcp wrappers errno=%d)", host_and_ident(FALSE), save_errno);
-      smtp_printf("451 Temporary local problem - please try later\r\n");
+      smtp_printf("451 Temporary local problem - please try later\r\n", FALSE);
       }
     return FALSE;
     }
@@ -2702,7 +2757,7 @@ if (!sender_host_unknown)
         host_and_ident(FALSE), smtp_accept_count - 1, smtp_accept_max,
         smtp_accept_reserve, (rc == DEFER)? " (lookup deferred)" : "");
       smtp_printf("421 %s: Too many concurrent SMTP connections; "
-        "please try again later\r\n", smtp_active_hostname);
+        "please try again later\r\n", FALSE, smtp_active_hostname);
       return FALSE;
       }
     reserved_host = TRUE;
@@ -2723,7 +2778,7 @@ if (!sender_host_unknown)
       LOG_MAIN, "temporarily refused connection from %s: not in "
       "reserve list and load average = %.2f", host_and_ident(FALSE),
       (double)load_average/1000.0);
-    smtp_printf("421 %s: Too much load; please try again later\r\n",
+    smtp_printf("421 %s: Too much load; please try again later\r\n", FALSE,
       smtp_active_hostname);
     return FALSE;
     }
@@ -2771,8 +2826,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 */
@@ -2829,51 +2888,59 @@ 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);
+  ss = string_catn(ss, code, 3);
   if (linebreak == NULL)
     {
     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++;
   }
 while (*p != 0);
 
-ss[ptr] = 0;  /* string_cat leaves room for this */
-
 /* Before we write the banner, check that there is no input pending, unless
 this synchronisation check is disabled. */
 
 if (!check_sync())
   {
+  unsigned n = smtp_inend - smtp_inptr;
+  if (n > 32) n = 32;
+
   log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
     "synchronization error (input sent without waiting for greeting): "
     "rejected connection from %s input=\"%s\"", host_and_ident(TRUE),
-    string_printing(smtp_inptr));
-  smtp_printf("554 SMTP synchronization error\r\n");
+    string_printing(string_copyn(smtp_inptr, n)));
+  smtp_printf("554 SMTP synchronization error\r\n", FALSE);
   return FALSE;
   }
 
 /* Now output the banner */
 
-smtp_printf("%s", 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;
 }
 
@@ -2920,10 +2987,10 @@ if (++synprot_error_count > smtp_max_synprot_errors)
 
 if (code > 0)
   {
-  smtp_printf("%d%c%s%s%s\r\n", code, (yield == 1)? '-' : ' ',
-    (data == NULL)? US"" : data, (data == NULL)? US"" : US": ", errmess);
+  smtp_printf("%d%c%s%s%s\r\n", FALSE, code, yield == 1 ? '-' : ' ',
+    data ? data : US"", data ? US": " : US"", errmess);
   if (yield == 1)
-    smtp_printf("%d Too many syntax or protocol errors\r\n", code);
+    smtp_printf("%d Too many syntax or protocol errors\r\n", FALSE, code);
   }
 
 return yield;
@@ -2979,25 +3046,27 @@ if (rcpt_in_progress)
   rcpt_in_progress = FALSE;
   }
 
-/* Not output the message, splitting it up into multiple lines if necessary. */
+/* Now output the message, splitting it up into multiple lines if necessary.
+We only handle pipelining these responses as far as nonfinal/final groups,
+not the whole MAIL/RCPT/DATA response set. */
 
 for (;;)
   {
   uschar *nl = Ustrchr(msg, '\n');
   if (nl == NULL)
     {
-    smtp_printf("%.3s%c%.*s%s\r\n", code, final? ' ':'-', esclen, esc, msg);
+    smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg);
     return;
     }
   else if (nl[1] == 0 || no_multiline_responses)
     {
-    smtp_printf("%.3s%c%.*s%.*s\r\n", code, final? ' ':'-', esclen, esc,
+    smtp_printf("%.3s%c%.*s%.*s\r\n", !final, code, final ? ' ':'-', esclen, esc,
       (int)(nl - msg), msg);
     return;
     }
   else
     {
-    smtp_printf("%.3s-%.*s%.*s\r\n", code, esclen, esc, (int)(nl - msg), msg);
+    smtp_printf("%.3s-%.*s%.*s\r\n", TRUE, code, esclen, esc, (int)(nl - msg), msg);
     msg = nl + 1;
     while (isspace(*msg)) msg++;
     }
@@ -3241,7 +3310,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"";
@@ -3341,7 +3411,7 @@ if (code && defaultrespond)
     va_start(ap, defaultrespond);
     if (!string_vformat(buffer, sizeof(buffer), CS defaultrespond, ap))
       log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_notquit_exit()");
-    smtp_printf("%s %s\r\n", code, buffer);
+    smtp_printf("%s %s\r\n", FALSE, code, buffer);
     va_end(ap);
     }
   mac_smtp_fflush();
@@ -3633,7 +3703,7 @@ if (allow_unqualified_recipient || strcmpic(*recipient, US"postmaster") == 0)
   *recipient = rewrite_address_qualify(*recipient, TRUE);
   return rd;
   }
-smtp_printf("501 %s: recipient address must contain a domain\r\n",
+smtp_printf("501 %s: recipient address must contain a domain\r\n", FALSE,
   smtp_cmd_data);
 log_write(L_smtp_syntax_error,
   LOG_MAIN|LOG_REJECT, "unqualified %s rejected: <%s> %s%s",
@@ -3659,7 +3729,7 @@ if (acl_smtp_quit)
 if (*user_msgp)
   smtp_respond(US"221", 3, TRUE, *user_msgp);
 else
-  smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
+  smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
 
 #ifdef SUPPORT_TLS
 tls_close(TRUE, TRUE);
@@ -3675,7 +3745,7 @@ smtp_rset_handler(void)
 {
 HAD(SCH_RSET);
 incomplete_transaction_log(US"RSET");
-smtp_printf("250 Reset OK\r\n");
+smtp_printf("250 Reset OK\r\n", FALSE);
 cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
 }
 
@@ -3734,7 +3804,6 @@ 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 */
@@ -3764,11 +3833,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 */
@@ -3895,7 +3965,7 @@ while (done <= 0)
       {
       c = smtp_in_auth(au, &s, &ss);
 
-      smtp_printf("%s\r\n", s);
+      smtp_printf("%s\r\n", FALSE, s);
       if (c != OK)
        log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
          au->name, host_and_ident(FALSE), ss);
@@ -3942,7 +4012,7 @@ while (done <= 0)
 
     if (!check_helo(smtp_cmd_data))
       {
-      smtp_printf("501 Syntactically invalid %s argument(s)\r\n", hello);
+      smtp_printf("501 Syntactically invalid %s argument(s)\r\n", FALSE, hello);
 
       log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically "
         "invalid argument(s): %s", hello, host_and_ident(FALSE),
@@ -4006,7 +4076,7 @@ while (done <= 0)
           {
           if (helo_required)
             {
-            smtp_printf("%d %s argument does not match calling host\r\n",
+            smtp_printf("%d %s argument does not match calling host\r\n", FALSE,
               tempfail? 451 : 550, hello);
             log_write(0, LOG_MAIN|LOG_REJECT, "%srejected \"%s %s\" from %s",
               tempfail? "temporarily " : "",
@@ -4056,23 +4126,21 @@ while (done <= 0)
 #endif
 
     smtp_code = US"250 ";        /* Default response code plus space*/
-    if (user_msg == NULL)
+    if (!user_msg)
       {
       s = string_sprintf("%.3s %s Hello %s%s%s",
         smtp_code,
         smtp_active_hostname,
-        (sender_ident == NULL)?  US"" : sender_ident,
-        (sender_ident == NULL)?  US"" : US" at ",
-        (sender_host_name == NULL)? sender_helo_name : sender_host_name);
-
-      ptr = Ustrlen(s);
-      size = ptr + 1;
+        sender_ident ? sender_ident : US"",
+        sender_ident ? US" at " : US"",
+        sender_host_name ? sender_host_name : sender_helo_name);
+      g = string_cat(NULL, s);
 
-      if (sender_host_address != NULL)
+      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);
         }
       }
 
@@ -4092,18 +4160,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
@@ -4115,12 +4182,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
@@ -4132,15 +4199,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;
         }
 
@@ -4149,18 +4216,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
@@ -4169,8 +4236,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;
         }
@@ -4196,36 +4263,44 @@ while (done <= 0)
        auth_instance *au;
        BOOL first = TRUE;
        for (au = auths; au; au = au->next)
-         if (au->server && (au->advertise_condition == NULL ||
-             expand_check_condition(au->advertise_condition, au->name,
-             US"authenticator")))
+         {
+         au->advertised = FALSE;
+         if (au->server)
            {
-           int saveptr;
-           if (first)
+           DEBUG(D_auth+D_expand) debug_printf_indent(
+             "Evaluating advertise_condition for %s athenticator\n",
+             au->public_name);
+           if (  !au->advertise_condition
+              || expand_check_condition(au->advertise_condition, au->name,
+                     US"authenticator")
+              )
              {
-             s = string_catn(s, &size, &ptr, smtp_code, 3);
-             s = string_catn(s, &size, &ptr, US"-AUTH", 5);
-             first = FALSE;
-             auth_advertised = TRUE;
+             int saveptr;
+             if (first)
+               {
+               g = string_catn(g, smtp_code, 3);
+               g = string_catn(g, US"-AUTH", 5);
+               first = FALSE;
+               auth_advertised = TRUE;
+               }
+             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;
              }
-           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]);
-           au->advertised = TRUE;
            }
-         else
-           au->advertised = FALSE;
+         }
 
-       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;
         }
@@ -4239,8 +4314,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
@@ -4249,8 +4324,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
 
@@ -4258,36 +4333,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); 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;
 
@@ -4319,7 +4394,7 @@ while (done <= 0)
 
     if (helo_required && !helo_seen)
       {
-      smtp_printf("503 HELO or EHLO required\r\n");
+      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;
@@ -4345,7 +4420,7 @@ while (done <= 0)
     if (smtp_accept_max_per_connection > 0 &&
         smtp_mailcmd_count > smtp_accept_max_per_connection)
       {
-      smtp_printf("421 too many messages in this connection\r\n");
+      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 "
         "messages in one connection", host_and_ident(TRUE));
       break;
@@ -4610,7 +4685,7 @@ while (done <= 0)
 
     if (thismessage_size_limit > 0 && message_size > thismessage_size_limit)
       {
-      smtp_printf("552 Message size exceeds maximum permitted\r\n");
+      smtp_printf("552 Message size exceeds maximum permitted\r\n", FALSE);
       log_write(L_size_reject,
           LOG_MAIN|LOG_REJECT, "rejected MAIL FROM:<%s> %s: "
           "message too big: size%s=%d max=%d",
@@ -4635,7 +4710,7 @@ while (done <= 0)
          (smtp_check_spool_space && message_size >= 0)?
             message_size + 5000 : 0))
       {
-      smtp_printf("452 Space shortage, please try later\r\n");
+      smtp_printf("452 Space shortage, please try later\r\n", FALSE);
       sender_address = NULL;
       break;
       }
@@ -4657,7 +4732,7 @@ while (done <= 0)
         }
       else
         {
-        smtp_printf("501 %s: sender address must contain a domain\r\n",
+        smtp_printf("501 %s: sender address must contain a domain\r\n", FALSE,
           smtp_cmd_data);
         log_write(L_smtp_syntax_error,
           LOG_MAIN|LOG_REJECT,
@@ -4685,8 +4760,10 @@ while (done <= 0)
 
     if (rc == OK || rc == DISCARD)
       {
+      BOOL more = pipeline_response();
+
       if (!user_msg)
-        smtp_printf("%s%s%s", US"250 OK",
+        smtp_printf("%s%s%s", more, US"250 OK",
                   #ifndef DISABLE_PRDR
                     prdr_requested ? US", PRDR Requested" : US"",
                  #else
@@ -4732,7 +4809,7 @@ while (done <= 0)
       {
       if (pipelining_advertised && last_was_rej_mail)
         {
-        smtp_printf("503 sender not yet given\r\n");
+        smtp_printf("503 sender not yet given\r\n", FALSE);
         was_rej_mail = TRUE;
         }
       else
@@ -4880,7 +4957,7 @@ while (done <= 0)
       if (recipients_max_reject)
         {
         rcpt_fail_count++;
-        smtp_printf("552 too many recipients\r\n");
+        smtp_printf("552 too many recipients\r\n", FALSE);
         if (!toomany)
           log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: message "
             "rejected: sender=<%s> %s", sender_address, host_and_ident(TRUE));
@@ -4888,7 +4965,7 @@ while (done <= 0)
       else
         {
         rcpt_defer_count++;
-        smtp_printf("452 too many recipients\r\n");
+        smtp_printf("452 too many recipients\r\n", FALSE);
         if (!toomany)
           log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: excess "
             "temporarily rejected: sender=<%s> %s", sender_address,
@@ -4930,10 +5007,12 @@ while (done <= 0)
 
     if (rc == OK)
       {
+      BOOL more = pipeline_response();
+
       if (user_msg)
         smtp_user_msg(US"250", user_msg);
       else
-        smtp_printf("250 Accepted\r\n");
+        smtp_printf("250 Accepted\r\n", more);
       receive_add_recipient(recipient, -1);
 
       /* Set the dsn flags in the recipients_list */
@@ -4952,7 +5031,7 @@ while (done <= 0)
       if (user_msg)
         smtp_user_msg(US"250", user_msg);
       else
-        smtp_printf("250 Accepted\r\n");
+        smtp_printf("250 Accepted\r\n", FALSE);
       rcpt_fail_count++;
       discarded = TRUE;
       log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: "
@@ -5045,7 +5124,7 @@ while (done <= 0)
         smtp_respond(code, 3, FALSE, rcpt_smtp_response);
         }
       if (pipelining_advertised && last_was_rcpt)
-        smtp_printf("503 Valid RCPT command must precede %s\r\n",
+        smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE,
          smtp_names[smtp_connection_had[smtp_ch_index-1]]);
       else
         done = synprot_error(L_smtp_protocol_error, 503, NULL,
@@ -5062,7 +5141,7 @@ 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");
+      smtp_printf("554 Too many recipients\r\n", FALSE);
       break;
       }
 
@@ -5097,7 +5176,7 @@ while (done <= 0)
        smtp_user_msg(US"354", user_msg);
       else
        smtp_printf(
-         "354 Enter message, ending with \".\" on a line by itself\r\n");
+         "354 Enter message, ending with \".\" on a line by itself\r\n", FALSE);
       }
 
 #ifdef TCP_QUICKACK
@@ -5120,7 +5199,7 @@ while (done <= 0)
       if (!(address = parse_extract_address(smtp_cmd_data, &errmess,
             &start, &end, &recipient_domain, FALSE)))
        {
-       smtp_printf("501 %s\r\n", errmess);
+       smtp_printf("501 %s\r\n", FALSE, errmess);
        break;
        }
 
@@ -5159,7 +5238,7 @@ while (done <= 0)
            break;
          }
 
-       smtp_printf("%s\r\n", s);
+       smtp_printf("%s\r\n", FALSE, s);
        }
       break;
       }
@@ -5288,7 +5367,7 @@ while (done <= 0)
 
     if (rc == DEFER)
       {
-      smtp_printf("454 TLS currently unavailable\r\n");
+      smtp_printf("454 TLS currently unavailable\r\n", FALSE);
       break;
       }
 
@@ -5321,14 +5400,14 @@ while (done <= 0)
        if (user_msg)
          smtp_respond(US"221", 3, TRUE, user_msg);
        else
-         smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
+         smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
        log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
          smtp_get_connection_info());
        done = 2;
        break;
 
       default:
-       smtp_printf("554 Security failure\r\n");
+       smtp_printf("554 Security failure\r\n", FALSE);
        break;
       }
     tls_close(TRUE, TRUE);
@@ -5356,7 +5435,7 @@ while (done <= 0)
 
     case NOOP_CMD:
     HAD(SCH_NOOP);
-    smtp_printf("250 OK\r\n");
+    smtp_printf("250 OK\r\n", FALSE);
     break;
 
 
@@ -5367,7 +5446,7 @@ while (done <= 0)
 
     case HELP_CMD:
     HAD(SCH_HELP);
-    smtp_printf("214-Commands supported:\r\n");
+    smtp_printf("214-Commands supported:\r\n", TRUE);
       {
       uschar buffer[256];
       buffer[0] = 0;
@@ -5382,7 +5461,7 @@ while (done <= 0)
       if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN");
       if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN");
       if (acl_smtp_vrfy != NULL) Ustrcat(buffer, " VRFY");
-      smtp_printf("214%s\r\n", buffer);
+      smtp_printf("214%s\r\n", FALSE, buffer);
       }
     break;
 
@@ -5396,15 +5475,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;
@@ -5451,7 +5537,7 @@ while (done <= 0)
         {
         log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s",
           error);
-        smtp_printf("458 Internal failure\r\n");
+        smtp_printf("458 Internal failure\r\n", FALSE);
         break;
         }
       }
@@ -5482,7 +5568,7 @@ while (done <= 0)
         debug_printf("ETRN command is: %s\n", etrn_command);
         debug_printf("ETRN command execution skipped\n");
         }
-      if (user_msg == NULL) smtp_printf("250 OK\r\n");
+      if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
         else smtp_user_msg(US"250", user_msg);
       break;
       }
@@ -5493,7 +5579,7 @@ while (done <= 0)
 
     if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
       {
-      smtp_printf("458 Already processing %s\r\n", smtp_cmd_data);
+      smtp_printf("458 Already processing %s\r\n", FALSE, smtp_cmd_data);
       break;
       }
 
@@ -5556,12 +5642,12 @@ while (done <= 0)
       {
       log_write(0, LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s",
         strerror(errno));
-      smtp_printf("458 Unable to fork process\r\n");
+      smtp_printf("458 Unable to fork process\r\n", FALSE);
       if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
       }
     else
       {
-      if (user_msg == NULL) smtp_printf("250 OK\r\n");
+      if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
         else smtp_user_msg(US"250", user_msg);
       }
 
@@ -5580,7 +5666,7 @@ 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");
+    smtp_printf("501 NULL characters are not allowed in SMTP commands\r\n", FALSE);
     break;
 
 
@@ -5617,7 +5703,7 @@ while (done <= 0)
 
 #ifdef SUPPORT_PROXY
     case PROXY_FAIL_IGNORE_CMD:
-    smtp_printf("503 Command refused, required Proxy negotiation failed\r\n");
+    smtp_printf("503 Command refused, required Proxy negotiation failed\r\n", FALSE);
     break;
 #endif