Logging: dnssec status on accept & delivery lines
[exim.git] / src / src / smtp_in.c
index cf0a5d64276e32623dd6f26af3d46da4e511d092..d66b59e302363016e5a24da5db23cdcaea190136 100644 (file)
@@ -2,13 +2,14 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
 
 
 #include "exim.h"
+#include <assert.h>
 
 
 /* Initialize for TCP wrappers if so configured. It appears that the macro
@@ -36,10 +37,10 @@ uschar *tcp_wrappers_name;
 /* Size of buffer for reading SMTP commands. We used to use 512, as defined
 by RFC 821. However, RFC 1869 specifies that this must be increased for SMTP
 commands that accept arguments, and this in particular applies to AUTH, where
-the data can be quite long.  More recently this value was 2048 in Exim; 
+the data can be quite long.  More recently this value was 2048 in Exim;
 however, RFC 4954 (circa 2007) recommends 12288 bytes to handle AUTH.  Clients
-such as Thunderbird will send an AUTH with an initial-response for GSSAPI. 
-The maximum size of a Kerberos ticket under Windows 2003 is 12000 bytes, and 
+such as Thunderbird will send an AUTH with an initial-response for GSSAPI.
+The maximum size of a Kerberos ticket under Windows 2003 is 12000 bytes, and
 we need room to handle large base64-encoded AUTHs for GSSAPI.
 */
 
