VRFY: Permit an ACL to override the default 252 response, to support
authorJeremy Harris <jgh146exb@wizmail.org>
Sat, 16 Jan 2016 22:17:33 +0000 (22:17 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Sat, 16 Jan 2016 23:20:24 +0000 (23:20 +0000)
verify-by-ACL instead of the more usual verify-by-routers.  Bug 1769

doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
src/src/acl.c
src/src/functions.h
src/src/receive.c
src/src/smtp_in.c
test/confs/0041
test/scripts/0000-Basic/0041
test/stderr/0041
test/stdout/0041

index aa1e677127e23d1e16e19ef45602fdae047954d9..cb913d6f1f481e5566f6f812173b17e447430636 100644 (file)
@@ -34046,13 +34046,20 @@ specific badly-behaved hosts that you have to live with.
 When Exim receives a VRFY or EXPN command on a TCP/IP connection, it
 runs the ACL specified by &%acl_smtp_vrfy%& or &%acl_smtp_expn%& (as
 appropriate) in order to decide whether the command should be accepted or not.
-If no ACL is defined, the command is rejected.
 
+.new
 .cindex "VRFY" "processing"
+When no ACL is defined for VRFY, or if it rejects without
+setting an explicit response code, the command is accepted
+(with a 252 SMTP response code)
+in order to support awkward clients that do a VRFY before every RCPT.
+.wen
 When VRFY is accepted, it runs exactly the same code as when Exim is
-called with the &%-bv%& option.
+called with the &%-bv%& option, and returns 250/451/550
+SMTP response codes.
 
 .cindex "EXPN" "processing"
+If no ACL for EXPN is defined, the command is rejected.
 When EXPN is accepted, a single-level expansion of the address is done.
 EXPN is treated as an &"address test"& (similar to the &%-bt%& option) rather
 than a verification (the &%-bv%& option). If an unqualified local part is given
index 28fe75447f8ee605be21fae25b392c9255a83cf6..9f275a9dbb396f92c8e485865d31d5bc668db008 100644 (file)
@@ -153,6 +153,9 @@ JH/36 Fix a longstanding bug in memory use by the ${run } expansion: A fresh
       concluded, but leaving the global pointer active for it.  Possibly
       involved in Bug 1778.
 
+JH/37 Bug 1769: Permit a VRFY ACL to override the default 252 response,
+      and to use the domains and local_parts ACL conditions.
+
 
 Exim version 4.86
 -----------------
index 1456cc72425fd65a0c1b077eda22d2d07c7adf4b..684b93bbb31641bb4e59784b1f3d9589e91f4e09 100644 (file)
@@ -482,6 +482,7 @@ static unsigned int cond_forbids[] = {
 
   (unsigned int)
   ~((1<<ACL_WHERE_RCPT)                            /* domains */
+    |(1<<ACL_WHERE_VRFY)
   #ifndef DISABLE_PRDR
     |(1<<ACL_WHERE_PRDR)
   #endif
@@ -499,6 +500,7 @@ static unsigned int cond_forbids[] = {
 
   (unsigned int)
   ~((1<<ACL_WHERE_RCPT)                             /* local_parts */
+    |(1<<ACL_WHERE_VRFY)
   #ifndef DISABLE_PRDR
     |(1<<ACL_WHERE_PRDR)
   #endif
@@ -4454,9 +4456,9 @@ ratelimiters_cmd = NULL;
 log_reject_target = LOG_MAIN|LOG_REJECT;
 
 #ifndef DISABLE_PRDR
-if (where == ACL_WHERE_RCPT || where == ACL_WHERE_PRDR)
+if (where==ACL_WHERE_RCPT || where==ACL_WHERE_VRFY || where==ACL_WHERE_PRDR)
 #else
-if (where == ACL_WHERE_RCPT)
+if (where==ACL_WHERE_RCPT || where==ACL_WHERE_VRFY)
 #endif
   {
   adb = address_defaults;
index 1d2d6b8ae2364b5d3b8a224f3c4c86ee398c476b..97d95dd3d49b697aff07346db801acf357393eda 100644 (file)
@@ -390,7 +390,7 @@ extern BOOL    smtp_get_port(uschar *, address_item *, int *, uschar *);
 extern int     smtp_getc(void);
 extern int     smtp_handle_acl_fail(int, int, uschar *, uschar *);
 extern void    smtp_log_no_mail(void);
-extern void    smtp_message_code(uschar **, int *, uschar **, uschar **);
+extern void    smtp_message_code(uschar **, int *, uschar **, uschar **, BOOL);
 extern BOOL    smtp_read_response(smtp_inblock *, uschar *, int, int, int);
 extern void    smtp_respond(uschar *, int, BOOL, uschar *);
 extern void    smtp_notquit_exit(uschar *, uschar *, uschar *, ...);
index 585cb902529fb8a600a67c0f0663f92509212a4a..f2a94e0f85128ca18e3ae69e692a0b89b1c6a69c 100644 (file)
@@ -525,7 +525,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);
 }
 #endif
@@ -1279,10 +1279,12 @@ else if (rc != OK)
 #ifdef EXPERIMENTAL_DCC
   dcc_ok = 0;
 #endif
-  if (smtp_input && smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0) {
+  if (  smtp_input
+     && smtp_handle_acl_fail(ACL_WHERE_MIME, rc, user_msg, log_msg) != 0)
+    {
     *smtp_yield_ptr = FALSE;    /* No more messsages after dropped connection */
     *smtp_reply_ptr = US"";     /* Indicate reply already sent */
-  }
+    }
   message_id[0] = 0;            /* Indicate no message accepted */
   return FALSE;                 /* Cause skip to end of receive function */
   }
@@ -4078,7 +4080,7 @@ if (smtp_input)
         {
         uschar *code = US"250";
         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);
         }
 
index b48e436e3c0d8ae3d89aa59c4b6c22b3da875b60..0498ecb18d8399c643f1b70e043ebb403f5dd1ff 100644 (file)
@@ -2364,7 +2364,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;
@@ -2626,23 +2626,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);
@@ -2677,18 +2678,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
@@ -2721,8 +2723,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
@@ -3096,7 +3099,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);
 }
 
