Apply Jakob Hirsch's patch for arbitrary ACL variable names, tidied up
[exim.git] / src / src / smtp_in.c
index 01f4a09ef57410e3e69bac7a335281364f42c82d..614a3ffe0fb49f15db057e2770c031dc71186b0b 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/smtp_in.c,v 1.26 2005/09/13 11:13:27 ph10 Exp $ */
+/* $Cambridge: exim/src/src/smtp_in.c,v 1.43 2006/09/19 11:28:45 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2006 */
 /* 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. */
@@ -338,8 +338,13 @@ va_list ap;
 
 DEBUG(D_receive)
   {
 
 DEBUG(D_receive)
   {
+  uschar *cr, *end;
   va_start(ap, format);
   (void) string_vformat(big_buffer, big_buffer_size, format, ap);
   va_start(ap, format);
   (void) string_vformat(big_buffer, big_buffer_size, format, ap);
+  va_end(ap);
+  end = big_buffer + Ustrlen(big_buffer);
+  while ((cr = Ustrchr(big_buffer, '\r')) != NULL)   /* lose CRs */
+    memmove(cr, cr + 1, (end--) - cr);
   debug_printf("SMTP>> %s", big_buffer);
   }
 
   debug_printf("SMTP>> %s", big_buffer);
   }
 