@@ -95,7 +96,7 @@ enum {
 
   QUIT_CMD, HELP_CMD,
 
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
   PROXY_FAIL_IGNORE_CMD,
 #endif
 
@@ -134,7 +135,7 @@ static BOOL rcpt_smtp_response_same;
 static BOOL rcpt_in_progress;
 static int  nonmail_command_count;
 static BOOL smtp_exit_function_called = 0;
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
 static BOOL smtputf8_advertised;
 #endif
 static int  synprot_error_count;
@@ -232,15 +233,15 @@ static uschar *protocols[] = {
 
 /* Sanity check and validate optional args to MAIL FROM: envelope */
 enum {
+  ENV_MAIL_OPT_NULL,
   ENV_MAIL_OPT_SIZE, ENV_MAIL_OPT_BODY, ENV_MAIL_OPT_AUTH,
 #ifndef DISABLE_PRDR
   ENV_MAIL_OPT_PRDR,
 #endif
   ENV_MAIL_OPT_RET, ENV_MAIL_OPT_ENVID,
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
   ENV_MAIL_OPT_UTF8,
 #endif
-  ENV_MAIL_OPT_NULL
   };
 typedef struct {
   uschar *   name;  /* option requested during MAIL cmd */
@@ -257,10 +258,11 @@ static env_mail_type_t env_mail_type_list[] = {
 #endif
     { US"RET",    ENV_MAIL_OPT_RET,    TRUE },
     { US"ENVID",  ENV_MAIL_OPT_ENVID,  TRUE },
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
     { US"SMTPUTF8",ENV_MAIL_OPT_UTF8,  FALSE },                /* rfc6531 */
 #endif
-    { US"NULL",   ENV_MAIL_OPT_NULL,   FALSE }
+    /* keep this the last entry */
+    { US"NULL",   ENV_MAIL_OPT_NULL,   FALSE },
   };
 
 /* When reading SMTP from a remote host, we have to use our own versions of the
@@ -581,7 +583,7 @@ exim_exit(EXIT_FAILURE);
 
 
 
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
 /*************************************************
 *     Restore socket timeout to previous value   *
 *************************************************/
@@ -618,7 +620,7 @@ int rc;
 /* Cannot configure local connection as a proxy inbound */
 if (sender_host_address == NULL) return proxy_session;
 
-rc = verify_check_this_host(&proxy_required_hosts, NULL, NULL,
+rc = verify_check_this_host(CUSS &hosts_proxy, NULL, NULL,
                            sender_host_address, NULL);
 if (rc == OK)
   {
@@ -759,10 +761,10 @@ if (ret >= 16 &&
             DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype);
             return ERRNO_PROXYFAIL;
             }
-          proxy_host_address  = sender_host_address;
+          proxy_local_address = sender_host_address;
           sender_host_address = string_copy(US tmpip);
           tmpport             = ntohs(hdr.v2.addr.ip4.src_port);
-          proxy_host_port     = sender_host_port;
+          proxy_local_port    = sender_host_port;
           sender_host_port    = tmpport;
           /* Save dest ip/port */
           tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.dst_addr;
@@ -772,9 +774,9 @@ if (ret >= 16 &&
             DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype);
             return ERRNO_PROXYFAIL;
             }
-          proxy_target_address = string_copy(US tmpip);
+          proxy_external_address = string_copy(US tmpip);
           tmpport              = ntohs(hdr.v2.addr.ip4.dst_port);
-          proxy_target_port    = tmpport;
+          proxy_external_port  = tmpport;
           goto done;
         case 0x21:  /* TCPv6 address type */
           iptype = US"IPv6";
@@ -785,10 +787,10 @@ if (ret >= 16 &&
             DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype);
             return ERRNO_PROXYFAIL;
             }
-          proxy_host_address  = sender_host_address;
+          proxy_local_address = sender_host_address;
           sender_host_address = string_copy(US tmpip6);
           tmpport             = ntohs(hdr.v2.addr.ip6.src_port);
-          proxy_host_port     = sender_host_port;
+          proxy_local_port    = sender_host_port;
           sender_host_port    = tmpport;
           /* Save dest ip/port */
           memmove(tmpaddr6.sin6_addr.s6_addr, hdr.v2.addr.ip6.dst_addr, 16);
@@ -798,9 +800,9 @@ if (ret >= 16 &&
             DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype);
             return ERRNO_PROXYFAIL;
             }
-          proxy_target_address = string_copy(US tmpip6);
+          proxy_external_address = string_copy(US tmpip6);
           tmpport              = ntohs(hdr.v2.addr.ip6.dst_port);
-          proxy_target_port    = tmpport;
+          proxy_external_port  = tmpport;
           goto done;
         default:
           DEBUG(D_receive)
@@ -879,7 +881,7 @@ else if (ret >= 8 &&
       debug_printf("Proxied src arg is not an %s address\n", iptype);
     goto proxyfail;
     }
-  proxy_host_address = sender_host_address;
+  proxy_local_address = sender_host_address;
   sender_host_address = p;
   p = sp + 1;
   if ((sp = Ustrchr(p, ' ')) == NULL)
@@ -895,7 +897,7 @@ else if (ret >= 8 &&
       debug_printf("Proxy dest arg is not an %s address\n", iptype);
     goto proxyfail;
     }
-  proxy_target_address = p;
+  proxy_external_address = p;
   p = sp + 1;
   if ((sp = Ustrchr(p, ' ')) == NULL)
     {
@@ -910,7 +912,7 @@ else if (ret >= 8 &&
       debug_printf("Proxied src port '%s' not an integer\n", p);
     goto proxyfail;
     }
-  proxy_host_port = sender_host_port;
+  proxy_local_port = sender_host_port;
   sender_host_port = tmp_port;
   p = sp + 1;
   if ((sp = Ustrchr(p, '\0')) == NULL)
@@ -925,7 +927,7 @@ else if (ret >= 8 &&
       debug_printf("Proxy dest port '%s' not an integer\n", p);
     goto proxyfail;
     }
-  proxy_target_port = tmp_port;
+  proxy_external_port = tmp_port;
   /* Already checked for /r /n above. Good V1 header received. */
   goto done;
   }
@@ -1023,7 +1025,7 @@ if required. */
 
 for (p = cmd_list; p < cmd_list_end; p++)
   {
-  #ifdef EXPERIMENTAL_PROXY
+  #ifdef SUPPORT_PROXY
   /* Only allow QUIT command if Proxy Protocol parsing failed */
   if (proxy_session && proxy_session_failed)
     {
@@ -1080,7 +1082,7 @@ for (p = cmd_list; p < cmd_list_end; p++)
     }
   }
 
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
 /* Only allow QUIT command if Proxy Protocol parsing failed */
 if (proxy_session && proxy_session_failed)
   return PROXY_FAIL_IGNORE_CMD;
@@ -1232,8 +1234,7 @@ if (sender_host_unknown || sender_host_notsocket)
 if (is_inetd)
   return string_sprintf("SMTP connection from %s (via inetd)", hostname);
 
-if ((log_extra_selector & LX_incoming_interface) != 0 &&
-     interface_address != NULL)
+if (LOGGING(incoming_interface) && interface_address != NULL)
   return string_sprintf("SMTP connection from %s I=[%s]:%d", hostname,
     interface_address, interface_port);
 
@@ -1258,16 +1259,15 @@ s_tlslog(uschar * s, int * sizep, int * ptrp)
   int size = sizep ? *sizep : 0;
   int ptr = ptrp ? *ptrp : 0;
 
-  if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL)
+  if (LOGGING(tls_cipher) && tls_in.cipher != NULL)
     s = string_append(s, &size, &ptr, 2, US" X=", tls_in.cipher);
