$smtp_command and $smtp_command_argument were incomplete for MAIL
[exim.git] / src / src / smtp_in.c
index c75b2b207ebd8bb876c41017c206a8b87e3130f4..c9c5842b10ee0e861bca19b10974bac4998af7f3 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/smtp_in.c,v 1.46 2006/10/23 10:55:10 ph10 Exp $ */
+/* $Cambridge: exim/src/src/smtp_in.c,v 1.54 2007/02/20 11:37:16 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2006 */
+/* Copyright (c) University of Cambridge 1995 - 2007 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
@@ -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          *
@@ -119,6 +126,9 @@ static int  unknown_command_count;
 static int  sync_cmd_limit;
 static int  smtp_write_error = 0;
 
 static int  sync_cmd_limit;
 static int  smtp_write_error = 0;
 
+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 +175,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 */
@@ -536,11 +555,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 +582,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;
     }
   }
 
     }
   }
 
@@ -666,6 +689,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 +846,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 +861,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;
@@ -874,7 +965,7 @@ if (message_body_end != NULL)
 
 /* Warning log messages are also saved in malloc store. They are saved to avoid
 repetition in the same message, but it seems right to repeat them for different
 
 /* Warning log messages are also saved in malloc store. They are saved to avoid
 repetition in the same message, but it seems right to repeat them for different
-messagess. */
+messages. */
 
 while (acl_warn_logged != NULL)
   {
 
 while (acl_warn_logged != NULL)
   {
@@ -938,7 +1029,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 +1049,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 +1060,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 +1104,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 +1119,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,
@@ -1141,17 +1232,26 @@ BOOL
 smtp_start_session(void)
 {
 int size = 256;
 smtp_start_session(void)
 {
 int size = 256;
-int ptr;
+int ptr, esclen;
+uschar *user_msg, *log_msg;
+uschar *code, *esc;
 uschar *p, *s, *ss;
 
 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));
@@ -1171,12 +1271,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. */
@@ -1448,7 +1549,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
 
@@ -1500,18 +1603,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);
@@ -1571,10 +1674,10 @@ if (smtp_batched_input) return TRUE;
 
 /* Run the ACL if it exists */
 
 
 /* Run the ACL if it exists */
 
+user_msg = NULL;
 if (acl_smtp_connect != NULL)
   {
   int rc;
 if (acl_smtp_connect != NULL)
   {
   int rc;
-  uschar *user_msg, *log_msg;
   rc = acl_check(ACL_WHERE_CONNECT, NULL, acl_smtp_connect, &user_msg,
     &log_msg);
   if (rc != OK)
   rc = acl_check(ACL_WHERE_CONNECT, NULL, acl_smtp_connect, &user_msg,
     &log_msg);
   if (rc != OK)
@@ -1587,10 +1690,28 @@ if (acl_smtp_connect != NULL)
 /* Output the initial message for a two-way SMTP connection. It may contain
 newlines, which then cause a multi-line response to be given. */
 
 /* Output the initial message for a two-way SMTP connection. It may contain
 newlines, which then cause a multi-line response to be given. */
 
-s = expand_string(smtp_banner);
-if (s == NULL)
-  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" (smtp_banner) "
-    "failed: %s", smtp_banner, expand_string_message);
+code = US"220";   /* Default status code */
+esc = US"";       /* Default extended status code */
+esclen = 0;       /* Length of esc */
+
+if (user_msg == NULL)
+  {
+  s = expand_string(smtp_banner);
+  if (s == NULL)
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" (smtp_banner) "
+      "failed: %s", smtp_banner, expand_string_message);
+  }
+else
+  {
+  int codelen = 3;
+  s = user_msg;
+  smtp_message_code(&code, &codelen, &s, NULL);
+  if (codelen > 4)
+    {
+    esc = code + 4;
+    esclen = codelen - 4;
+    }
+  }
 
 /* Remove any terminating newlines; might as well remove trailing space too */
 
 
 /* Remove any terminating newlines; might as well remove trailing space too */
 
@@ -1615,16 +1736,18 @@ do       /* At least once, in case we have an empty string */
   {
   int len;
   uschar *linebreak = Ustrchr(p, '\n');
   {
   int len;
   uschar *linebreak = Ustrchr(p, '\n');
+  ss = string_cat(ss, &size, &ptr, code, 3);
   if (linebreak == NULL)
     {
     len = Ustrlen(p);
   if (linebreak == NULL)
     {
     len = Ustrlen(p);
-    ss = string_cat(ss, &size, &ptr, US"220 ", 4);
+    ss = string_cat(ss, &size, &ptr, US" ", 1);
     }
   else
     {
     len = linebreak - p;
     }
   else
     {
     len = linebreak - p;
-    ss = string_cat(ss, &size, &ptr, US"220-", 4);
+    ss = string_cat(ss, &size, &ptr, US"-", 1);
     }
     }
+  ss = string_cat(ss, &size, &ptr, esc, esclen);
   ss = string_cat(ss, &size, &ptr, p, len);
   ss = string_cat(ss, &size, &ptr, US"\r\n", 2);
   p += len;
   ss = string_cat(ss, &size, &ptr, p, len);
   ss = string_cat(ss, &size, &ptr, US"\r\n", 2);
   p += len;
@@ -1771,7 +1894,7 @@ output nothing for non-final calls, and only the first line for anything else.
 
 Arguments:
   code          SMTP code, may involve extended status codes
 
 Arguments:
   code          SMTP code, may involve extended status codes
-  codelen       length of smtp code; uf > 3 there's an ESC
+  codelen       length of smtp code; if > 4 there's an ESC
   final         FALSE if the last line isn't the final line
   msg           message text, possibly containing newlines
 
   final         FALSE if the last line isn't the final line
   msg           message text, possibly containing newlines
 
@@ -1786,7 +1909,7 @@ uschar *esc = US"";
 
 if (!final && no_multiline_responses) return;
 
 
 if (!final && no_multiline_responses) return;
 
-if (codelen > 3)
+if (codelen > 4)
   {
   esc = code + 4;
   esclen = codelen - 4;
   {
   esc = code + 4;
   esclen = codelen - 4;
@@ -1818,6 +1941,65 @@ for (;;)
 
 
 
 
 
 
+/*************************************************
+*            Parse user SMTP message             *
+*************************************************/
+
+/* This function allows for user messages overriding the response code details
+by providing a suitable response code string at the start of the message
+user_msg. Check the message for starting with a response code and optionally an
+extended status code. If found, check that the first digit is valid, and if so,
+change the code pointer and length to use the replacement. An invalid code
+causes a panic log; in this case, if the log messages is the same as the user
+message, we must also adjust the value of the log message to show the code that
+is actually going to be used (the original one).
+
+This function is global because it is called from receive.c as well as within
+this module.
+
+Note that the code length returned includes the terminating whitespace
+character, which is always included in the regex match.
+
+Arguments:
+  code          SMTP code, may involve extended status codes
+  codelen       length of smtp code; if > 4 there's an ESC
+  msg           message text
+  log_msg       optional log message, to be adjusted with the new SMTP code
+
+Returns:        nothing
+*/
+
+void
+smtp_message_code(uschar **code, int *codelen, uschar **msg, uschar **log_msg)
+{
+int n;
+int ovector[3];
+
+if (msg == NULL || *msg == NULL) return;
+
+n = pcre_exec(regex_smtp_code, NULL, CS *msg, Ustrlen(*msg), 0,
+  PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int));
+if (n < 0) return;
+
+if ((*msg)[0] != (*code)[0])
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "configured error code starts with "
+    "incorrect digit (expected %c) in \"%s\"", (*code)[0], *msg);
+  if (log_msg != NULL && *log_msg == *msg)
+    *log_msg = string_sprintf("%s %s", *code, *log_msg + ovector[1]);
+  }
+else
+  {
+  *code = *msg;
+  *codelen = ovector[1];    /* Includes final space */
+  }
+*msg += ovector[1];         /* Chop the code off the message */
+return;
+}
+
+
+
+
 /*************************************************
 *           Handle an ACL failure                *
 *************************************************/
 /*************************************************
 *           Handle an ACL failure                *
 *************************************************/
@@ -1858,7 +2040,6 @@ smtp_handle_acl_fail(int where, int rc, uschar *user_msg, uschar *log_msg)
 {
 BOOL drop = rc == FAIL_DROP;
 int codelen = 3;
 {
 BOOL drop = rc == FAIL_DROP;
 int codelen = 3;
-int ovector[3];
 uschar *smtp_code;
 uschar *lognl;
 uschar *sender_info = US"";
 uschar *smtp_code;
 uschar *lognl;
 uschar *sender_info = US"";
@@ -1868,46 +2049,16 @@ 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;
 
-/* Set the default SMTP code */
+/* Set the default SMTP code, and allow a user message to change it. */
 
 smtp_code = (rc != FAIL)? US"451" : acl_wherecodes[where];
 
 smtp_code = (rc != FAIL)? US"451" : acl_wherecodes[where];
-
-/* Check a user message for starting with a response code and optionally an
-extended status code. If found, check that the first digit is valid, and if so,
-use it instead of the default code. */
-
-if (user_msg != NULL)
-  {
-  int n = pcre_exec(regex_smtp_code, NULL, CS user_msg, Ustrlen(user_msg), 0,
-    PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int));
-  if (n >= 0)
-    {
-    if (user_msg[0] != smtp_code[0])
-      {
-      log_write(0, LOG_MAIN|LOG_PANIC, "configured error code starts with "
-        "incorrect digit (expected %c) in \"%s\"", smtp_code[0], user_msg);
-
-      /* If log_msg == user_msg (the default set in acl.c if no log message is
-      specified, we must adjust the log message to show the code that is
-      actually going to be used. */
-
-      if (log_msg == user_msg)
-        log_msg = string_sprintf("%s %s", smtp_code, log_msg + ovector[1]);
-      }
-    else
-      {
-      smtp_code = user_msg;
-      codelen = ovector[1];    /* Includes final space */
-      }
-    user_msg += ovector[1];    /* Chop the code off the message */
-    }
-  }
+smtp_message_code(&smtp_code, &codelen, &user_msg, &log_msg);
 
 /* We used to have sender_address here; however, there was a bug that was not
 updating sender_address after a rewrite during a verify. When this bug was
 
 /* We used to have sender_address here; however, there was a bug that was not
 updating sender_address after a rewrite during a verify. When this bug was
@@ -2156,6 +2307,33 @@ return yield;
 
 
 
 
 
 
+/*************************************************
+*        Send user response message              *
+*************************************************/
+
+/* This function is passed a default response code and a user message. It calls
+smtp_message_code() to check and possibly modify the response code, and then
+calls smtp_respond() to transmit the response. I put this into a function
+just to avoid a lot of repetition.
+
+Arguments:
+  code         the response code
+  user_msg     the user message
+
+Returns:       nothing
+*/
+
+static void
+smtp_user_msg(uschar *code, uschar *user_msg)
+{
+int len = 3;
+smtp_message_code(&code, &len, &user_msg, NULL);
+smtp_respond(code, len, TRUE, user_msg);
+}
+
+
+
+
 /*************************************************
 *       Initialize for SMTP incoming message     *
 *************************************************/
 /*************************************************
 *       Initialize for SMTP incoming message     *
 *************************************************/
@@ -2226,7 +2404,8 @@ while (done <= 0)
   uschar *etrn_command;
   uschar *etrn_serialize_key;
   uschar *errmess;
   uschar *etrn_command;
   uschar *etrn_serialize_key;
   uschar *errmess;
-  uschar *user_msg, *log_msg;
+  uschar *log_msg, *smtp_code;
+  uschar *user_msg = NULL;
   uschar *recipient = NULL;
   uschar *hello = NULL;
   uschar *set_id = NULL;
   uschar *recipient = NULL;
   uschar *hello = NULL;
   uschar *set_id = NULL;
@@ -2257,6 +2436,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;
 
@@ -2293,8 +2473,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 != '_')
         {
@@ -2302,16 +2482,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
@@ -2347,7 +2527,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> */
@@ -2449,11 +2629,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;
 
@@ -2464,7 +2646,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);
 
@@ -2494,7 +2676,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;
@@ -2563,26 +2745,11 @@ while (done <= 0)
         }
       }
 
         }
       }
 
