Remove unwanted (int) case when reading SIZE. Fix runtest for 64-bit
[exim.git] / src / src / smtp_in.c
index 7e80c6209afd643bcae18a84c27a2b729ffea838..ca198af2932ac975bdea4240f69b8276c1237f7c 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/smtp_in.c,v 1.49 2007/01/08 10:50:18 ph10 Exp $ */
+/* $Cambridge: exim/src/src/smtp_in.c,v 1.59 2007/07/04 10:37:03 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -96,6 +96,13 @@ enum {
   TOO_MANY_NONMAIL_CMD };
 
 
   TOO_MANY_NONMAIL_CMD };
 
 
+/* This is a convenience macro for adding the identity of an SMTP command
+to the circular buffer that holds a list of the last n received. */
+
+#define HAD(n) \
+    smtp_connection_had[smtp_ch_index++] = n; \
+    if (smtp_ch_index >= SMTP_HBUFF_SIZE) smtp_ch_index = 0
+
 
 /*************************************************
 *                Local static variables          *
 
 /*************************************************
 *                Local static variables          *
@@ -113,12 +120,18 @@ static BOOL helo_seen;
 static BOOL helo_accept_junk;
 static BOOL count_nonmail;
 static BOOL pipelining_advertised;
 static BOOL helo_accept_junk;
 static BOOL count_nonmail;
 static BOOL pipelining_advertised;
+static BOOL rcpt_smtp_response_same;
+static BOOL rcpt_in_progress;
 static int  nonmail_command_count;
 static int  synprot_error_count;
 static int  unknown_command_count;
 static int  sync_cmd_limit;
 static int  smtp_write_error = 0;
 
 static int  nonmail_command_count;
 static int  synprot_error_count;
 static int  unknown_command_count;
 static int  sync_cmd_limit;
 static int  smtp_write_error = 0;
 
+static uschar *rcpt_smtp_response;
+static uschar *smtp_data_buffer;
+static uschar *smtp_cmd_data;
+
 /* We need to know the position of RSET, HELO, EHLO, AUTH, and STARTTLS. Their
 final fields of all except AUTH are forced TRUE at the start of a new message
 setup, to allow one of each between messages that is not counted as a nonmail
 /* We need to know the position of RSET, HELO, EHLO, AUTH, and STARTTLS. Their
 final fields of all except AUTH are forced TRUE at the start of a new message
 setup, to allow one of each between messages that is not counted as a nonmail
@@ -165,6 +178,15 @@ static smtp_cmd_list *cmd_list_end =
 #define CMD_LIST_AUTH      3
 #define CMD_LIST_STARTTLS  4
 
 #define CMD_LIST_AUTH      3
 #define CMD_LIST_STARTTLS  4
 
+/* This list of names is used for performing the smtp_no_mail logging action.
+It must be kept in step with the SCH_xxx enumerations. */
+
+static uschar *smtp_names[] =
+  {
+  US"NONE", US"AUTH", US"DATA", US"EHLO", US"ETRN", US"EXPN", US"HELO",
+  US"HELP", US"MAIL", US"NOOP", US"QUIT", US"RCPT", US"RSET", US"STARTTLS",
+  US"VRFY" };
+
 static uschar *protocols[] = {
   US"local-smtp",        /* HELO */
   US"local-smtps",       /* The rare case EHLO->STARTTLS->HELO */
 static uschar *protocols[] = {
   US"local-smtp",        /* HELO */
   US"local-smtps",       /* The rare case EHLO->STARTTLS->HELO */
@@ -311,6 +333,23 @@ return smtp_had_error;
 
 
 
 
 
 
+/*************************************************
+*      Test for characters in the SMTP buffer    *
+*************************************************/
+
+/* Used at the end of a message
+
+Arguments:     none
+Returns:       TRUE/FALSE
+*/
+
+BOOL
+smtp_buffered(void)
+{
+return smtp_inptr < smtp_inend;
+}
+
+
 
 /*************************************************
 *     Write formatted string to SMTP channel     *
 
 /*************************************************
 *     Write formatted string to SMTP channel     *
@@ -349,28 +388,41 @@ DEBUG(D_receive)
   }
 
 va_start(ap, format);
   }
 
 va_start(ap, format);
+if (!string_vformat(big_buffer, big_buffer_size, format, ap))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()");
+  smtp_closedown(US"Unexpected error");
+  exim_exit(EXIT_FAILURE);
+  }
+va_end(ap);
 
 
-/* If in a TLS session we have to format the string, and then write it using a
-TLS function. */
+/* If this is the first output for a (non-batch) RCPT command, see if all RCPTs
+have had the same. Note: this code is also present in smtp_respond(). It would
+be tidier to have it only in one place, but when it was added, it was easier to
+do it that way, so as not to have to mess with the code for the RCPT command,
+which sometimes uses smtp_printf() and sometimes smtp_respond(). */
+
+if (rcpt_in_progress)
+  {
+  if (rcpt_smtp_response == NULL)
+    rcpt_smtp_response = string_copy(big_buffer);
+  else if (rcpt_smtp_response_same &&
+           Ustrcmp(rcpt_smtp_response, big_buffer) != 0)
+    rcpt_smtp_response_same = FALSE;
+  rcpt_in_progress = FALSE;
+  }
+
+/* Now write the string */
 
 #ifdef SUPPORT_TLS
 if (tls_active >= 0)
   {
 
 #ifdef SUPPORT_TLS
 if (tls_active >= 0)
   {
-  if (!string_vformat(big_buffer, big_buffer_size, format, ap))
-    {
-    log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf");
-    smtp_closedown(US"Unexpected error");
-    exim_exit(EXIT_FAILURE);
-    }
   if (tls_write(big_buffer, Ustrlen(big_buffer)) < 0) smtp_write_error = -1;
   }
 else
 #endif
 
   if (tls_write(big_buffer, Ustrlen(big_buffer)) < 0) smtp_write_error = -1;
   }
 else
 #endif
 
-/* Otherwise, just use the standard library function. */
-
-if (vfprintf(smtp_out, format, ap) < 0) smtp_write_error = -1;
-va_end(ap);
+if (fprintf(smtp_out, "%s", big_buffer) < 0) smtp_write_error = -1;
 }
 
 
 }
 
 
@@ -450,6 +502,7 @@ exim_exit(EXIT_FAILURE);
 
 
 
 
 
 
+
 /*************************************************
 *           Read one command line                *
 *************************************************/
 /*************************************************
 *           Read one command line                *
 *************************************************/