-  if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
-       tls_in.cipher != NULL)
+  if (LOGGING(tls_certificate_verified) && tls_in.cipher != NULL)
     s = string_append(s, &size, &ptr, 2, US" CV=",
       tls_in.certificate_verified? "yes":"no");
-  if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL)
+  if (LOGGING(tls_peerdn) && tls_in.peerdn != NULL)
     s = string_append(s, &size, &ptr, 3, US" DN=\"",
       string_printing(tls_in.peerdn), US"\"");
-  if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL)
+  if (LOGGING(tls_sni) && tls_in.sni != NULL)
     s = string_append(s, &size, &ptr, 3, US" SNI=\"",
       string_printing(tls_in.sni), US"\"");
 
@@ -1299,7 +1299,7 @@ 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)
+if (smtp_mailcmd_count > 0 || !LOGGING(smtp_no_mail))
   return;
 
 s = NULL;
@@ -1544,7 +1544,7 @@ spf_received = NULL;
 spf_result = NULL;
 spf_smtp_comment = NULL;
 #endif
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
 message_smtputf8 = FALSE;
 #endif
 body_linecount = body_zerocount = 0;
@@ -1866,8 +1866,6 @@ pipelining_enable = TRUE;
 sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
 smtp_exit_function_called = FALSE;    /* For avoiding loop in not-quit exit */
 
-memset(sender_host_cache, 0, sizeof(sender_host_cache));
-
 /* If receiving by -bs from a trusted user, or testing with -bh, we allow
 authentication settings from -oMaa to remain in force. */
 
@@ -1882,7 +1880,7 @@ tls_in.ocsp = OCSP_NOT_REQ;
 tls_advertised = FALSE;
 #endif
 dsn_advertised = FALSE;
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
 smtputf8_advertised = FALSE;
 #endif
 
@@ -2311,7 +2309,7 @@ if (!sender_host_unknown)
 
 if (smtp_batched_input) return TRUE;
 
-#ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
 /* If valid Proxy Protocol source is connecting, set up session.
  * Failure will not allow any SMTP function other than QUIT. */
 proxy_session = FALSE;
@@ -2355,10 +2353,9 @@ code = US"220";   /* Default status code */
 esc = US"";       /* Default extended status code */
 esclen = 0;       /* Length of esc */
 
-if (user_msg == NULL)
+if (!user_msg)
   {
-  s = expand_string(smtp_banner);
-  if (s == NULL)
+  if (!(s = expand_string(smtp_banner)))
     log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" (smtp_banner) "
       "failed: %s", smtp_banner, expand_string_message);
   }