-    /* The EHLO/HELO command is acceptable. Reset the protocol and the state,
-    abandoning any previous message. */
-
-    received_protocol = (esmtp?
-      protocols[pextend +
-        ((sender_host_authenticated != NULL)? pauthed : 0) +
-        ((tls_active >= 0)? pcrpted : 0)]
-      :
-      protocols[pnormal + ((tls_active >= 0)? pcrpted : 0)])
-      +
-      ((sender_host_address != NULL)? pnlocal : 0);
-
-    smtp_reset(reset_point);
-    toomany = FALSE;
-
-    /* Generate an OK reply, including the ident if present, and also
-    the IP address if present. Reflecting back the ident is intended
-    as a deterrent to mail forgers. For maximum efficiency, and also
-    because some broken systems expect each response to be in a single
-    packet, arrange that it is sent in one write(). */
+    /* Generate an OK reply. The default string includes the ident if present,
+    and also the IP address if present. Reflecting back the ident is intended
+    as a deterrent to mail forgers. For maximum efficiency, and also because
+    some broken systems expect each response to be in a single packet, arrange
+    that the entire reply is sent in one write(). */
 
     auth_advertised = FALSE;
     pipelining_advertised = FALSE;
 
     auth_advertised = FALSE;
     pipelining_advertised = FALSE;