@@ -536,11 +589,16 @@ for (p = cmd_list; p < cmd_list_end; p++)
         !sender_host_notsocket)                        /* Really is a socket */
       return BADSYN_CMD;
 
         !sender_host_notsocket)                        /* Really is a socket */
       return BADSYN_CMD;
 
-    /* Point after the command, but don't skip over leading spaces till after
-    the following test, so that if it fails, the command name can easily be
-    logged. */
+    /* The variables $smtp_command and $smtp_command_argument point into the
+    unmodified input buffer. A copy of the latter is taken for actual
+    processing, so that it can be chopped up into separate parts if necessary,
+    for example, when processing a MAIL command options such as SIZE that can
+    follow the sender address. */
 
     smtp_cmd_argument = smtp_cmd_buffer + p->len;
 
     smtp_cmd_argument = smtp_cmd_buffer + p->len;
+    while (isspace(*smtp_cmd_argument)) smtp_cmd_argument++;
+    Ustrcpy(smtp_data_buffer, smtp_cmd_argument);
+    smtp_cmd_data = smtp_data_buffer;
 
     /* Count non-mail commands from those hosts that are controlled in this
     way. The default is all hosts. We don't waste effort checking the list
 
     /* Count non-mail commands from those hosts that are controlled in this
     way. The default is all hosts. We don't waste effort checking the list
@@ -558,11 +616,10 @@ for (p = cmd_list; p < cmd_list_end; p++)
         return TOO_MANY_NONMAIL_CMD;
       }
 
         return TOO_MANY_NONMAIL_CMD;
       }
 
-    /* Get the data pointer over leading spaces and return; if there is data
-    for a command that does not expect it, give the error centrally here. */
+    /* If there is data for a command that does not expect it, generate the
+    error here. */
 
 
-    while (isspace(*smtp_cmd_argument)) smtp_cmd_argument++;
-    return (p->has_arg || *smtp_cmd_argument == 0)? p->cmd : BADARG_CMD;
+    return (p->has_arg || *smtp_cmd_data == 0)? p->cmd : BADARG_CMD;
     }
   }
 
     }
   }
 
@@ -580,6 +637,60 @@ return OTHER_CMD;
 
 
 
 
 
 
+/*************************************************
+*          Recheck synchronization               *
+*************************************************/
+
+/* 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.
+
+However, for some commands an ACL is run, and that can include delays. In those
+cases, it is useful to do another check on the input just before sending the
+response. This also applies at the start of a connection. This function does
+that check by means of the select() function, as long as the facility is not
+disabled or inappropriate. A failure of select() is ignored.
+
+When there is unwanted input, we read it so that it appears in the log of the
+error.
+
+Arguments: none
+Returns:   TRUE if all is well; FALSE if there is input pending
+*/
+
+static BOOL
+check_sync(void)
+{
+int fd, rc;
+fd_set fds;
+struct timeval tzero;
+
+if (!smtp_enforce_sync || sender_host_address == NULL ||
+    sender_host_notsocket || tls_active >= 0)
+  return TRUE;
+
+fd = fileno(smtp_in);
+FD_ZERO(&fds);
+FD_SET(fd, &fds);
+tzero.tv_sec = 0;
+tzero.tv_usec = 0;
+rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero);
+
+if (rc <= 0) return TRUE;     /* Not ready to read */
+rc = smtp_getc();
+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;
+}
+
+
+
 /*************************************************
 *          Forced closedown of call              *
 *************************************************/
 /*************************************************
 *          Forced closedown of call              *
 *************************************************/
@@ -666,6 +777,74 @@ return string_sprintf("SMTP connection from %s", hostname);
 
 
 
 
 
 
+/*************************************************
+*      Log lack of MAIL if so configured         *
+*************************************************/
+
+/* This function is called when an SMTP session ends. If the log selector
+smtp_no_mail is set, write a log line giving some details of what has happened
+in the SMTP session.
+
+Arguments:   none
+Returns:     nothing
+*/
+
+void
+smtp_log_no_mail(void)
+{
+int size, ptr, i;
+uschar *s, *sep;
+
+if (smtp_mailcmd_count > 0 || (log_extra_selector & LX_smtp_no_mail) == 0)
+  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);
+  }
+
+#ifdef SUPPORT_TLS
+if ((log_extra_selector & LX_tls_cipher) != 0 && tls_cipher != NULL)
+  s = string_append(s, &size, &ptr, 2, US" X=", tls_cipher);
+if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
+     tls_cipher != NULL)
+  s = string_append(s, &size, &ptr, 2, US" CV=",
+    tls_certificate_verified? "yes":"no");
+if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL)
+  s = string_append(s, &size, &ptr, 3, US" DN=\"", tls_peerdn, US"\"");
+#endif
+
+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]]);
+    sep = US",";
+    }
+  }
+
+for (i = 0; i < smtp_ch_index; i++)
+  {
+  s = string_append(s, &size, &ptr, 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(time(NULL) - smtp_connection_start), s);
+}
+
+
+
 /*************************************************
 *   Check HELO line and set sender_helo_name     *
 *************************************************/
 /*************************************************
 *   Check HELO line and set sender_helo_name     *
 *************************************************/
@@ -755,7 +934,7 @@ return yield;
 *         Extract SMTP command option            *
 *************************************************/
 
 *         Extract SMTP command option            *
 *************************************************/
 
-/* This function picks the next option setting off the end of smtp_cmd_argument. It
+/* This function picks the next option setting off the end of smtp_cmd_data. It
 is called for MAIL FROM and RCPT TO commands, to pick off the optional ESMTP
 things that can appear there.
 
 is called for MAIL FROM and RCPT TO commands, to pick off the optional ESMTP
 things that can appear there.
 
@@ -770,11 +949,11 @@ static BOOL
 extract_option(uschar **name, uschar **value)
 {
 uschar *n;
 extract_option(uschar **name, uschar **value)
 {
 uschar *n;
-uschar *v = smtp_cmd_argument + Ustrlen(smtp_cmd_argument) -1;
+uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
 while (isspace(*v)) v--;
 v[1] = 0;
 
 while (isspace(*v)) v--;
 v[1] = 0;
 
-while (v > smtp_cmd_argument && *v != '=' && !isspace(*v)) v--;
+while (v > smtp_cmd_data && *v != '=' && !isspace(*v)) v--;
 if (*v != '=') return FALSE;
 
 n = v;
 if (*v != '=') return FALSE;
 
 n = v;
@@ -815,6 +994,9 @@ message_linecount = 0;
 message_size = -1;
 acl_added_headers = NULL;
 queue_only_policy = FALSE;
 message_size = -1;
 acl_added_headers = NULL;
 queue_only_policy = FALSE;
+rcpt_smtp_response = NULL;
+rcpt_smtp_response_same = TRUE;
+rcpt_in_progress = FALSE;
 deliver_freeze = FALSE;                              /* Can be set by ACL */
 freeze_tell = freeze_tell_config;                    /* Can be set by ACL */
 fake_response = OK;                                  /* Can be set by ACL */
 deliver_freeze = FALSE;                              /* Can be set by ACL */
 freeze_tell = freeze_tell_config;                    /* Can be set by ACL */
 fake_response = OK;                                  /* Can be set by ACL */