@@ -2366,7 +2363,7 @@ else
   {
   int codelen = 3;
   s = user_msg;
-  smtp_message_code(&code, &codelen, &s, NULL);
+  smtp_message_code(&code, &codelen, &s, NULL, TRUE);
   if (codelen > 4)
     {
     esc = code + 4;
@@ -2397,20 +2394,20 @@ do       /* At least once, in case we have an empty string */
   {
   int len;
   uschar *linebreak = Ustrchr(p, '\n');
-  ss = string_cat(ss, &size, &ptr, code, 3);
+  ss = string_catn(ss, &size, &ptr, code, 3);
   if (linebreak == NULL)
     {
     len = Ustrlen(p);
-    ss = string_cat(ss, &size, &ptr, US" ", 1);
+    ss = string_catn(ss, &size, &ptr, US" ", 1);
     }
   else
     {
     len = linebreak - p;
-    ss = string_cat(ss, &size, &ptr, US"-", 1);
+    ss = string_catn(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);
+  ss = string_catn(ss, &size, &ptr, esc, esclen);
+  ss = string_catn(ss, &size, &ptr, p, len);
+  ss = string_catn(ss, &size, &ptr, US"\r\n", 2);
   p += len;
   if (linebreak != NULL) p++;
   }
@@ -2508,8 +2505,8 @@ static void
 incomplete_transaction_log(uschar *what)
 {
 if (sender_address == NULL ||                 /* No transaction in progress */
-    (log_write_selector & L_smtp_incomplete_transaction) == 0  /* Not logging */
-  return;
+    !LOGGING(smtp_incomplete_transaction))
+  return;
 
 /* Build list of recipients for logging */
 
@@ -2628,23 +2625,24 @@ Arguments:
   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
+  check_valid   if true, verify the response code
 
 Returns:        nothing
 */
 
 void
-smtp_message_code(uschar **code, int *codelen, uschar **msg, uschar **log_msg)
+smtp_message_code(uschar **code, int *codelen, uschar **msg, uschar **log_msg,
+  BOOL check_valid)
 {
 int n;
 int ovector[3];
 
-if (msg == NULL || *msg == NULL) return;
+if (!msg || !*msg) 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 ((n = pcre_exec(regex_smtp_code, NULL, CS *msg, Ustrlen(*msg), 0,
+  PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int))) < 0) return;
 
-if ((*msg)[0] != (*code)[0])
+if (check_valid && (*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);
@@ -2679,18 +2677,19 @@ defaults disabled in Exim. However, discussion in connection with RFC 821bis
 (aka RFC 2821) has concluded that the response should be 252 in the disabled
 state, because there are broken clients that try VRFY before RCPT. A 5xx
 response should be given only when the address is positively known to be
-undeliverable. Sigh. Also, for ETRN, 458 is given on refusal, and for AUTH,
-503.
+undeliverable. Sigh. We return 252 if there is no VRFY ACL or it provides
+no explicit code, but if there is one we let it know best.
+Also, for ETRN, 458 is given on refusal, and for AUTH, 503.
 
 From Exim 4.63, it is possible to override the response code details by
 providing a suitable response code string at the start of the message provided
 in user_msg. The code's first digit is checked for validity.
 
 Arguments:
-  where      where the ACL was called from
-  rc         the failure code
-  user_msg   a message that can be included in an SMTP response
-  log_msg    a message for logging
+  where        where the ACL was called from
+  rc           the failure code
+  user_msg     a message that can be included in an SMTP response
+  log_msg      a message for logging
 
 Returns:     0 in most cases
              2 if the failure code was FAIL_DROP, in which case the
@@ -2723,8 +2722,9 @@ if (drop) rc = FAIL;
 
 /* Set the default SMTP code, and allow a user message to change it. */
 
-smtp_code = (rc != FAIL)? US"451" : acl_wherecodes[where];
-smtp_message_code(&smtp_code, &codelen, &user_msg, &log_msg);
+smtp_code = rc == FAIL ? acl_wherecodes[where] : US"451";
+smtp_message_code(&smtp_code, &codelen, &user_msg, &log_msg,
+  where != ACL_WHERE_VRFY);
 
 /* 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
@@ -2760,7 +2760,7 @@ if (sender_verified_failed != NULL &&
 
   setflag(sender_verified_failed, af_sverify_told);
 
-  if (rc != FAIL || (log_extra_selector & LX_sender_verify_fail) != 0)
+  if (rc != FAIL || LOGGING(sender_verify_fail))
     log_write(0, LOG_MAIN|LOG_REJECT, "%s sender verify %s for <%s>%s",
       host_and_ident(TRUE),
       ((sender_verified_failed->special_action & 255) == DEFER)? "defer":"fail",
@@ -2837,14 +2837,18 @@ is closing if required and return 2.  */
 if (log_reject_target != 0)
   {
 #ifdef SUPPORT_TLS
-  uschar * s = s_tlslog(NULL, NULL, NULL);
-  if (!s) s = US"";
+  uschar * tls = s_tlslog(NULL, NULL, NULL);
+  if (!tls) tls = US"";
 #else
-  uschar * s = US"";
+  uschar * tls = US"";
 #endif
-  log_write(0, log_reject_target, "%s%s %s%srejected %s%s",
-    host_and_ident(TRUE), s,
-    sender_info, (rc == FAIL)? US"" : US"temporarily ", what, log_msg);
+  log_write(0, log_reject_target, "%s%s%s %s%srejected %s%s",
+    LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"",
+    host_and_ident(TRUE),
+    tls,
+    sender_info,
+    rc == FAIL ? US"" : US"temporarily ",
+    what, log_msg);
   }
 
 if (!drop) return 0;
@@ -3098,7 +3102,7 @@ static void
 smtp_user_msg(uschar *code, uschar *user_msg)
 {
 int len = 3;
-smtp_message_code(&code, &len, &user_msg, NULL);
+smtp_message_code(&code, &len, &user_msg, NULL, TRUE);
 smtp_respond(code, len, TRUE, user_msg);
 }
 
@@ -3293,7 +3297,7 @@ while (done <= 0)
   pid_t pid;
   int start, end, sender_domain, recipient_domain;
   int ptr, size, rc;
-  int c, i;
+  int c;
   auth_instance *au;
   uschar *orcpt = NULL;
   int flags;
@@ -3307,14 +3311,13 @@ while (done <= 0)
      )
     {
     cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = FALSE;
-    if (acl_smtp_auth)
+    if (  acl_smtp_auth
+       && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
+                 &user_msg, &log_msg)) != OK
+       )
       {
-      rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth, &user_msg, &log_msg);
-      if (rc != OK)
-        {
-        done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
-        continue;
-        }
+      done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
+      continue;
       }
 
     for (au = auths; au; au = au->next)
@@ -3322,12 +3325,10 @@ while (done <= 0)
        {
        smtp_cmd_data = NULL;
 
-       if ((c = smtp_in_auth(au, &s, &ss)) != OK)
-         log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
-           au->name, host_and_ident(FALSE), ss);
+       if (smtp_in_auth(au, &s, &ss) == OK)
+         { DEBUG(D_auth) debug_printf("tls auth succeeded\n"); }
        else
-         DEBUG(D_auth) debug_printf("tls auth succeeded\n");
-
+         { DEBUG(D_auth) debug_printf("tls auth not succeeded\n"); }
        break;
        }
     }