@@ -2590,21 +2757,46 @@ while (done <= 0)
     tls_advertised = FALSE;
     #endif
 
     tls_advertised = FALSE;
     #endif
 
-    s = string_sprintf("250 %s Hello %s%s%s",
-      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);
+    smtp_code = US"250 ";        /* Default response code plus space*/
+    if (user_msg == NULL)
+      {
+      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;
+      ptr = Ustrlen(s);
+      size = ptr + 1;
 
 
-    if (sender_host_address != NULL)
+      if (sender_host_address != NULL)
+        {
+        s = string_cat(s, &size, &ptr, US" [", 2);
+        s = string_cat(s, &size, &ptr, sender_host_address,
+          Ustrlen(sender_host_address));
+        s = string_cat(s, &size, &ptr, US"]", 1);
+        }
+      }
+
+    /* A user-supplied EHLO greeting may not contain more than one line. Note
+    that the code returned by smtp_message_code() includes the terminating
+    whitespace character. */
+
+    else
       {
       {
-      s = string_cat(s, &size, &ptr, US" [", 2);
-      s = string_cat(s, &size, &ptr, sender_host_address,
-        Ustrlen(sender_host_address));
-      s = string_cat(s, &size, &ptr, US"]", 1);
+      char *ss;
+      int codelen = 4;
+      smtp_message_code(&smtp_code, &codelen, &user_msg, NULL);
+      s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg);
+      if ((ss = strpbrk(CS s, "\r\n")) != NULL)
+        {
+        log_write(0, LOG_MAIN|LOG_PANIC, "EHLO/HELO response must not contain "
+          "newlines: message truncated: %s", string_printing(s));
+        *ss = 0;
+        }
+      ptr = Ustrlen(s);
+      size = ptr + 1;
       }
 
     s = string_cat(s, &size, &ptr, US"\r\n", 2);
       }
 
     s = string_cat(s, &size, &ptr, US"\r\n", 2);