@@ -938,7 +1120,7 @@ while (done <= 0)
     case HELO_CMD:
     case EHLO_CMD:
 
     case HELO_CMD:
     case EHLO_CMD:
 
-    check_helo(smtp_cmd_argument);
+    check_helo(smtp_cmd_data);
     /* Fall through */
 
     case RSET_CMD:
     /* Fall through */
 
     case RSET_CMD:
@@ -958,7 +1140,7 @@ while (done <= 0)
       /* The function moan_smtp_batch() does not return. */
       moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given");
 
       /* The function moan_smtp_batch() does not return. */
       moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given");
 
-    if (smtp_cmd_argument[0] == 0)
+    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");
 
       /* The function moan_smtp_batch() does not return. */
       moan_smtp_batch(smtp_cmd_buffer, "501 MAIL FROM must have an address operand");
 
@@ -969,8 +1151,8 @@ while (done <= 0)
     /* Apply SMTP rewrite */
 
     raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)?
     /* Apply SMTP rewrite */
 
     raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)?
-      rewrite_one(smtp_cmd_argument, rewrite_smtp|rewrite_smtp_sender, NULL, FALSE,
-        US"", global_rewrite_rules) : smtp_cmd_argument;
+      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 */
 
@@ -1013,7 +1195,7 @@ while (done <= 0)
       /* The function moan_smtp_batch() does not return. */
       moan_smtp_batch(smtp_cmd_buffer, "503 No sender yet given");
 
       /* The function moan_smtp_batch() does not return. */
       moan_smtp_batch(smtp_cmd_buffer, "503 No sender yet given");
 
-    if (smtp_cmd_argument[0] == 0)
+    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");
 
       /* The function moan_smtp_batch() does not return. */
       moan_smtp_batch(smtp_cmd_buffer, "501 RCPT TO must have an address operand");
 
@@ -1028,8 +1210,8 @@ while (done <= 0)
     recipient address */
 
     recipient = ((rewrite_existflags & rewrite_smtp) != 0)?
     recipient address */
 
     recipient = ((rewrite_existflags & rewrite_smtp) != 0)?
-      rewrite_one(smtp_cmd_argument, rewrite_smtp, NULL, FALSE, US"",
-        global_rewrite_rules) : smtp_cmd_argument;
+      rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+        global_rewrite_rules) : smtp_cmd_data;
 
     /* rfc821_domains = TRUE; << no longer needed */
     recipient = parse_extract_address(recipient, &errmess, &start, &end,
 
     /* rfc821_domains = TRUE; << no longer needed */
     recipient = parse_extract_address(recipient, &errmess, &start, &end,
@@ -1146,14 +1328,21 @@ uschar *user_msg, *log_msg;
 uschar *code, *esc;
 uschar *p, *s, *ss;
 
 uschar *code, *esc;
 uschar *p, *s, *ss;
 
+smtp_connection_start = time(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;
+
 /* Default values for certain variables */
 
 helo_seen = esmtp = helo_accept_junk = FALSE;
 /* Default values for certain variables */
 
 helo_seen = esmtp = helo_accept_junk = FALSE;
+smtp_mailcmd_count = 0;
 count_nonmail = TRUE_UNSET;
 synprot_error_count = unknown_command_count = nonmail_command_count = 0;
 smtp_delay_mail = smtp_rlm_base;
 auth_advertised = FALSE;
 pipelining_advertised = FALSE;
 count_nonmail = TRUE_UNSET;
 synprot_error_count = unknown_command_count = nonmail_command_count = 0;
 smtp_delay_mail = smtp_rlm_base;
 auth_advertised = FALSE;
 pipelining_advertised = FALSE;
+pipelining_enable = TRUE;
 sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
 
 memset(sender_host_cache, 0, sizeof(sender_host_cache));
 sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
 
 memset(sender_host_cache, 0, sizeof(sender_host_cache));
@@ -1173,12 +1362,13 @@ tls_advertised = FALSE;
 
 acl_var_c = NULL;
 
 
 acl_var_c = NULL;
 
-/* Allow for trailing 0 in the command buffer. */
+/* Allow for trailing 0 in the command and data buffers. */
 
 
-smtp_cmd_buffer = (uschar *)malloc(smtp_cmd_buffer_size + 1);
+smtp_cmd_buffer = (uschar *)malloc(2*smtp_cmd_buffer_size + 2);
 if (smtp_cmd_buffer == NULL)
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
     "malloc() failed for SMTP command buffer");
 if (smtp_cmd_buffer == NULL)
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
     "malloc() failed for SMTP command buffer");
+smtp_data_buffer = smtp_cmd_buffer + smtp_cmd_buffer_size + 1;
 
 /* For batched input, the protocol setting can be overridden from the
 command line by a trusted caller. */
 
 /* For batched input, the protocol setting can be overridden from the
 command line by a trusted caller. */
@@ -1205,6 +1395,7 @@ receive_getc = smtp_getc;
 receive_ungetc = smtp_ungetc;
 receive_feof = smtp_feof;
 receive_ferror = smtp_ferror;
 receive_ungetc = smtp_ungetc;
 receive_feof = smtp_feof;
 receive_ferror = smtp_ferror;
+receive_smtp_buffered = smtp_buffered;
 smtp_inptr = smtp_inend = smtp_inbuffer;
 smtp_had_eof = smtp_had_error = 0;
 
 smtp_inptr = smtp_inend = smtp_inbuffer;
 smtp_had_eof = smtp_had_error = 0;
 
@@ -1450,7 +1641,9 @@ if (!sender_host_unknown)
   smtps port for use with older style SSL MTAs. */
 
   #ifdef SUPPORT_TLS
   smtps port for use with older style SSL MTAs. */
 
   #ifdef SUPPORT_TLS
-  if (tls_on_connect && tls_server_start(tls_require_ciphers) != OK)
+  if (tls_on_connect &&
+      tls_server_start(tls_require_ciphers,
+        gnutls_require_mac, gnutls_require_kx, gnutls_require_proto) != OK)
     return FALSE;
   #endif
 
     return FALSE;
   #endif
 
@@ -1502,18 +1695,18 @@ if (!sender_host_unknown)
     }
   #endif
 
     }
   #endif
 