@@ -3375,14 +3376,13 @@ while (done <= 0)
 
     /* Check the ACL */
 
-    if (acl_smtp_auth)
+    if (  acl_smtp_auth
+       && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
+                 &user_msg, &log_msg)) != OK
+       )
       {
-      rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth, &user_msg, &log_msg);
-      if (rc != OK)
-        {
-        done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
-        break;
-        }
+      done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
+      break;
       }
 
     /* Find the name of the requested authentication mechanism. */
@@ -3554,10 +3554,9 @@ while (done <= 0)
     /* 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)
-      {
-      rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo, &user_msg, &log_msg);
-      if (rc != OK)
+    if (acl_smtp_helo)
+      if ((rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo,
+               &user_msg, &log_msg)) != OK)
         {
         done = smtp_handle_acl_fail(ACL_WHERE_HELO, rc, user_msg, log_msg);
         sender_helo_name = NULL;
@@ -3565,7 +3564,6 @@ while (done <= 0)
         break;
         }
       else if (!check_sync()) goto SYNC_FAILURE;
-      }
 
     /* 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
@@ -3579,7 +3577,7 @@ while (done <= 0)
     tls_advertised = FALSE;
 #endif
     dsn_advertised = FALSE;
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
     smtputf8_advertised = FALSE;
 #endif
 
@@ -3598,10 +3596,9 @@ while (done <= 0)
 
       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);
+        s = string_catn(s, &size, &ptr, US" [", 2);
+        s = string_cat (s, &size, &ptr, sender_host_address);
+        s = string_catn(s, &size, &ptr, US"]", 1);
         }
       }
 
@@ -3613,7 +3610,7 @@ while (done <= 0)
       {
       char *ss;
       int codelen = 4;
-      smtp_message_code(&smtp_code, &codelen, &user_msg, NULL);
+      smtp_message_code(&smtp_code, &codelen, &user_msg, NULL, TRUE);
       s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg);
       if ((ss = strpbrk(CS s, "\r\n")) != NULL)
         {
@@ -3625,7 +3622,7 @@ while (done <= 0)
       size = ptr + 1;
       }
 
-    s = string_cat(s, &size, &ptr, US"\r\n", 2);
+    s = string_catn(s, &size, &ptr, US"\r\n", 2);
 
     /* If we received EHLO, we must create a multiline response which includes
     the functions supported. */