@@ -2624,12 +2816,14 @@ while (done <= 0)
 
       if (thismessage_size_limit > 0)
         {
 
       if (thismessage_size_limit > 0)
         {
-        sprintf(CS big_buffer, "250-SIZE %d\r\n", thismessage_size_limit);
+        sprintf(CS big_buffer, "%.3s-SIZE %d\r\n", smtp_code,
+          thismessage_size_limit);
         s = string_cat(s, &size, &ptr, big_buffer, Ustrlen(big_buffer));
         }
       else
         {
         s = string_cat(s, &size, &ptr, big_buffer, Ustrlen(big_buffer));
         }
       else
         {
-        s = string_cat(s, &size, &ptr, US"250-SIZE\r\n", 10);
+        s = string_cat(s, &size, &ptr, smtp_code, 3);
+        s = string_cat(s, &size, &ptr, US"-SIZE\r\n", 7);
         }
 
       /* Exim does not do protocol conversion or data conversion. It is 8-bit
         }
 
       /* Exim does not do protocol conversion or data conversion. It is 8-bit
@@ -2640,14 +2834,18 @@ while (done <= 0)
       provided as an option. */
 
       if (accept_8bitmime)
       provided as an option. */
 
       if (accept_8bitmime)
-        s = string_cat(s, &size, &ptr, US"250-8BITMIME\r\n", 14);
+        {
+        s = string_cat(s, &size, &ptr, smtp_code, 3);
+        s = string_cat(s, &size, &ptr, US"-8BITMIME\r\n", 11);
+        }
 
       /* Advertise ETRN if there's an ACL checking whether a host is
       permitted to issue it; a check is made when any host actually tries. */
 
       if (acl_smtp_etrn != NULL)
         {
 
       /* Advertise ETRN if there's an ACL checking whether a host is
       permitted to issue it; a check is made when any host actually tries. */
 
       if (acl_smtp_etrn != NULL)
         {
-        s = string_cat(s, &size, &ptr, US"250-ETRN\r\n", 10);
+        s = string_cat(s, &size, &ptr, smtp_code, 3);
+        s = string_cat(s, &size, &ptr, US"-ETRN\r\n", 7);
         }
 
       /* Advertise EXPN if there's an ACL checking whether a host is
         }
 
       /* Advertise EXPN if there's an ACL checking whether a host is
@@ -2655,15 +2853,18 @@ while (done <= 0)
 
       if (acl_smtp_expn != NULL)
         {
 
       if (acl_smtp_expn != NULL)
         {
-        s = string_cat(s, &size, &ptr, US"250-EXPN\r\n", 10);
+        s = string_cat(s, &size, &ptr, smtp_code, 3);
+        s = string_cat(s, &size, &ptr, US"-EXPN\r\n", 7);
         }
 
       /* 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, US"250-PIPELINING\r\n", 16);
+        s = string_cat(s, &size, &ptr, smtp_code, 3);
+        s = string_cat(s, &size, &ptr, US"-PIPELINING\r\n", 13);
         sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
         pipelining_advertised = TRUE;
         }
         sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
         pipelining_advertised = TRUE;
         }
@@ -2693,7 +2894,8 @@ while (done <= 0)
               int saveptr;
               if (first)
                 {
               int saveptr;
               if (first)
                 {
-                s = string_cat(s, &size, &ptr, US"250-AUTH", 8);
+                s = string_cat(s, &size, &ptr, smtp_code, 3);
+                s = string_cat(s, &size, &ptr, US"-AUTH", 5);
                 first = FALSE;
                 auth_advertised = TRUE;
                 }
                 first = FALSE;
                 auth_advertised = TRUE;
                 }
@@ -2719,14 +2921,16 @@ while (done <= 0)
       if (tls_active < 0 &&
           verify_check_host(&tls_advertise_hosts) != FAIL)
         {
       if (tls_active < 0 &&
           verify_check_host(&tls_advertise_hosts) != FAIL)
         {
-        s = string_cat(s, &size, &ptr, US"250-STARTTLS\r\n", 14);
+        s = string_cat(s, &size, &ptr, smtp_code, 3);
+        s = string_cat(s, &size, &ptr, US"-STARTTLS\r\n", 11);
         tls_advertised = TRUE;
         }
       #endif
 
       /* Finish off the multiline reply with one that is always available. */
 
         tls_advertised = TRUE;
         }
       #endif
 
       /* Finish off the multiline reply with one that is always available. */
 
-      s = string_cat(s, &size, &ptr, US"250 HELP\r\n", 10);
+      s = string_cat(s, &size, &ptr, smtp_code, 3);
+      s = string_cat(s, &size, &ptr, US" HELP\r\n", 7);
       }
 
     /* Terminate the string (for debug), write it, and note that HELO/EHLO
       }
 
     /* Terminate the string (for debug), write it, and note that HELO/EHLO
@@ -2747,6 +2951,20 @@ while (done <= 0)
       debug_printf("SMTP>> %s", s);
       }
     helo_seen = TRUE;
       debug_printf("SMTP>> %s", s);
       }
     helo_seen = TRUE;
+
+    /* Reset the protocol and the state, abandoning any previous message. */
+
+    received_protocol = (esmtp?
+      protocols[pextend +
+        ((sender_host_authenticated != NULL)? pauthed : 0) +
+        ((tls_active >= 0)? pcrpted : 0)]
+      :
+      protocols[pnormal + ((tls_active >= 0)? pcrpted : 0)])
+      +
+      ((sender_host_address != NULL)? pnlocal : 0);
+
+    smtp_reset(reset_point);
+    toomany = FALSE;
     break;   /* HELO/EHLO */
 
 
     break;   /* HELO/EHLO */
 
 