-  /* Check for reserved slots. Note that the count value doesn't include
-  this process, as it gets upped in the parent process. */
+  /* Check for reserved slots. The value of smtp_accept_count has already been
+  incremented to include this process. */
 
   if (smtp_accept_max > 0 &&
 
   if (smtp_accept_max > 0 &&
-      smtp_accept_count + 1 > smtp_accept_max - smtp_accept_reserve)
+      smtp_accept_count > smtp_accept_max - smtp_accept_reserve)
     {
     if ((rc = verify_check_host(&smtp_reserve_hosts)) != OK)
       {
       log_write(L_connection_reject,
         LOG_MAIN, "temporarily refused connection from %s: not in "
         "reserve list: connected=%d max=%d reserve=%d%s",
     {
     if ((rc = verify_check_host(&smtp_reserve_hosts)) != OK)
       {
       log_write(L_connection_reject,
         LOG_MAIN, "temporarily refused connection from %s: not in "
         "reserve list: connected=%d max=%d reserve=%d%s",
-        host_and_ident(FALSE), smtp_accept_count, smtp_accept_max,
+        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);
         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);
@@ -1659,30 +1852,14 @@ 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. */
 
 /* Before we write the banner, check that there is no input pending, unless
 this synchronisation check is disabled. */
 
-if (smtp_enforce_sync && sender_host_address != NULL && !sender_host_notsocket)
+if (!check_sync())
   {
   {
-  fd_set fds;
-  struct timeval tzero;
-  tzero.tv_sec = 0;
-  tzero.tv_usec = 0;
-  FD_ZERO(&fds);
-  FD_SET(fileno(smtp_in), &fds);
-  if (select(fileno(smtp_in) + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL,
-      &tzero) > 0)
-    {
-    int rc = read(fileno(smtp_in), smtp_inbuffer, in_buffer_size);
-    if (rc > 0)
-      {
-      if (rc > 150) rc = 150;
-      smtp_inbuffer[rc] = 0;
-      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_inbuffer));
-      smtp_printf("554 SMTP synchronization error\r\n");
-      return FALSE;
-      }
-    }
+  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");
+  return FALSE;
   }
 
 /* Now output the banner */
   }
 
 /* Now output the banner */
@@ -1814,6 +1991,24 @@ if (codelen > 4)
   esclen = codelen - 4;
   }
 
   esclen = codelen - 4;
   }
 
+/* If this is the first output for a (non-batch) RCPT command, see if all RCPTs
+have had the same. Note: this code is also present in smtp_printf(). It would
+be tidier to have it only in one place, but when it was added, it was easier to
+do it that way, so as not to have to mess with the code for the RCPT command,
+which sometimes uses smtp_printf() and sometimes smtp_respond(). */
+
+if (rcpt_in_progress)
+  {
+  if (rcpt_smtp_response == NULL)
+    rcpt_smtp_response = string_copy(msg);
+  else if (rcpt_smtp_response_same &&
+           Ustrcmp(rcpt_smtp_response, msg) != 0)
+    rcpt_smtp_response_same = FALSE;
+  rcpt_in_progress = FALSE;
+  }
+
+/* Not output the message, splitting it up into multiple lines if necessary. */
+
 for (;;)
   {
   uschar *nl = Ustrchr(msg, '\n');
 for (;;)
   {
   uschar *nl = Ustrchr(msg, '\n');
@@ -1948,9 +2143,9 @@ uschar *what =
 #endif
   (where == ACL_WHERE_PREDATA)? US"DATA" :
   (where == ACL_WHERE_DATA)? US"after DATA" :
 #endif
   (where == ACL_WHERE_PREDATA)? US"DATA" :
   (where == ACL_WHERE_DATA)? US"after DATA" :
-  (smtp_cmd_argument == NULL)?
+  (smtp_cmd_data == NULL)?
     string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]) :
     string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]) :
-    string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_argument);
+    string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data);
 
 if (drop) rc = FAIL;
 
 
 if (drop) rc = FAIL;
 
@@ -1983,6 +2178,9 @@ unless the sender_verify_fail log selector has been turned off. */
 if (sender_verified_failed != NULL &&
     !testflag(sender_verified_failed, af_sverify_told))
   {
 if (sender_verified_failed != NULL &&
     !testflag(sender_verified_failed, af_sverify_told))
   {
+  BOOL save_rcpt_in_progress = rcpt_in_progress;
+  rcpt_in_progress = FALSE;  /* So as not to treat these as the error */
+
   setflag(sender_verified_failed, af_sverify_told);
 
   if (rc != FAIL || (log_extra_selector & LX_sender_verify_fail) != 0)
   setflag(sender_verified_failed, af_sverify_told);
 
   if (rc != FAIL || (log_extra_selector & LX_sender_verify_fail) != 0)
@@ -2012,6 +2210,8 @@ if (sender_verified_failed != NULL &&
           "Verification failed for <%s>\n%s",
         sender_verified_failed->address,
         sender_verified_failed->user_message));
           "Verification failed for <%s>\n%s",
         sender_verified_failed->address,
         sender_verified_failed->user_message));
+
+  rcpt_in_progress = save_rcpt_in_progress;
   }
 
 /* Sort out text for logging */
   }
 
 /* Sort out text for logging */
@@ -2335,6 +2535,7 @@ while (done <= 0)
     AUTHS will eventually hit the nonmail threshold. */
 
     case AUTH_CMD:
     AUTHS will eventually hit the nonmail threshold. */
 
     case AUTH_CMD:
+    HAD(SCH_AUTH);
     authentication_failed = TRUE;
     cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE;
 
     authentication_failed = TRUE;
     cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE;
 
@@ -2371,8 +2572,8 @@ while (done <= 0)
 
     /* Find the name of the requested authentication mechanism. */
 
 
     /* Find the name of the requested authentication mechanism. */
 
-    s = smtp_cmd_argument;
-    while ((c = *smtp_cmd_argument) != 0 && !isspace(c))
+    s = smtp_cmd_data;
+    while ((c = *smtp_cmd_data) != 0 && !isspace(c))
       {
       if (!isalnum(c) && c != '-' && c != '_')
         {
       {
       if (!isalnum(c) && c != '-' && c != '_')
         {
@@ -2380,16 +2581,16 @@ while (done <= 0)
           US"invalid character in authentication mechanism name");
         goto COMMAND_LOOP;
         }
           US"invalid character in authentication mechanism name");
         goto COMMAND_LOOP;
         }