@@ -3644,12 +3641,12 @@ while (done <= 0)
         {
         sprintf(CS big_buffer, "%.3s-SIZE %d\r\n", smtp_code,
           thismessage_size_limit);
-        s = string_cat(s, &size, &ptr, big_buffer, Ustrlen(big_buffer));
+        s = string_cat(s, &size, &ptr, big_buffer);
         }
       else
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-SIZE\r\n", 7);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-SIZE\r\n", 7);
         }
 
       /* Exim does not do protocol conversion or data conversion. It is 8-bit
@@ -3661,15 +3658,15 @@ while (done <= 0)
 
       if (accept_8bitmime)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-8BITMIME\r\n", 11);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-8BITMIME\r\n", 11);
         }
 
       /* Advertise DSN support if configured to do so. */
-      if (verify_check_host(&dsn_advertise_hosts) != FAIL) 
+      if (verify_check_host(&dsn_advertise_hosts) != FAIL)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-DSN\r\n", 6);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-DSN\r\n", 6);
         dsn_advertised = TRUE;
         }
 
@@ -3678,8 +3675,8 @@ while (done <= 0)
 
       if (acl_smtp_etrn != NULL)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-ETRN\r\n", 7);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-ETRN\r\n", 7);
         }
 
       /* Advertise EXPN if there's an ACL checking whether a host is
@@ -3687,8 +3684,8 @@ while (done <= 0)
 
       if (acl_smtp_expn != NULL)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-EXPN\r\n", 7);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-EXPN\r\n", 7);
         }
 
       /* Exim is quite happy with pipelining, so let the other end know that
@@ -3697,8 +3694,8 @@ while (done <= 0)
       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_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-PIPELINING\r\n", 13);
         sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
         pipelining_advertised = TRUE;
         }
@@ -3731,22 +3728,21 @@ while (done <= 0)
            int saveptr;
            if (first)
              {
-             s = string_cat(s, &size, &ptr, smtp_code, 3);
-             s = string_cat(s, &size, &ptr, US"-AUTH", 5);
+             s = string_catn(s, &size, &ptr, smtp_code, 3);
+             s = string_catn(s, &size, &ptr, US"-AUTH", 5);
              first = FALSE;
              auth_advertised = TRUE;
              }
            saveptr = ptr;
-           s = string_cat(s, &size, &ptr, US" ", 1);
-           s = string_cat(s, &size, &ptr, au->public_name,
-             Ustrlen(au->public_name));
+           s = string_catn(s, &size, &ptr, US" ", 1);
+           s = string_cat (s, &size, &ptr, au->public_name);
            while (++saveptr < ptr) s[saveptr] = toupper(s[saveptr]);
            au->advertised = TRUE;
            }
          else
            au->advertised = FALSE;
 
-       if (!first) s = string_cat(s, &size, &ptr, US"\r\n", 2);
+       if (!first) s = string_catn(s, &size, &ptr, US"\r\n", 2);
        }
 
       /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
@@ -3758,8 +3754,8 @@ while (done <= 0)
       if (tls_in.active < 0 &&
           verify_check_host(&tls_advertise_hosts) != FAIL)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-STARTTLS\r\n", 11);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-STARTTLS\r\n", 11);
         tls_advertised = TRUE;
         }
 #endif
@@ -3768,25 +3764,25 @@ while (done <= 0)
       /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */
       if (prdr_enable)
         {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-PRDR\r\n", 7);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-PRDR\r\n", 7);
        }
 #endif
 
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
       if (  accept_8bitmime
          && verify_check_host(&smtputf8_advertise_hosts) != FAIL)
        {
-        s = string_cat(s, &size, &ptr, smtp_code, 3);
-        s = string_cat(s, &size, &ptr, US"-SMTPUTF8\r\n", 11);
+        s = string_catn(s, &size, &ptr, smtp_code, 3);
+        s = string_catn(s, &size, &ptr, US"-SMTPUTF8\r\n", 11);
         smtputf8_advertised = TRUE;
        }
 #endif
 
       /* Finish off the multiline reply with one that is always available. */
 