@@ -3305,14 +3308,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)
@@ -3371,14 +3373,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. */
@@ -3550,10 +3551,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;
@@ -3561,7 +3561,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
@@ -3609,7 +3608,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)
         {
@@ -4581,51 +4580,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;
 
-      /* 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 */
+      HAD(SCH_VRFY);
 
-      if (address == NULL)
-        s = string_sprintf("501 %s", errmess);
+      if(!(address = parse_extract_address(smtp_cmd_data, &errmess, &start, &end,
+           &recipient_domain, FALSE)))
+       smtp_printf("501 %s\r\n", 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:
@@ -4659,15 +4656,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
@@ -4910,8 +4905,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;
index 51cba04acc5c01ef58fb4df8a61aa9ba761e1395..ec1067584443df435271984a437ec69ef8029bd5 100644 (file)
@@ -12,6 +12,7 @@ tls_advertise_hosts =
 
 domainlist local_domains = test.ex
 
+acl_smtp_vrfy = check_vrfy
 acl_smtp_expn = check_expn
 qualify_domain = test.ex
 no_write_rejectlog
@@ -21,6 +22,10 @@ no_write_rejectlog
 
 begin acl
 
+check_vrfy:
+  deny  local_parts = hardfail
+         message = 599 custom reject
+
 check_expn:
   accept hosts = 2.2.2.2
 
index 5601d650615c061ab5a4b146a57c3aedb1f47d06..3495375cbf75a4b775f66a461d451e7a28f5848c 100644 (file)
@@ -1,6 +1,7 @@
 # VRFY & EXPN blocking
 exim -bh 1.1.1.1
 vrfy userx@test.ex
+vrfy hardfail@test.ex
 expn postmaster
 quit
 ****
index 4aacae8838c0604a54335580b86fa4c9937a3761..b0030521234f926e3905192581b9d0d4a09a3c11 100644 (file)
@@ -7,8 +7,21 @@
 >>> host in helo_try_verify_hosts? no (option unset)
 >>> host in helo_accept_junk_hosts? no (option unset)
 >>> host in smtp_accept_max_nonmail_hosts? yes (matched "*")
->>> ACL is NULL: implicit DENY
+>>> using ACL "check_vrfy"
+>>> processing "deny"
+>>> check local_parts = hardfail
+>>> userx in "hardfail"? no (end of list)
+>>> deny: condition test failed in ACL "check_vrfy"
+>>> end of ACL "check_vrfy": implicit DENY
 LOG: H=[1.1.1.1] rejected VRFY userx@test.ex
+>>> using ACL "check_vrfy"
+>>> processing "deny"
+>>> check local_parts = hardfail
+>>> hardfail in "hardfail"? yes (matched "hardfail")
+>>>   message: 599 custom reject
+>>> deny: condition test succeeded in ACL "check_vrfy"
+>>> end of ACL "check_vrfy": DENY
+LOG: H=[1.1.1.1] rejected VRFY hardfail@test.ex: 599 custom reject
 >>> using ACL "check_expn"
 >>> processing "accept"
 >>> check hosts = 2.2.2.2
index d43b4501a68f8ddaeeafb44cc7e4f62878817044..b88c93ac70d681552d977b2bb9f920ed772bf0ce 100644 (file)
@@ -5,6 +5,7 @@
 
 220 the.local.host.name ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000\r
 252 Administrative prohibition\r
+599 custom reject\r
 550 Administrative prohibition\r
 221 the.local.host.name closing connection\r