-      smtp_cmd_argument++;
+      smtp_cmd_data++;
       }
 
     /* If not at the end of the line, we must be at white space. Terminate the
     name and move the pointer on to any data that may be present. */
 
       }
 
     /* If not at the end of the line, we must be at white space. Terminate the
     name and move the pointer on to any data that may be present. */
 
-    if (*smtp_cmd_argument != 0)
+    if (*smtp_cmd_data != 0)
       {
       {
-      *smtp_cmd_argument++ = 0;
-      while (isspace(*smtp_cmd_argument)) smtp_cmd_argument++;
+      *smtp_cmd_data++ = 0;
+      while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
       }
 
     /* Search for an authentication mechanism which is configured for use
       }
 
     /* Search for an authentication mechanism which is configured for use
@@ -2425,7 +2626,7 @@ while (done <= 0)
     expand_nmax = 0;
     expand_nlength[0] = 0;   /* $0 contains nothing */
 
     expand_nmax = 0;
     expand_nlength[0] = 0;   /* $0 contains nothing */
 
-    c = (au->info->servercode)(au, smtp_cmd_argument);
+    c = (au->info->servercode)(au, smtp_cmd_data);
     if (au->set_id != NULL) set_id = expand_string(au->set_id);
     expand_nmax = -1;        /* Reset numeric variables */
     for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;   /* Reset $auth<n> */
     if (au->set_id != NULL) set_id = expand_string(au->set_id);
     expand_nmax = -1;        /* Reset numeric variables */
     for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;   /* Reset $auth<n> */
@@ -2527,11 +2728,13 @@ while (done <= 0)
     it did the reset first. */
 
     case HELO_CMD:
     it did the reset first. */
 
     case HELO_CMD:
+    HAD(SCH_HELO);
     hello = US"HELO";
     esmtp = FALSE;
     goto HELO_EHLO;
 
     case EHLO_CMD:
     hello = US"HELO";
     esmtp = FALSE;
     goto HELO_EHLO;
 
     case EHLO_CMD:
+    HAD(SCH_EHLO);
     hello = US"EHLO";
     esmtp = TRUE;
 
     hello = US"EHLO";
     esmtp = TRUE;
 
@@ -2542,7 +2745,7 @@ while (done <= 0)
     /* Reject the HELO if its argument was invalid or non-existent. A
     successful check causes the argument to be saved in malloc store. */
 
     /* Reject the HELO if its argument was invalid or non-existent. A
     successful check causes the argument to be saved in malloc store. */
 
-    if (!check_helo(smtp_cmd_argument))
+    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", hello);
 
@@ -2572,7 +2775,7 @@ while (done <= 0)
     if (!sender_host_unknown)
       {
       BOOL old_helo_verified = helo_verified;
     if (!sender_host_unknown)
       {
       BOOL old_helo_verified = helo_verified;
-      uschar *p = smtp_cmd_argument;
+      uschar *p = smtp_cmd_data;
 
       while (*p != 0 && !isspace(*p)) { *p = tolower(*p); p++; }
       *p = 0;
 
       while (*p != 0 && !isspace(*p)) { *p = tolower(*p); p++; }
       *p = 0;
@@ -2627,7 +2830,8 @@ while (done <= 0)
     spf_init(sender_helo_name, sender_host_address);
 #endif
 
     spf_init(sender_helo_name, sender_host_address);
 #endif
 
-    /* Apply an ACL check if one is defined */
+    /* Apply an ACL check if one is defined; afterwards, recheck
+    synchronization in case the client started sending in a delay. */
 
     if (acl_smtp_helo != NULL)
       {
 
     if (acl_smtp_helo != NULL)
       {
@@ -2639,6 +2843,7 @@ while (done <= 0)
         host_build_sender_fullhost();  /* Rebuild */
         break;
         }
         host_build_sender_fullhost();  /* Rebuild */
         break;
         }
+      else if (!check_sync()) goto SYNC_FAILURE;
       }
 
     /* Generate an OK reply. The default string includes the ident if present,
       }
 
     /* Generate an OK reply. The default string includes the ident if present,
@@ -2756,7 +2961,8 @@ while (done <= 0)
       /* Exim is quite happy with pipelining, so let the other end know that
       it is safe to use it, unless advertising is disabled. */
 
       /* Exim is quite happy with pipelining, so let the other end know that
       it is safe to use it, unless advertising is disabled. */
 
-      if (verify_check_host(&pipelining_advertise_hosts) == OK)
+      if (pipelining_enable &&
+          verify_check_host(&pipelining_advertise_hosts) == OK)
         {
         s = string_cat(s, &size, &ptr, smtp_code, 3);
         s = string_cat(s, &size, &ptr, US"-PIPELINING\r\n", 13);
         {
         s = string_cat(s, &size, &ptr, smtp_code, 3);
         s = string_cat(s, &size, &ptr, US"-PIPELINING\r\n", 13);
@@ -2870,6 +3076,7 @@ while (done <= 0)
     it is the canonical extracted address which is all that is kept. */
 
     case MAIL_CMD:
     it is the canonical extracted address which is all that is kept. */
 
     case MAIL_CMD:
+    HAD(SCH_MAIL);
     smtp_mailcmd_count++;              /* Count for limit and ratelimit */
     was_rej_mail = TRUE;               /* Reset if accepted */
 
     smtp_mailcmd_count++;              /* Count for limit and ratelimit */
     was_rej_mail = TRUE;               /* Reset if accepted */
 
@@ -2888,7 +3095,7 @@ while (done <= 0)
       break;
       }
 
       break;
       }
 
-    if (smtp_cmd_argument[0] == 0)
+    if (smtp_cmd_data[0] == 0)
       {
       done = synprot_error(L_smtp_protocol_error, 501, NULL,
         US"MAIL must have an address operand");
       {
       done = synprot_error(L_smtp_protocol_error, 501, NULL,
         US"MAIL must have an address operand");
@@ -2927,7 +3134,7 @@ while (done <= 0)
       in order to be able to log the sender address on failure. */
 
       if (strcmpic(name, US"SIZE") == 0 &&
       in order to be able to log the sender address on failure. */
 
       if (strcmpic(name, US"SIZE") == 0 &&
-          ((size = (int)Ustrtoul(value, &end, 10)), *end == 0))
+          ((size = Ustrtoul(value, &end, 10)), *end == 0))
         {
         if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX)
           size = INT_MAX;
         {
         if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX)
           size = INT_MAX;
@@ -3047,8 +3254,8 @@ while (done <= 0)
     TRUE flag allows "<>" as a sender address. */
 
     raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)?
     TRUE flag allows "<>" as a sender address. */
 
     raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)?
-      rewrite_one(smtp_cmd_argument, rewrite_smtp, NULL, FALSE, US"",
-        global_rewrite_rules) : smtp_cmd_argument;
+      rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+        global_rewrite_rules) : smtp_cmd_data;
 
     /* rfc821_domains = TRUE; << no longer needed */
     raw_sender =
 
     /* rfc821_domains = TRUE; << no longer needed */
     raw_sender =
@@ -3058,7 +3265,7 @@ while (done <= 0)
 
     if (raw_sender == NULL)
       {
 
     if (raw_sender == NULL)
       {
-      done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_argument, errmess);
+      done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
       break;
       }
 
       break;
       }
 