-      s = string_cat(s, &size, &ptr, smtp_code, 3);
-      s = string_cat(s, &size, &ptr, US" HELP\r\n", 7);
+      s = string_catn(s, &size, &ptr, smtp_code, 3);
+      s = string_catn(s, &size, &ptr, US" HELP\r\n", 7);
       }
 
     /* Terminate the string (for debug), write it, and note that HELO/EHLO
@@ -3887,7 +3883,7 @@ while (done <= 0)
       if (!extract_option(&name, &value)) break;
 
       for (mail_args = env_mail_type_list;
-           (char *)mail_args < (char *)env_mail_type_list + sizeof(env_mail_type_list);
+           mail_args->value != ENV_MAIL_OPT_NULL;
            mail_args++
           )
         if (strcmpic(name, mail_args->name) == 0)
@@ -4013,7 +4009,7 @@ while (done <= 0)
               rc = acl_check(ACL_WHERE_MAILAUTH, NULL, acl_smtp_mailauth,
                 &user_msg, &log_msg);
               }
-  
+
             switch (rc)
               {
               case OK:
@@ -4022,23 +4018,23 @@ while (done <= 0)
                    expand_check_condition(authenticated_by->mail_auth_condition,
                        authenticated_by->name, US"authenticator"))
                  break;     /* Accept the AUTH */
-    
+
                ignore_msg = US"server_mail_auth_condition failed";
                if (authenticated_id != NULL)
                  ignore_msg = string_sprintf("%s: authenticated ID=\"%s\"",
                    ignore_msg, authenticated_id);
-  
+
               /* Fall through */
-  
+
               case FAIL:
                authenticated_sender = NULL;
                log_write(0, LOG_MAIN, "ignoring AUTH=%s from %s (%s)",
                  value, host_and_ident(TRUE), ignore_msg);
                break;
-  
+
               /* Should only get DEFER or ERROR here. Put back terminator
               overrides for error message */
-  
+
               default:
                value[-1] = '=';
                name[-1] = ' ';
@@ -4056,7 +4052,7 @@ while (done <= 0)
           break;
 #endif
 
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
         case ENV_MAIL_OPT_UTF8:
          if (smtputf8_advertised)
            {
@@ -4066,15 +4062,17 @@ while (done <= 0)
            }
          break;
 #endif
-        /* Unknown option. Stick back the terminator characters and break
+        /* No valid option. Stick back the terminator characters and break
         the loop.  Do the name-terminator second as extract_option sets
-       value==name when it found no equal-sign.
-       An error for a malformed address will occur. */
-        default:
+        value==name when it found no equal-sign.
+        An error for a malformed address will occur. */
+        case ENV_MAIL_OPT_NULL:
           value[-1] = '=';
           name[-1] = ' ';
           arg_error = TRUE;
           break;
+
+        default:  assert(0);
         }
       /* Break out of for loop if switch() had bad argument or
          when start of the email address is reached */
@@ -4206,7 +4204,7 @@ while (done <= 0)
                     US"",
                   #endif
                     US"\r\n");
-      else 
+      else
         {
       #ifndef DISABLE_PRDR
         if (prdr_requested)
@@ -4464,7 +4462,7 @@ while (done <= 0)
       if (user_msg == NULL) smtp_printf("250 Accepted\r\n");
         else smtp_user_msg(US"250", user_msg);
       receive_add_recipient(recipient, -1);
+
       /* Set the dsn flags in the recipients_list */
       recipients_list[recipients_count-1].orcpt = orcpt;
       recipients_list[recipients_count-1].dsn_flags = flags;
@@ -4583,51 +4581,49 @@ while (done <= 0)
 
 
     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);