@@ -2757,6 +2975,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 */
 
@@ -2775,7 +2994,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");
@@ -2934,8 +3153,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 =
@@ -2945,7 +3164,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;
       }
 
@@ -3005,7 +3224,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",
@@ -3024,12 +3243,12 @@ while (done <= 0)
 
     if (rc == OK || rc == DISCARD)
       {
 
     if (rc == OK || rc == DISCARD)
       {
-      smtp_printf("250 OK\r\n");
+      if (user_msg == NULL) smtp_printf("250 OK\r\n");
+        else smtp_user_msg(US"250", user_msg);
       smtp_delay_rcpt = smtp_rlr_base;
       recipients_discarded = (rc == DISCARD);
       was_rej_mail = FALSE;
       }
       smtp_delay_rcpt = smtp_rlr_base;
       recipients_discarded = (rc == DISCARD);
       was_rej_mail = FALSE;
       }
-
     else
       {
       done = smtp_handle_acl_fail(ACL_WHERE_MAIL, rc, user_msg, log_msg);
     else
       {
       done = smtp_handle_acl_fail(ACL_WHERE_MAIL, rc, user_msg, log_msg);
@@ -3046,6 +3265,7 @@ while (done <= 0)
     extracted address. */
 
     case RCPT_CMD:
     extracted address. */
 
     case RCPT_CMD:
+    HAD(SCH_RCPT);
     rcpt_count++;
     was_rcpt = TRUE;
 
     rcpt_count++;
     was_rcpt = TRUE;
 
@@ -3073,7 +3293,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");
@@ -3085,8 +3305,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,
@@ -3095,7 +3315,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;
       }
@@ -3125,7 +3345,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),
@@ -3184,7 +3404,8 @@ while (done <= 0)
 
     if (rc == OK)
       {
 
     if (rc == OK)
       {
-      smtp_printf("250 Accepted\r\n");
+      if (user_msg == NULL) smtp_printf("250 Accepted\r\n");
+        else smtp_user_msg(US"250", user_msg);
       receive_add_recipient(recipient, -1);
       }
 
       receive_add_recipient(recipient, -1);
       }
 