@@ -3118,7 +3325,7 @@ while (done <= 0)
       else
         {
         smtp_printf("501 %s: sender address must contain a domain\r\n",
       else
         {
         smtp_printf("501 %s: sender address must contain a domain\r\n",
-          smtp_cmd_argument);
+          smtp_cmd_data);
         log_write(L_smtp_syntax_error,
           LOG_MAIN|LOG_REJECT,
           "unqualified sender rejected: <%s> %s%s",
         log_write(L_smtp_syntax_error,
           LOG_MAIN|LOG_REJECT,
           "unqualified sender rejected: <%s> %s%s",
@@ -3130,10 +3337,16 @@ while (done <= 0)
         }
       }
 
         }
       }
 
-    /* Apply an ACL check if one is defined, before responding */
+    /* Apply an ACL check if one is defined, before responding. Afterwards,
+    when pipelining is not advertised, do another sync check in case the ACL
+    delayed and the client started sending in the meantime. */
 
 
-    rc = (acl_smtp_mail == NULL)? OK :
-      acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
+    if (acl_smtp_mail == NULL) rc = OK; else
+      {
+      rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
+      if (rc == OK && !pipelining_advertised && !check_sync())
+        goto SYNC_FAILURE;
+      }
 
     if (rc == OK || rc == DISCARD)
       {
 
     if (rc == OK || rc == DISCARD)
       {
@@ -3151,16 +3364,15 @@ while (done <= 0)
     break;
 
 
     break;
 
 
-    /* The RCPT command requires an address as an operand. All we do
-    here is to parse it for syntactic correctness. There may be any number
-    of RCPT commands, specifying multiple senders. We build them all into
-    a data structure that is in argc/argv format. The start/end values
-    given by parse_extract_address are not used, as we keep only the
-    extracted address. */
+    /* The RCPT command requires an address as an operand. There may be any
+    number of RCPT commands, specifying multiple recipients. We build them all
+    into a data structure. The start/end values given by parse_extract_address
+    are not used, as we keep only the extracted address. */
 
     case RCPT_CMD:
 
     case RCPT_CMD:
+    HAD(SCH_RCPT);
     rcpt_count++;
     rcpt_count++;
-    was_rcpt = TRUE;
+    was_rcpt = rcpt_in_progress = TRUE;
 
     /* There must be a sender address; if the sender was rejected and
     pipelining was advertised, we assume the client was pipelining, and do not
 
     /* There must be a sender address; if the sender was rejected and
     pipelining was advertised, we assume the client was pipelining, and do not
@@ -3186,7 +3398,7 @@ while (done <= 0)
 
     /* Check for an operand */
 
 
     /* Check for an operand */
 
-    if (smtp_cmd_argument[0] == 0)
+    if (smtp_cmd_data[0] == 0)
       {
       done = synprot_error(L_smtp_syntax_error, 501, NULL,
         US"RCPT must have an address operand");
       {
       done = synprot_error(L_smtp_syntax_error, 501, NULL,
         US"RCPT must have an address operand");
@@ -3198,8 +3410,8 @@ while (done <= 0)
     as a recipient address */
 
     recipient = ((rewrite_existflags & rewrite_smtp) != 0)?
     as a recipient address */
 
     recipient = ((rewrite_existflags & rewrite_smtp) != 0)?
-      rewrite_one(smtp_cmd_argument, rewrite_smtp, NULL, FALSE, US"",
-        global_rewrite_rules) : smtp_cmd_argument;
+      rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+        global_rewrite_rules) : smtp_cmd_data;
 
     /* rfc821_domains = TRUE; << no longer needed */
     recipient = parse_extract_address(recipient, &errmess, &start, &end,
 
     /* rfc821_domains = TRUE; << no longer needed */
     recipient = parse_extract_address(recipient, &errmess, &start, &end,
@@ -3208,7 +3420,7 @@ while (done <= 0)
 
     if (recipient == NULL)
       {
 
     if (recipient == NULL)
       {
-      done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_argument, errmess);
+      done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
       rcpt_fail_count++;
       break;
       }
       rcpt_fail_count++;
       break;
       }
@@ -3238,7 +3450,7 @@ while (done <= 0)
         {
         rcpt_fail_count++;
         smtp_printf("501 %s: recipient address must contain a domain\r\n",
         {
         rcpt_fail_count++;
         smtp_printf("501 %s: recipient address must contain a domain\r\n",
-          smtp_cmd_argument);
+          smtp_cmd_data);
         log_write(L_smtp_syntax_error,
           LOG_MAIN|LOG_REJECT, "unqualified recipient rejected: "
           "<%s> %s%s", recipient, host_and_ident(TRUE),
         log_write(L_smtp_syntax_error,
           LOG_MAIN|LOG_REJECT, "unqualified recipient rejected: "
           "<%s> %s%s", recipient, host_and_ident(TRUE),
@@ -3288,10 +3500,17 @@ while (done <= 0)
       }
 
     /* If the MAIL ACL discarded all the recipients, we bypass ACL checking
       }
 
     /* If the MAIL ACL discarded all the recipients, we bypass ACL checking
-    for them. Otherwise, check the access control list for this recipient. */
+    for them. Otherwise, check the access control list for this recipient. As
+    there may be a delay in this, re-check for a synchronization error
+    afterwards, unless pipelining was advertised. */
 
 
-    rc = recipients_discarded? DISCARD :
-      acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg, &log_msg);
+    if (recipients_discarded) rc = DISCARD; else
+      {
+      rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg,
+        &log_msg);
+      if (rc == OK && !pipelining_advertised && !check_sync())
+        goto SYNC_FAILURE;
+      }
 
     /* The ACL was happy */
 
 
     /* The ACL was happy */
 
@@ -3343,13 +3562,29 @@ while (done <= 0)
         DATA command.
 
     The example in the pipelining RFC 2920 uses 554, but I use 503 here
         DATA command.
 
     The example in the pipelining RFC 2920 uses 554, but I use 503 here
-    because it is the same whether pipelining is in use or not. */
+    because it is the same whether pipelining is in use or not.
+
+    If all the RCPT commands that precede DATA provoked the same error message
+    (often indicating some kind of system error), it is helpful to include it
+    with the DATA rejection (an idea suggested by Tony Finch). */
 
     case DATA_CMD:
 
     case DATA_CMD:
+    HAD(SCH_DATA);
     if (!discarded && recipients_count <= 0)
       {
     if (!discarded && recipients_count <= 0)
       {
+      if (rcpt_smtp_response_same && rcpt_smtp_response != NULL)
+        {
+        uschar *code = US"503";
+        int len = Ustrlen(rcpt_smtp_response);
+        smtp_respond(code, 3, FALSE, US"All RCPT commands were rejected with "
+          "this error:");
+        /* Responses from smtp_printf() will have \r\n on the end */
+        if (len > 2 && rcpt_smtp_response[len-2] == '\r')
+          rcpt_smtp_response[len-2] = 0;
+        smtp_respond(code, 3, FALSE, rcpt_smtp_response);
+        }
       if (pipelining_advertised && last_was_rcpt)
       if (pipelining_advertised && last_was_rcpt)
-        smtp_printf("503 valid RCPT command must precede DATA\r\n");
+        smtp_printf("503 Valid RCPT command must precede DATA\r\n");
       else
         done = synprot_error(L_smtp_protocol_error, 503, NULL,
           US"valid RCPT command must precede DATA");
       else
         done = synprot_error(L_smtp_protocol_error, 503, NULL,
           US"valid RCPT command must precede DATA");
@@ -3364,12 +3599,16 @@ while (done <= 0)
       break;
       }
 
       break;
       }
 
+    /* If there is an ACL, re-check the synchronization afterwards, since the
+    ACL may have delayed. */
+
     if (acl_smtp_predata == NULL) rc = OK; else
       {
       enable_dollar_recipients = TRUE;
       rc = acl_check(ACL_WHERE_PREDATA, NULL, acl_smtp_predata, &user_msg,
         &log_msg);
       enable_dollar_recipients = FALSE;
     if (acl_smtp_predata == NULL) rc = OK; else
       {
       enable_dollar_recipients = TRUE;
       rc = acl_check(ACL_WHERE_PREDATA, NULL, acl_smtp_predata, &user_msg,
         &log_msg);
       enable_dollar_recipients = FALSE;
+      if (rc == OK && !check_sync()) goto SYNC_FAILURE;
       }
 
     if (rc == OK)
       }
 
     if (rc == OK)
@@ -3385,11 +3624,11 @@ while (done <= 0)
 
     else
       done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg);
 
     else
       done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg);
-
     break;
 
 
     case VRFY_CMD:
     break;
 
 
     case VRFY_CMD:
+    HAD(SCH_VRFY);
     rc = acl_check(ACL_WHERE_VRFY, NULL, acl_smtp_vrfy, &user_msg, &log_msg);
     if (rc != OK)
       done = smtp_handle_acl_fail(ACL_WHERE_VRFY, rc, user_msg, log_msg);
     rc = acl_check(ACL_WHERE_VRFY, NULL, acl_smtp_vrfy, &user_msg, &log_msg);
     if (rc != OK)
       done = smtp_handle_acl_fail(ACL_WHERE_VRFY, rc, user_msg, log_msg);
@@ -3399,7 +3638,7 @@ while (done <= 0)
       uschar *s = NULL;
 
       /* rfc821_domains = TRUE; << no longer needed */
       uschar *s = NULL;
 
       /* rfc821_domains = TRUE; << no longer needed */
-      address = parse_extract_address(smtp_cmd_argument, &errmess, &start, &end,
+      address = parse_extract_address(smtp_cmd_data, &errmess, &start, &end,
         &recipient_domain, FALSE);
       /* rfc821_domains = FALSE; << no longer needed */
 
         &recipient_domain, FALSE);
       /* rfc821_domains = FALSE; << no longer needed */
 
@@ -3437,6 +3676,7 @@ while (done <= 0)
 
 
     case EXPN_CMD:
 
 
     case EXPN_CMD:
+    HAD(SCH_EXPN);
     rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg);
     if (rc != OK)
       done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg);
     rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg);
     if (rc != OK)
       done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg);