@@ -518,7 +523,10 @@ if required. */
 
 for (p = cmd_list; p < cmd_list_end; p++)
   {
 
 for (p = cmd_list; p < cmd_list_end; p++)
   {
-  if (strncmpic(smtp_cmd_buffer, US p->name, p->len) == 0)
+  if (strncmpic(smtp_cmd_buffer, US p->name, p->len) == 0 &&
+       (smtp_cmd_buffer[p->len-1] == ':' ||   /* "mail from:" or "rcpt to:" */
+        smtp_cmd_buffer[p->len] == 0 ||
+        smtp_cmd_buffer[p->len] == ' '))
     {
     if (smtp_inptr < smtp_inend &&                     /* Outstanding input */
         p->cmd < sync_cmd_limit &&                     /* Command should sync */
     {
     if (smtp_inptr < smtp_inend &&                     /* Outstanding input */
         p->cmd < sync_cmd_limit &&                     /* Command should sync */
@@ -626,6 +634,8 @@ for (;;)
 
 /* This function is called when logging information about an SMTP connection.
 It sets up appropriate source information, depending on the type of connection.
 
 /* This function is called when logging information about an SMTP connection.
 It sets up appropriate source information, depending on the type of connection.
+If sender_fullhost is NULL, we are at a very early stage of the connection;
+just use the IP address.
 
 Argument:    none
 Returns:     a string describing the connection
 
 Argument:    none
 Returns:     a string describing the connection
@@ -634,21 +644,24 @@ Returns:     a string describing the connection
 uschar *
 smtp_get_connection_info(void)
 {
 uschar *
 smtp_get_connection_info(void)
 {
+uschar *hostname = (sender_fullhost == NULL)?
+  sender_host_address : sender_fullhost;
+
 if (host_checking)
 if (host_checking)
-  return string_sprintf("SMTP connection from %s", sender_fullhost);
+  return string_sprintf("SMTP connection from %s", hostname);
 
 if (sender_host_unknown || sender_host_notsocket)
   return string_sprintf("SMTP connection from %s", sender_ident);
 
 if (is_inetd)
 
 if (sender_host_unknown || sender_host_notsocket)
   return string_sprintf("SMTP connection from %s", sender_ident);
 
 if (is_inetd)
-  return string_sprintf("SMTP connection from %s (via inetd)", sender_fullhost);
+  return string_sprintf("SMTP connection from %s (via inetd)", hostname);
 
 if ((log_extra_selector & LX_incoming_interface) != 0 &&
      interface_address != NULL)
 
 if ((log_extra_selector & LX_incoming_interface) != 0 &&
      interface_address != NULL)
-  return string_sprintf("SMTP connection from %s I=[%s]:%d", sender_fullhost,
+  return string_sprintf("SMTP connection from %s I=[%s]:%d", hostname,
     interface_address, interface_port);
 
     interface_address, interface_port);
 
-return string_sprintf("SMTP connection from %s", sender_fullhost);
+return string_sprintf("SMTP connection from %s", hostname);
 }
 
 
 }
 
 
@@ -780,8 +793,6 @@ return TRUE;
 
 
 
 
 
 
-
-
 /*************************************************
 *         Reset for new message                  *
 *************************************************/
 /*************************************************
 *         Reset for new message                  *
 *************************************************/
@@ -796,16 +807,16 @@ Returns:    nothing
 static void
 smtp_reset(void *reset_point)
 {
 static void
 smtp_reset(void *reset_point)
 {
-int i;
 store_reset(reset_point);
 recipients_list = NULL;
 rcpt_count = rcpt_defer_count = rcpt_fail_count =
   raw_recipients_count = recipients_count = recipients_list_max = 0;
 message_linecount = 0;
 message_size = -1;
 store_reset(reset_point);
 recipients_list = NULL;
 rcpt_count = rcpt_defer_count = rcpt_fail_count =
   raw_recipients_count = recipients_count = recipients_list_max = 0;
 message_linecount = 0;
 message_size = -1;
-acl_warn_headers = NULL;
+acl_added_headers = NULL;
 queue_only_policy = FALSE;
 deliver_freeze = FALSE;                              /* Can be set by ACL */
 queue_only_policy = 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 */
 #ifdef WITH_CONTENT_SCAN
 no_mbox_unspool = FALSE;                             /* Can be set by ACL */
 fake_response = OK;                                  /* Can be set by ACL */
 #ifdef WITH_CONTENT_SCAN
 no_mbox_unspool = FALSE;                             /* Can be set by ACL */
@@ -841,7 +852,9 @@ sender_rate = sender_rate_limit = sender_rate_period = NULL;
 ratelimiters_mail = NULL;           /* Updated by ratelimit ACL condition */
                    /* Note that ratelimiters_conn persists across resets. */
 
 ratelimiters_mail = NULL;           /* Updated by ratelimit ACL condition */
                    /* Note that ratelimiters_conn persists across resets. */
 
-for (i = 0; i < ACL_M_MAX; i++) acl_var[ACL_C_MAX + i] = NULL;
+/* Reset message ACL variables */
+
+acl_var_m = NULL;
 
 /* The message body variables use malloc store. They may be set if this is
 not the first message in an SMTP session and the previous message caused them
 
 /* The message body variables use malloc store. They may be set if this is
 not the first message in an SMTP session and the previous message caused them
@@ -1128,18 +1141,9 @@ BOOL
 smtp_start_session(void)
 {
 int size = 256;
 smtp_start_session(void)
 {
 int size = 256;
-int i, ptr;
+int ptr;
 uschar *p, *s, *ss;
 
 uschar *p, *s, *ss;
 
-/* If we are running in the test harness, and the incoming call is from
-127.0.0.2 (sic), have a short delay. This makes it possible to test handling of
-input sent too soon (before the banner is output). */
-
-if (running_in_test_harness &&
-    sender_host_address != NULL &&
-    Ustrcmp(sender_host_address, "127.0.0.2") == 0)
-  sleep(1);
-
 /* Default values for certain variables */
 
 helo_seen = esmtp = helo_accept_junk = FALSE;
 /* Default values for certain variables */
 
 helo_seen = esmtp = helo_accept_junk = FALSE;
@@ -1152,7 +1156,10 @@ sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
 
 memset(sender_host_cache, 0, sizeof(sender_host_cache));
 
 
 memset(sender_host_cache, 0, sizeof(sender_host_cache));
 
-sender_host_authenticated = NULL;
+/* If receiving by -bs from a trusted user, or testing with -bh, we allow
+authentication settings from -oMaa to remain in force. */
+
+if (!host_checking && !sender_host_notsocket) sender_host_authenticated = NULL;
 authenticated_by = NULL;
 
 #ifdef SUPPORT_TLS
 authenticated_by = NULL;
 
 #ifdef SUPPORT_TLS
@@ -1162,7 +1169,7 @@ tls_advertised = FALSE;
 
 /* Reset ACL connection variables */
 
 
 /* Reset ACL connection variables */
 
-for (i = 0; i < ACL_C_MAX; i++) acl_var[i] = NULL;
+acl_var_c = NULL;
 
 /* Allow for trailing 0 in the command buffer. */
 
 
 /* Allow for trailing 0 in the command buffer. */
 
@@ -1201,8 +1208,8 @@ smtp_had_eof = smtp_had_error = 0;
 
 /* Set up the message size limit; this may be host-specific */
 
 
 /* Set up the message size limit; this may be host-specific */
 
-thismessage_size_limit = expand_string_integer(message_size_limit);
-if (thismessage_size_limit < 0)
+thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
+if (expand_string_message != NULL)
   {
   if (thismessage_size_limit == -1)
     log_write(0, LOG_MAIN|LOG_PANIC, "unable to expand message_size_limit: "
   {
   if (thismessage_size_limit == -1)
     log_write(0, LOG_MAIN|LOG_PANIC, "unable to expand message_size_limit: "
@@ -1455,19 +1462,40 @@ if (!sender_host_unknown)
     return FALSE;
     }
 
     return FALSE;
     }
 
-  /* Test with TCP Wrappers if so configured */
+  /* Test with TCP Wrappers if so configured. There is a problem in that
+  hosts_ctl() returns 0 (deny) under a number of system failure circumstances,
+  such as disks dying. In these cases, it is desirable to reject with a 4xx
+  error instead of a 5xx error. There isn't a "right" way to detect such
+  problems. The following kludge is used: errno is zeroed before calling
+  hosts_ctl(). If the result is "reject", a 5xx error is given only if the
+  value of errno is 0 or ENOENT (which happens if /etc/hosts.{allow,deny} does
+  not exist). */
 
   #ifdef USE_TCP_WRAPPERS
 
   #ifdef USE_TCP_WRAPPERS
+  errno = 0;
   if (!hosts_ctl("exim",
          (sender_host_name == NULL)? STRING_UNKNOWN : CS sender_host_name,
          (sender_host_address == NULL)? STRING_UNKNOWN : CS sender_host_address,
          (sender_ident == NULL)? STRING_UNKNOWN : CS sender_ident))
     {
   if (!hosts_ctl("exim",
          (sender_host_name == NULL)? STRING_UNKNOWN : CS sender_host_name,
          (sender_host_address == NULL)? STRING_UNKNOWN : CS sender_host_address,
          (sender_ident == NULL)? STRING_UNKNOWN : CS sender_ident))
     {
-    HDEBUG(D_receive) debug_printf("tcp wrappers rejection\n");
-    log_write(L_connection_reject,
-              LOG_MAIN|LOG_REJECT, "refused connection from %s "
-              "(tcp wrappers)", host_and_ident(FALSE));
-    smtp_printf("554 SMTP service not available\r\n");
+    if (errno == 0 || errno == ENOENT)
+      {
+      HDEBUG(D_receive) debug_printf("tcp wrappers rejection\n");
+      log_write(L_connection_reject,
+                LOG_MAIN|LOG_REJECT, "refused connection from %s "
+                "(tcp wrappers)", host_and_ident(FALSE));
+      smtp_printf("554 SMTP service not available\r\n");
+      }
+    else
+      {
+      int save_errno = errno;
+      HDEBUG(D_receive) debug_printf("tcp wrappers rejected with unexpected "
+        "errno value %d\n", save_errno);
+      log_write(L_connection_reject,
+                LOG_MAIN|LOG_REJECT, "temporarily refused connection from %s "
+                "(tcp wrappers errno=%d)", host_and_ident(FALSE), save_errno);
+      smtp_printf("451 Temporary local problem - please try later\r\n");
+      }
     return FALSE;
     }
   #endif
     return FALSE;
     }
   #endif
@@ -1742,7 +1770,8 @@ responses. If no_multiline_responses is TRUE (it can be set from an ACL), we
 output nothing for non-final calls, and only the first line for anything else.
 
 Arguments:
 output nothing for non-final calls, and only the first line for anything else.
 
 Arguments:
-  code          SMTP code
+  code          SMTP code, may involve extended status codes
+  codelen       length of smtp code; uf > 3 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
 
@@ -1750,26 +1779,36 @@ Returns:        nothing
 */
 
 void
 */
 
 void
-smtp_respond(int code, BOOL final, uschar *msg)
+smtp_respond(uschar* code, int codelen, BOOL final, uschar *msg)
 {
 {
+int esclen = 0;
+uschar *esc = US"";
+
 if (!final && no_multiline_responses) return;
 
 if (!final && no_multiline_responses) return;
 
+if (codelen > 3)
+  {
+  esc = code + 4;
+  esclen = codelen - 4;
+  }
+
 for (;;)
   {
   uschar *nl = Ustrchr(msg, '\n');
   if (nl == NULL)
     {
 for (;;)
   {
   uschar *nl = Ustrchr(msg, '\n');
   if (nl == NULL)
     {
-    smtp_printf("%d%c%s\r\n", code, final? ' ':'-', msg);
+    smtp_printf("%.3s%c%.*s%s\r\n", code, final? ' ':'-', esclen, esc, msg);
     return;
     }
   else if (nl[1] == 0 || no_multiline_responses)
     {
     return;
     }
   else if (nl[1] == 0 || no_multiline_responses)
     {
-    smtp_printf("%d%c%.*s\r\n", code, final? ' ':'-', (int)(nl - msg), msg);
+    smtp_printf("%.3s%c%.*s%.*s\r\n", code, final? ' ':'-', esclen, esc,
+      (int)(nl - msg), msg);
     return;
     }
   else
     {
     return;
     }
   else
     {
-    smtp_printf("%d-%.*s\r\n", code, (int)(nl - msg), msg);
+    smtp_printf("%.3s-%.*s%.*s\r\n", code, esclen, esc, (int)(nl - msg), msg);
     msg = nl + 1;
     while (isspace(*msg)) msg++;
     }
     msg = nl + 1;
     while (isspace(*msg)) msg++;
     }
@@ -1789,13 +1828,18 @@ logging the incident, and sets up the error response. A message containing
 newlines is turned into a multiline SMTP response, but for logging, only the
 first line is used.
 
 newlines is turned into a multiline SMTP response, but for logging, only the
 first line is used.
 
-There's a table of the response codes to use in globals.c, along with the table
-of names. VFRY is special. Despite RFC1123 it 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.
+There's a table of default permanent failure response codes to use in
+globals.c, along with the table of names. VFRY is special. Despite RFC1123 it
+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.
+
+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
 
 Arguments:
   where      where the ACL was called from
@@ -1812,8 +1856,10 @@ Returns:     0 in most cases
 int
 smtp_handle_acl_fail(int where, int rc, uschar *user_msg, uschar *log_msg)
 {
 int
 smtp_handle_acl_fail(int where, int rc, uschar *user_msg, uschar *log_msg)
 {
-int code = acl_wherecodes[where];
 BOOL drop = rc == FAIL_DROP;
 BOOL drop = rc == FAIL_DROP;
+int codelen = 3;
+int ovector[3];
+uschar *smtp_code;
 uschar *lognl;
 uschar *sender_info = US"";
 uschar *what =
 uschar *lognl;
 uschar *sender_info = US"";
 uschar *what =
@@ -1828,6 +1874,41 @@ uschar *what =
 
 if (drop) rc = FAIL;
 
 
 if (drop) rc = FAIL;
 
+/* Set the default SMTP code */
+
+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 */
+    }
+  }
+
 /* 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
 fixed, sender_address at this point became the rewritten address. I'm not sure
 /* 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
 fixed, sender_address at this point became the rewritten address. I'm not sure
@@ -1846,22 +1927,24 @@ if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA || where == ACL_WHERE_MIM
 
 /* If there's been a sender verification failure with a specific message, and
 we have not sent a response about it yet, do so now, as a preliminary line for
 
 /* If there's been a sender verification failure with a specific message, and
 we have not sent a response about it yet, do so now, as a preliminary line for
-failures, but not defers. However, log it in both cases. */
+failures, but not defers. However, always log it for defer, and log it for fail
+unless the sender_verify_fail log selector has been turned off. */
 
 if (sender_verified_failed != NULL &&
     !testflag(sender_verified_failed, af_sverify_told))
   {
   setflag(sender_verified_failed, af_sverify_told);
 
 
 if (sender_verified_failed != NULL &&
     !testflag(sender_verified_failed, af_sverify_told))
   {
   setflag(sender_verified_failed, af_sverify_told);
 
-  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",
-    sender_verified_failed->address,
-    (sender_verified_failed->message == NULL)? US"" :
-    string_sprintf(": %s", sender_verified_failed->message));
+  if (rc != FAIL || (log_extra_selector & LX_sender_verify_fail) != 0)
+    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",
+      sender_verified_failed->address,
+      (sender_verified_failed->message == NULL)? US"" :
+      string_sprintf(": %s", sender_verified_failed->message));
 
   if (rc == FAIL && sender_verified_failed->user_message != NULL)
 
   if (rc == FAIL && sender_verified_failed->user_message != NULL)
-    smtp_respond(code, FALSE, string_sprintf(
+    smtp_respond(smtp_code, codelen, FALSE, string_sprintf(
         testflag(sender_verified_failed, af_verify_pmfail)?
           "Postmaster verification failed while checking <%s>\n%s\n"
           "Several RFCs state that you are required to have a postmaster\n"
         testflag(sender_verified_failed, af_verify_pmfail)?
           "Postmaster verification failed while checking <%s>\n%s\n"
           "Several RFCs state that you are required to have a postmaster\n"
@@ -1891,7 +1974,7 @@ if (lognl != NULL) *lognl = 0;
 always a 5xx one - see comments at the start of this function. If the original
 rc was FAIL_DROP we drop the connection and yield 2. */
 
 always a 5xx one - see comments at the start of this function. If the original
 rc was FAIL_DROP we drop the connection and yield 2. */
 
-if (rc == FAIL) smtp_respond(code, TRUE, (user_msg == NULL)?
+if (rc == FAIL) smtp_respond(smtp_code, codelen, TRUE, (user_msg == NULL)?
   US"Administrative prohibition" : user_msg);
 
 /* Send temporary failure response to the command. Don't give any details,
   US"Administrative prohibition" : user_msg);
 
 /* Send temporary failure response to the command. Don't give any details,
@@ -1910,12 +1993,13 @@ else
         sender_verified_failed != NULL &&
         sender_verified_failed->message != NULL)
       {
         sender_verified_failed != NULL &&
         sender_verified_failed->message != NULL)
       {
-      smtp_respond(451, FALSE, sender_verified_failed->message);
+      smtp_respond(smtp_code, codelen, FALSE, sender_verified_failed->message);
       }
       }
-    smtp_respond(451, TRUE, user_msg);
+    smtp_respond(smtp_code, codelen, TRUE, user_msg);
     }
   else
     }
   else
-    smtp_printf("451 Temporary local problem - please try later\r\n");
+    smtp_respond(smtp_code, codelen, TRUE,
+      US"Temporary local problem - please try later");
   }
 
 /* Log the incident. If the connection is not forcibly to be dropped, return 0.
   }
 
 /* Log the incident. If the connection is not forcibly to be dropped, return 0.
@@ -2140,16 +2224,20 @@ while (done <= 0)
   pid_t pid;
   int start, end, sender_domain, recipient_domain;
   int ptr, size, rc;
   pid_t pid;
   int start, end, sender_domain, recipient_domain;
   int ptr, size, rc;
-  int c;
+  int c, i;
   auth_instance *au;
 
   switch(smtp_read_command(TRUE))
     {
     /* The AUTH command is not permitted to occur inside a transaction, and may
   auth_instance *au;
 
   switch(smtp_read_command(TRUE))
     {
     /* The AUTH command is not permitted to occur inside a transaction, and may
-    occur successfully only once per connection, and then only when we've
-    advertised it. Actually, that isn't quite true. When TLS is started, all
-    previous information about a connection must be discarded, so a new AUTH is
-    permitted at that time.
+    occur successfully only once per connection. Actually, that isn't quite
+    true. When TLS is started, all previous information about a connection must
+    be discarded, so a new AUTH is permitted at that time.
+
+    AUTH may only be used when it has been advertised. However, it seems that
+    there are clients that send AUTH when it hasn't been advertised, some of
+    them even doing this after HELO. And there are MTAs that accept this. Sigh.
+    So there's a get-out that allows this to happen.
 
     AUTH is initially labelled as a "nonmail command" so that one occurrence
     doesn't get counted. We change the label here so that multiple failing
 
     AUTH is initially labelled as a "nonmail command" so that one occurrence
     doesn't get counted. We change the label here so that multiple failing
@@ -2159,7 +2247,7 @@ while (done <= 0)
     authentication_failed = TRUE;
     cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE;
 
     authentication_failed = TRUE;
     cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE;
 
-    if (!auth_advertised)
+    if (!auth_advertised && !allow_auth_unadvertised)
       {
       done = synprot_error(L_smtp_protocol_error, 503, NULL,
         US"AUTH command used when not advertised");
       {
       done = synprot_error(L_smtp_protocol_error, 503, NULL,
         US"AUTH command used when not advertised");
@@ -2214,12 +2302,13 @@ while (done <= 0)
       }
 
     /* Search for an authentication mechanism which is configured for use
       }
 
     /* Search for an authentication mechanism which is configured for use
-    as a server and which has been advertised. */
+    as a server and which has been advertised (unless, sigh, allow_auth_
+    unadvertised is set). */
 
     for (au = auths; au != NULL; au = au->next)
       {
       if (strcmpic(s, au->public_name) == 0 && au->server &&
 
     for (au = auths; au != NULL; au = au->next)
       {
       if (strcmpic(s, au->public_name) == 0 && au->server &&
-          au->advertised) break;
+          (au->advertised || allow_auth_unadvertised)) break;
       }
 
     if (au == NULL)
       }
 
     if (au == NULL)
@@ -2229,20 +2318,26 @@ while (done <= 0)
       break;
       }
 
       break;
       }
 
-    /* Run the checking code, passing the remainder of the command
-    line as data. Initialize $0 empty. The authenticator may set up
-    other numeric variables. Afterwards, have a go at expanding the set_id
-    string, even if authentication failed - for bad passwords it can be useful
-    to log the userid. On success, require set_id to expand and exist, and
-    put it in authenticated_id. Save this in permanent store, as the working
-    store gets reset at HELO, RSET, etc. */
+    /* Run the checking code, passing the remainder of the command line as
+    data. Initials the $auth<n> variables as empty. Initialize $0 empty and set
+    it as the only set numerical variable. The authenticator may set $auth<n>
+    and also set other numeric variables. The $auth<n> variables are preferred
+    nowadays; the numerical variables remain for backwards compatibility.
+
+    Afterwards, have a go at expanding the set_id string, even if
+    authentication failed - for bad passwords it can be useful to log the
+    userid. On success, require set_id to expand and exist, and put it in
+    authenticated_id. Save this in permanent store, as the working store gets
+    reset at HELO, RSET, etc. */
 
 
+    for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
     expand_nmax = 0;
     expand_nlength[0] = 0;   /* $0 contains nothing */
 
     c = (au->info->servercode)(au, smtp_cmd_argument);
     if (au->set_id != NULL) set_id = expand_string(au->set_id);
     expand_nmax = -1;        /* Reset numeric variables */
     expand_nmax = 0;
     expand_nlength[0] = 0;   /* $0 contains nothing */
 
     c = (au->info->servercode)(au, smtp_cmd_argument);
     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> */
 
     /* The value of authenticated_id is stored in the spool file and printed in
     log lines. It must not contain binary zeros or newline characters. In
 
     /* The value of authenticated_id is stored in the spool file and printed in
     log lines. It must not contain binary zeros or newline characters. In
@@ -2631,7 +2726,13 @@ while (done <= 0)
     #endif
 
     (void)fwrite(s, 1, ptr, smtp_out);
     #endif
 
     (void)fwrite(s, 1, ptr, smtp_out);
-    DEBUG(D_receive) debug_printf("SMTP>> %s", s);
+    DEBUG(D_receive)
+      {
+      uschar *cr;
+      while ((cr = Ustrchr(s, '\r')) != NULL)   /* lose CRs */
+        memmove(cr, cr + 1, (ptr--) - (cr - s));
+      debug_printf("SMTP>> %s", s);
+      }
     helo_seen = TRUE;
     break;   /* HELO/EHLO */
 
     helo_seen = TRUE;
     break;   /* HELO/EHLO */
 
@@ -3185,14 +3286,14 @@ while (done <= 0)
           break;
 
           case DEFER:
           break;
 
           case DEFER:
-          s = (addr->message != NULL)?
-            string_sprintf("451 <%s> %s", address, addr->message) :
+          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;
 
           case FAIL:
             string_sprintf("451 Cannot resolve <%s> at this time", address);
           break;
 
           case FAIL:
-          s = (addr->message != NULL)?
-            string_sprintf("550 <%s> %s", address, addr->message) :
+          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));
             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));