@@ -3192,7 +3413,8 @@ while (done <= 0)
 
     else if (rc == DISCARD)
       {
 
     else if (rc == DISCARD)
       {
-      smtp_printf("250 Accepted\r\n");
+      if (user_msg == NULL) smtp_printf("250 Accepted\r\n");
+        else smtp_user_msg(US"250", user_msg);
       rcpt_fail_count++;
       discarded = TRUE;
       log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> rejected RCPT %s: "
       rcpt_fail_count++;
       discarded = TRUE;
       log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> rejected RCPT %s: "
@@ -3231,6 +3453,7 @@ while (done <= 0)
     because it is the same whether pipelining is in use or not. */
 
     case DATA_CMD:
     because it is the same whether pipelining is in use or not. */
 
     case DATA_CMD:
+    HAD(SCH_DATA);
     if (!discarded && recipients_count <= 0)
       {
       if (pipelining_advertised && last_was_rcpt)
     if (!discarded && recipients_count <= 0)
       {
       if (pipelining_advertised && last_was_rcpt)
@@ -3259,7 +3482,9 @@ while (done <= 0)
 
     if (rc == OK)
       {
 
     if (rc == OK)
       {
-      smtp_printf("354 Enter message, ending with \".\" on a line by itself\r\n");
+      if (user_msg == NULL)
+        smtp_printf("354 Enter message, ending with \".\" on a line by itself\r\n");
+      else smtp_user_msg(US"354", user_msg);
       done = 3;
       message_ended = END_NOTENDED;   /* Indicate in middle of data */
       }
       done = 3;
       message_ended = END_NOTENDED;   /* Indicate in middle of data */
       }
@@ -3273,6 +3498,7 @@ while (done <= 0)
 
 
     case VRFY_CMD:
 
 
     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);
@@ -3282,7 +3508,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 */
 
@@ -3320,6 +3546,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);
@@ -3327,7 +3554,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;
@@ -3339,6 +3566,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,
@@ -3377,7 +3605,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;
@@ -3452,6 +3681,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)
@@ -3461,12 +3691,11 @@ while (done <= 0)
         log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
           log_msg);
       }
         log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
           log_msg);
       }