@@ -3444,7 +3684,7 @@ while (done <= 0)
       {
       BOOL save_log_testing_mode = log_testing_mode;
       address_test_mode = log_testing_mode = TRUE;
       {
       BOOL save_log_testing_mode = log_testing_mode;
       address_test_mode = log_testing_mode = TRUE;
-      (void) verify_address(deliver_make_addr(smtp_cmd_argument, FALSE),
+      (void) verify_address(deliver_make_addr(smtp_cmd_data, FALSE),
         smtp_out, vopt_is_recipient | vopt_qualify | vopt_expn, -1, -1, -1,
         NULL, NULL, NULL);
       address_test_mode = FALSE;
         smtp_out, vopt_is_recipient | vopt_qualify | vopt_expn, -1, -1, -1,
         NULL, NULL, NULL);
       address_test_mode = FALSE;
@@ -3456,6 +3696,7 @@ while (done <= 0)
     #ifdef SUPPORT_TLS
 
     case STARTTLS_CMD:
     #ifdef SUPPORT_TLS
 
     case STARTTLS_CMD:
+    HAD(SCH_STARTTLS);
     if (!tls_advertised)
       {
       done = synprot_error(L_smtp_protocol_error, 503, NULL,
     if (!tls_advertised)
       {
       done = synprot_error(L_smtp_protocol_error, 503, NULL,
@@ -3494,7 +3735,8 @@ while (done <= 0)
     We must allow for an extra EHLO command and an extra AUTH command after
     STARTTLS that don't add to the nonmail command count. */
 
     We must allow for an extra EHLO command and an extra AUTH command after
     STARTTLS that don't add to the nonmail command count. */
 
-    if ((rc = tls_server_start(tls_require_ciphers)) == OK)
+    if ((rc = tls_server_start(tls_require_ciphers, gnutls_require_mac,
+           gnutls_require_kx, gnutls_require_proto)) == OK)
       {
       if (!tls_remember_esmtp)
         helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE;
       {
       if (!tls_remember_esmtp)
         helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE;
@@ -3569,6 +3811,7 @@ while (done <= 0)
     message. */
 
     case QUIT_CMD:
     message. */
 
     case QUIT_CMD:
+    HAD(SCH_QUIT);
     incomplete_transaction_log(US"QUIT");
 
     if (acl_smtp_quit != NULL)
     incomplete_transaction_log(US"QUIT");
 
     if (acl_smtp_quit != NULL)
@@ -3595,6 +3838,7 @@ while (done <= 0)
 
 
     case RSET_CMD:
 
 
     case RSET_CMD:
+    HAD(SCH_RSET);
     incomplete_transaction_log(US"RSET");
     smtp_reset(reset_point);
     toomany = FALSE;
     incomplete_transaction_log(US"RSET");
     smtp_reset(reset_point);
     toomany = FALSE;
@@ -3604,22 +3848,27 @@ while (done <= 0)
 
 
     case NOOP_CMD:
 
 
     case NOOP_CMD:
+    HAD(SCH_NOOP);
     smtp_printf("250 OK\r\n");
     break;
 
 
     smtp_printf("250 OK\r\n");
     break;
 
 
-    /* Show ETRN/EXPN/VRFY if there's
-    an ACL for checking hosts; if actually used, a check will be done for
-    permitted hosts. */
+    /* Show ETRN/EXPN/VRFY if there's an ACL for checking hosts; if actually
+    used, a check will be done for permitted hosts. Show STARTTLS only if not
+    already in a TLS session and if it would be advertised in the EHLO
+    response. */
 
     case HELP_CMD:
 
     case HELP_CMD:
+    HAD(SCH_HELP);
     smtp_printf("214-Commands supported:\r\n");
       {
       uschar buffer[256];
       buffer[0] = 0;
       Ustrcat(buffer, " AUTH");
       #ifdef SUPPORT_TLS
     smtp_printf("214-Commands supported:\r\n");
       {
       uschar buffer[256];
       buffer[0] = 0;
       Ustrcat(buffer, " AUTH");
       #ifdef SUPPORT_TLS
-      Ustrcat(buffer, " STARTTLS");
+      if (tls_active < 0 &&
+          verify_check_host(&tls_advertise_hosts) != FAIL)
+        Ustrcat(buffer, " STARTTLS");
       #endif
       Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA");
       Ustrcat(buffer, " NOOP QUIT RSET HELP");
       #endif
       Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA");
       Ustrcat(buffer, " NOOP QUIT RSET HELP");
@@ -3654,6 +3903,7 @@ while (done <= 0)
 
 
     case ETRN_CMD:
 
 
     case ETRN_CMD:
+    HAD(SCH_ETRN);
     if (sender_address != NULL)
       {
       done = synprot_error(L_smtp_protocol_error, 503, NULL,
     if (sender_address != NULL)
       {
       done = synprot_error(L_smtp_protocol_error, 503, NULL,
@@ -3673,7 +3923,7 @@ while (done <= 0)
 
     /* Compute the serialization key for this command. */
 
 
     /* Compute the serialization key for this command. */
 
-    etrn_serialize_key = string_sprintf("etrn-%s\n", smtp_cmd_argument);
+    etrn_serialize_key = string_sprintf("etrn-%s\n", smtp_cmd_data);
 
     /* If a command has been specified for running as a result of ETRN, we
     permit any argument to ETRN. If not, only the # standard form is permitted,
 
     /* If a command has been specified for running as a result of ETRN, we
     permit any argument to ETRN. If not, only the # standard form is permitted,
@@ -3685,7 +3935,7 @@ while (done <= 0)
       uschar *error;
       BOOL rc;
       etrn_command = smtp_etrn_command;
       uschar *error;
       BOOL rc;
       etrn_command = smtp_etrn_command;
-      deliver_domain = smtp_cmd_argument;
+      deliver_domain = smtp_cmd_data;
       rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL,
         US"ETRN processing", &error);
       deliver_domain = NULL;
       rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL,
         US"ETRN processing", &error);
       deliver_domain = NULL;
@@ -3702,7 +3952,7 @@ while (done <= 0)
 
     else
       {
 
     else
       {
-      if (*smtp_cmd_argument++ != '#')
+      if (*smtp_cmd_data++ != '#')
         {
         done = synprot_error(L_smtp_syntax_error, 501, NULL,
           US"argument must begin with #");
         {
         done = synprot_error(L_smtp_syntax_error, 501, NULL,
           US"argument must begin with #");
@@ -3710,7 +3960,7 @@ while (done <= 0)
         }
       etrn_command = US"exim -R";
       argv = child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE, 2, US"-R",
         }
       etrn_command = US"exim -R";
       argv = child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE, 2, US"-R",
-        smtp_cmd_argument);
+        smtp_cmd_data);
       }
 
     /* If we are host-testing, don't actually do anything. */
       }
 
     /* If we are host-testing, don't actually do anything. */
@@ -3733,7 +3983,7 @@ while (done <= 0)
 
     if (smtp_etrn_serialize && !enq_start(etrn_serialize_key))
       {
 
     if (smtp_etrn_serialize && !enq_start(etrn_serialize_key))
       {
-      smtp_printf("458 Already processing %s\r\n", smtp_cmd_argument);
+      smtp_printf("458 Already processing %s\r\n", smtp_cmd_data);
       break;
       }
 
       break;
       }
 
@@ -3825,6 +4075,7 @@ while (done <= 0)
 
 
     case BADSYN_CMD:
 
 
     case BADSYN_CMD:
+    SYNC_FAILURE:
     if (smtp_inend >= smtp_inbuffer + in_buffer_size)
       smtp_inend = smtp_inbuffer + in_buffer_size - 1;
     c = smtp_inend - smtp_inptr;
     if (smtp_inend >= smtp_inbuffer + in_buffer_size)
       smtp_inend = smtp_inbuffer + in_buffer_size - 1;
     c = smtp_inend - smtp_inptr;
@@ -3843,10 +4094,12 @@ while (done <= 0)
 
 
     case TOO_MANY_NONMAIL_CMD:
 
 
     case TOO_MANY_NONMAIL_CMD:
+    s = smtp_cmd_buffer;
+    while (*s != 0 && !isspace(*s)) s++;
     incomplete_transaction_log(US"too many non-mail commands");
     log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
       "nonmail commands (last was \"%.*s\")",  host_and_ident(FALSE),
     incomplete_transaction_log(US"too many non-mail commands");
     log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
       "nonmail commands (last was \"%.*s\")",  host_and_ident(FALSE),
-      smtp_cmd_argument - smtp_cmd_buffer, smtp_cmd_buffer);
+      s - smtp_cmd_buffer, smtp_cmd_buffer);
     smtp_printf("554 Too many nonmail commands\r\n");
     done = 1;   /* Pretend eof - drops connection */
     break;
     smtp_printf("554 Too many nonmail commands\r\n");
     done = 1;   /* Pretend eof - drops connection */
     break;