-    else
       {
-      uschar *address;
-      uschar *s = NULL;
+      uschar * address;
+
+      HAD(SCH_VRFY);
 
-      /* rfc821_domains = TRUE; << no longer needed */
-      address = parse_extract_address(smtp_cmd_data, &errmess, &start, &end,
-        &recipient_domain, FALSE);
-      /* rfc821_domains = FALSE; << no longer needed */
+      if(!(address = parse_extract_address(smtp_cmd_data, &errmess, &start, &end,
+           &recipient_domain, FALSE)))
+       smtp_printf("501 %s\r\n", errmess);
 
-      if (address == NULL)
-        s = string_sprintf("501 %s", errmess);
+      else if ((rc = acl_check(ACL_WHERE_VRFY, address, acl_smtp_vrfy,
+                   &user_msg, &log_msg)) != OK)
+       done = smtp_handle_acl_fail(ACL_WHERE_VRFY, rc, user_msg, log_msg);
       else
-        {
-        address_item *addr = deliver_make_addr(address, FALSE);
-        switch(verify_address(addr, NULL, vopt_is_recipient | vopt_qualify, -1,
-               -1, -1, NULL, NULL, NULL))
-          {
-          case OK:
-          s = string_sprintf("250 <%s> is deliverable", address);
-          break;
+       {
+       uschar *s = NULL;
 
-          case DEFER:
-          s = (addr->user_message != NULL)?
-            string_sprintf("451 <%s> %s", address, addr->user_message) :
-            string_sprintf("451 Cannot resolve <%s> at this time", address);
-          break;
+       address_item *addr = deliver_make_addr(address, FALSE);
+       switch(verify_address(addr, NULL, vopt_is_recipient | vopt_qualify, -1,
+              -1, -1, NULL, NULL, NULL))
+         {
+         case OK:
+           s = string_sprintf("250 <%s> is deliverable", address);
+           break;
 
-          case FAIL:
-          s = (addr->user_message != NULL)?
-            string_sprintf("550 <%s> %s", address, addr->user_message) :
-            string_sprintf("550 <%s> is not deliverable", address);
-          log_write(0, LOG_MAIN, "VRFY failed for %s %s",
-            smtp_cmd_argument, host_and_ident(TRUE));
-          break;
-          }
-        }
+         case DEFER:
+           s = (addr->user_message != NULL)?
+             string_sprintf("451 <%s> %s", address, addr->user_message) :
+             string_sprintf("451 Cannot resolve <%s> at this time", address);
+           break;
 
-      smtp_printf("%s\r\n", s);
+         case FAIL:
+           s = (addr->user_message != NULL)?
+             string_sprintf("550 <%s> %s", address, addr->user_message) :
+             string_sprintf("550 <%s> is not deliverable", address);
+           log_write(0, LOG_MAIN, "VRFY failed for %s %s",
+             smtp_cmd_argument, host_and_ident(TRUE));
+           break;
+         }
+
+       smtp_printf("%s\r\n", s);
+       }
+      break;
       }
-    break;
 
 
     case EXPN_CMD:
@@ -4661,15 +4657,13 @@ while (done <= 0)
 
     /* Apply an ACL check if one is defined */
 
-    if (acl_smtp_starttls != NULL)
+    if (  acl_smtp_starttls
+       && (rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls,
+                 &user_msg, &log_msg)) != OK
+       )
       {
-      rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls, &user_msg,
-        &log_msg);
-      if (rc != OK)
-        {
-        done = smtp_handle_acl_fail(ACL_WHERE_STARTTLS, rc, user_msg, log_msg);
-        break;
-        }
+      done = smtp_handle_acl_fail(ACL_WHERE_STARTTLS, rc, user_msg, log_msg);
+      break;
       }
 
     /* RFC 2487 is not clear on when this command may be sent, though it
@@ -4912,8 +4906,8 @@ while (done <= 0)
     log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument,
       host_and_ident(FALSE));
 
-    rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn, &user_msg, &log_msg);
-    if (rc != OK)
+    if ((rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn,
+               &user_msg, &log_msg)) != OK)
       {
       done = smtp_handle_acl_fail(ACL_WHERE_ETRN, rc, user_msg, log_msg);
       break;
@@ -4979,7 +4973,7 @@ while (done <= 0)
     /* If ETRN queue runs are to be serialized, check the database to
     ensure one isn't already running. */
 
-    if (smtp_etrn_serialize && !enq_start(etrn_serialize_key))
+    if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
       {
       smtp_printf("458 Already processing %s\r\n", smtp_cmd_data);
       break;
@@ -5103,11 +5097,11 @@ while (done <= 0)
     done = 1;   /* Pretend eof - drops connection */
     break;
 
-    #ifdef EXPERIMENTAL_PROXY
+#ifdef SUPPORT_PROXY
     case PROXY_FAIL_IGNORE_CMD:
     smtp_printf("503 Command refused, required Proxy negotiation failed\r\n");
     break;
-    #endif
+#endif
 
     default:
     if (unknown_command_count++ >= smtp_max_unknown_commands)