-    else user_msg = NULL;
 
     if (user_msg == NULL)
       smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
     else
 
     if (user_msg == NULL)
       smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
     else
-      smtp_printf("221 %s\r\n", user_msg);
+      smtp_respond(US"221", 3, TRUE, user_msg);
 
     #ifdef SUPPORT_TLS
     tls_close(TRUE);
 
     #ifdef SUPPORT_TLS
     tls_close(TRUE);
@@ -3479,6 +3708,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;
@@ -3488,6 +3718,7 @@ 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;
 
@@ -3497,6 +3728,7 @@ while (done <= 0)
     permitted hosts. */
 
     case HELP_CMD:
     permitted hosts. */
 
     case HELP_CMD:
+    HAD(SCH_HELP);
     smtp_printf("214-Commands supported:\r\n");
       {
       uschar buffer[256];
     smtp_printf("214-Commands supported:\r\n");
       {
       uschar buffer[256];
@@ -3538,6 +3770,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,
@@ -3557,7 +3790,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,
@@ -3569,7 +3802,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;
@@ -3586,7 +3819,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 #");
@@ -3594,7 +3827,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. */
@@ -3606,7 +3839,8 @@ while (done <= 0)
         debug_printf("ETRN command is: %s\n", etrn_command);
         debug_printf("ETRN command execution skipped\n");
         }
         debug_printf("ETRN command is: %s\n", etrn_command);
         debug_printf("ETRN command execution skipped\n");
         }
-      smtp_printf("250 OK\r\n");
+      if (user_msg == NULL) smtp_printf("250 OK\r\n");
+        else smtp_user_msg(US"250", user_msg);
       break;
       }
 
       break;
       }
 
@@ -3616,7 +3850,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;
       }
 
@@ -3682,7 +3916,11 @@ while (done <= 0)
       smtp_printf("458 Unable to fork process\r\n");
       if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
       }
       smtp_printf("458 Unable to fork process\r\n");
       if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
       }
-    else smtp_printf("250 OK\r\n");
+    else
+      {
+      if (user_msg == NULL) smtp_printf("250 OK\r\n");
+        else smtp_user_msg(US"250", user_msg);
+      }
 
     signal(SIGCHLD, oldsignal);
     break;
 
     signal(SIGCHLD, oldsignal);
     break;
@@ -3722,10 +3960,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;