Bug#378131: allow overriding smtp codes

Robert Millan rmh at aybabtu.com
Thu Jul 13 15:05:00 UTC 2006


Package: exim4
Version: 4.62-2
Severity: wishlist
Tags: patch

This is a backport of the following (post-4.62) CVS commit:

  http://www.exim.org/mail-archives/exim-cvs/2006-July/msg00009.html

ChangeLog reads:

  PH/16 Recognize SMTP codes at the start of "message" in ACLs and after :fail:
        and :defer: in a redirect router. Add forbid_smtp_code to suppress the
        latter.

NewStuff reads:

  3. When an SMTP error message is specified in a "message" modifier in an ACL,
     or in a :fail: or :defer: message in a redirect router, Exim now checks the
     start of the message for an SMTP error code. This consists of three digits
     followed by a space, optionally followed by an extended code of the form
     n.n.n, also followed by a space. If this is the case and the very first
     digit is the same as the default error code, the code from the message is
     used instead. If the very first digit is incorrect, a panic error is logged,
     and the default code is used. This is an incompatible change, but it is not
     expected to affect many (if any) configurations. It is possible to suppress
     the use of the supplied code in a redirect router by setting the
     smtp_error_code option false. In this case, any SMTP code is quietly
     ignored.

-- System Information:
Debian Release: testing/unstable
  APT prefers testing
  APT policy: (500, 'testing'), (1, 'experimental')
Architecture: amd64 (x86_64)
Shell:  /bin/sh linked to /bin/bash
Kernel: Linux 2.6.17-1-amd64-k8
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8) (ignored: LC_ALL set to en_US.UTF-8)

Versions of packages exim4 depends on:
ii  exim4-base                    4.62-2     support files for all exim MTA (v4
ii  exim4-daemon-light            4.62-2     lightweight exim MTA (v4) daemon

exim4 recommends no packages.

-- no debconf information
-------------- next part --------------

This is a backport of the following (post-4.62) CVS commit:

  http://www.exim.org/mail-archives/exim-cvs/2006-July/msg00009.html

ChangeLog reads:

  PH/16 Recognize SMTP codes at the start of "message" in ACLs and after :fail:
        and :defer: in a redirect router. Add forbid_smtp_code to suppress the
        latter.
   
NewStuff reads:

  3. When an SMTP error message is specified in a "message" modifier in an ACL,
     or in a :fail: or :defer: message in a redirect router, Exim now checks the
     start of the message for an SMTP error code. This consists of three digits
     followed by a space, optionally followed by an extended code of the form
     n.n.n, also followed by a space. If this is the case and the very first
     digit is the same as the default error code, the code from the message is
     used instead. If the very first digit is incorrect, a panic error is logged,
     and the default code is used. This is an incompatible change, but it is not
     expected to affect many (if any) configurations. It is possible to suppress
     the use of the supplied code in a redirect router by setting the
     smtp_error_code option false. In this case, any SMTP code is quietly
     ignored.

diff -ur exim4-4.62.old/README.UPDATING exim4-4.62/README.UPDATING
--- exim4-4.62.old/README.UPDATING	2006-04-28 10:32:21.000000000 +0000
+++ exim4-4.62/README.UPDATING	2006-07-13 14:33:27.000000000 +0000
@@ -28,6 +28,22 @@
 that might affect a running system.
 
 
+Exim version 4.63
+-----------------
+
+When an SMTP error message is specified in a "message" modifier in an ACL, or
+in a :fail: or :defer: message in a redirect router, Exim now checks the start
+of the message for an SMTP error code. This consists of three digits followed
+by a space, optionally followed by an extended code of the form n.n.n, also
+followed by a space. If this is the case and the very first digit is the same
+as the default error code, the code from the message is used instead. If the
+very first digit is incorrect, a panic error is logged, and the default code is
+used. This is an incompatible change, but it is not expected to affect many (if
+any) configurations. It is possible to suppress the use of the supplied code in
+a redirect router by setting the smtp_error_code option false. In this case,
+any SMTP code is quietly ignored.
+
+
 Exim version 4.61
 -----------------
 
diff -ur exim4-4.62.old/src/exim.c exim4-4.62/src/exim.c
--- exim4-4.62.old/src/exim.c	2006-04-28 10:32:22.000000000 +0000
+++ exim4-4.62/src/exim.c	2006-07-13 14:33:27.000000000 +0000
@@ -1492,6 +1492,13 @@
 regex_ismsgid =
   regex_must_compile(US"^(?:[^\\W_]{6}-){2}[^\\W_]{2}$", FALSE, TRUE);
 
+/* Precompile the regular expression that is used for matching an SMTP error
+code, possibly extended, at the start of an error message. */
+
+regex_smtp_code =
+  regex_must_compile(US"^\\d\\d\\d\\s(?:\\d\\.\\d\\d?\\d?\\.\\d\\d?\\d?\\s)?",
+    FALSE, TRUE);
+
 /* If the program is called as "mailq" treat it as equivalent to "exim -bp";
 this seems to be a generally accepted convention, since one finds symbolic
 links called "mailq" in standard OS configurations. */
diff -ur exim4-4.62.old/src/functions.h exim4-4.62/src/functions.h
--- exim4-4.62.old/src/functions.h	2006-04-28 10:32:22.000000000 +0000
+++ exim4-4.62/src/functions.h	2006-07-13 14:33:27.000000000 +0000
@@ -266,7 +266,7 @@
 extern int     smtp_getc(void);
 extern int     smtp_handle_acl_fail(int, int, uschar *, uschar *);
 extern BOOL    smtp_read_response(smtp_inblock *, uschar *, int, int, int);
-extern void    smtp_respond(int, BOOL, uschar *);
+extern void    smtp_respond(uschar *, int, BOOL, uschar *);
 extern void    smtp_send_prohibition_message(int, uschar *);
 extern int     smtp_setup_msg(void);
 extern BOOL    smtp_start_session(void);
diff -ur exim4-4.62.old/src/globals.c exim4-4.62/src/globals.c
--- exim4-4.62.old/src/globals.c	2006-04-28 10:32:22.000000000 +0000
+++ exim4-4.62/src/globals.c	2006-07-13 14:34:54.000000000 +0000
@@ -209,21 +209,21 @@
                                    US"VRFY"
                                  };
 
-int     acl_wherecodes[]       = { 550,     /* RCPT */
-                                   550,     /* MAIL */
-                                   550,     /* PREDATA */
-                                   550,     /* MIME */
-                                   550,     /* DATA */
-                                   0,       /* not SMTP; not relevant */
-                                   503,     /* AUTH */
-                                   550,     /* connect */
-                                   458,     /* ETRN */
-                                   550,     /* EXPN */
-                                   550,     /* HELO/EHLO */
-                                   0,       /* MAILAUTH; not relevant */
-                                   0,       /* QUIT; not relevant */
-                                   550,     /* STARTTLS */
-                                   252      /* VRFY */
+uschar *acl_wherecodes[]       = { US"550",     /* RCPT */
+                                   US"550",     /* MAIL */
+                                   US"550",     /* PREDATA */
+                                   US"550",     /* MIME */
+                                   US"550",     /* DATA */
+                                   US"0",       /* not SMTP; not relevant */
+                                   US"503",     /* AUTH */
+                                   US"550",     /* connect */
+                                   US"458",     /* ETRN */
+                                   US"550",     /* EXPN */
+                                   US"550",     /* HELO/EHLO */
+                                   US"0",       /* MAILAUTH; not relevant */
+                                   US"0",       /* QUIT; not relevant */
+                                   US"550",     /* STARTTLS */
+                                   US"252"      /* VRFY */
                                  };
 
 BOOL    active_local_from_check = FALSE;
@@ -863,6 +863,7 @@
 const pcre *regex_IGNOREQUOTA  = NULL;
 const pcre *regex_PIPELINING   = NULL;
 const pcre *regex_SIZE         = NULL;
+const pcre *regex_smtp_code    = NULL;
 const pcre *regex_ismsgid      = NULL;
 #ifdef WITH_CONTENT_SCAN
 uschar *regex_match_string     = NULL;
diff -ur exim4-4.62.old/src/globals.h exim4-4.62/src/globals.h
--- exim4-4.62.old/src/globals.h	2006-04-28 10:32:22.000000000 +0000
+++ exim4-4.62/src/globals.h	2006-07-13 14:33:27.000000000 +0000
@@ -130,7 +130,7 @@
 extern uschar *acl_var[ACL_CVARS+ACL_MVARS]; /* User ACL variables */
 extern uschar *acl_verify_message;     /* User message for verify failure */
 extern string_item *acl_warn_logged;   /* Logged lines */
-extern int     acl_wherecodes[];       /* Response codes for ACL fails */
+extern uschar *acl_wherecodes[];       /* Response codes for ACL fails */
 extern uschar *acl_wherenames[];       /* Names for messages */
 extern BOOL    active_local_from_check;/* For adding Sender: (switchable) */
 extern BOOL    active_local_sender_retain; /* For keeping Sender: (switchable) */
@@ -555,6 +555,7 @@
 extern const pcre  *regex_IGNOREQUOTA; /* For recognizing IGNOREQUOTA (LMTP) */
 extern const pcre  *regex_PIPELINING;  /* For recognizing PIPELINING */
 extern const pcre  *regex_SIZE;        /* For recognizing SIZE settings */
+extern const pcre  *regex_smtp_code;   /* For recognizing SMTP codes */
 extern const pcre  *regex_ismsgid;     /* Compiled r.e. for message it */
 #ifdef WITH_CONTENT_SCAN
 extern uschar *regex_match_string;     /* regex that matched a line (regex ACL condition) */
diff -ur exim4-4.62.old/src/receive.c exim4-4.62/src/receive.c
--- exim4-4.62.old/src/receive.c	2006-04-28 10:32:22.000000000 +0000
+++ exim4-4.62/src/receive.c	2006-07-13 14:33:27.000000000 +0000
@@ -1067,7 +1067,7 @@
          "acl_smtp_mime: error while creating mbox spool file, message temporarily rejected.");
   Uunlink(spool_name);
   unspool_mbox();
-  smtp_respond(451, TRUE, US"temporary local problem");
+  smtp_respond(US"451", 3, TRUE, US"temporary local problem");
   message_id[0] = 0;            /* Indicate no message accepted */
   *smtp_reply_ptr = US"";       /* Indicate reply already sent */
   return FALSE;                 /* Indicate skip to end of receive function */
@@ -3110,9 +3110,9 @@
   {
   uschar *istemp = US"";
   uschar *s = NULL;
+  uschar *smtp_code;
   int size = 0;
   int sptr = 0;
-  int code;
 
   errmsg = local_scan_data;
 
@@ -3129,7 +3129,7 @@
     /* Fall through */
 
     case LOCAL_SCAN_REJECT:
-    code = 550;
+    smtp_code = US"550";
     if (errmsg == NULL) errmsg =  US"Administrative prohibition";
     break;
 
@@ -3139,7 +3139,7 @@
 
     case LOCAL_SCAN_TEMPREJECT:
     TEMPREJECT:
-    code = 451;
+    smtp_code = US"451";
     if (errmsg == NULL) errmsg = US"Temporary local problem";
     istemp = US"temporarily ";
     break;
@@ -3157,14 +3157,14 @@
     {
     if (!smtp_batched_input)
       {
-      smtp_respond(code, TRUE, errmsg);
+      smtp_respond(smtp_code, 3, TRUE, errmsg);
       message_id[0] = 0;            /* Indicate no message accepted */
       smtp_reply = US"";            /* Indicate reply already sent */
       goto TIDYUP;                  /* Skip to end of function */
       }
     else
       {
-      moan_smtp_batch(NULL, "%d %s", code, errmsg);
+      moan_smtp_batch(NULL, "%s %s", smtp_code, errmsg);
       /* Does not return */
       }
     }
@@ -3483,8 +3483,8 @@
     if (smtp_reply == NULL)
       {
       if (fake_response != OK)
-        smtp_respond(fake_response == DEFER ? 450 : 550,
-                     TRUE, fake_response_text);
+        smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE,
+          fake_response_text);
       else
         smtp_printf("250 OK id=%s\r\n", message_id);
       if (host_checking)
@@ -3494,8 +3494,8 @@
     else if (smtp_reply[0] != 0)
       {
       if (fake_response != OK && (smtp_reply[0] == '2'))
-        smtp_respond(fake_response == DEFER ? 450 : 550,
-                     TRUE, fake_response_text);
+        smtp_respond((fake_response == DEFER)? US"450" : US"550", 3, TRUE,
+          fake_response_text);
       else
         smtp_printf("%.1024s\r\n", smtp_reply);
       }
diff -ur exim4-4.62.old/src/routers/redirect.c exim4-4.62/src/routers/redirect.c
--- exim4-4.62.old/src/routers/redirect.c	2006-04-28 10:32:22.000000000 +0000
+++ exim4-4.62/src/routers/redirect.c	2006-07-13 14:33:27.000000000 +0000
@@ -69,6 +69,8 @@
       (void *)offsetof(redirect_router_options_block, forbid_pipe) },
   { "forbid_sieve_filter",opt_bit | (RDON_SIEVE_FILTER << 16),
       (void *)offsetof(redirect_router_options_block, bit_options) },
+  { "forbid_smtp_code",     opt_bool,
+      (void *)offsetof(redirect_router_options_block, forbid_smtp_code) },
   { "hide_child_in_errmsg", opt_bool,
       (void *)offsetof(redirect_router_options_block,  hide_child_in_errmsg) },
   { "ignore_eacces",      opt_bit | (RDON_EACCES << 16),
@@ -167,6 +169,7 @@
   FALSE,       /* forbid_file */
   FALSE,       /* forbid_filter_reply */
   FALSE,       /* forbid_pipe */
+  FALSE,       /* forbid_smtp_code */
   FALSE,       /* hide_child_in_errmsg */
   FALSE,       /* one_time */
   FALSE,       /* qualify_preserve_domain */
@@ -709,26 +712,39 @@
   break;
 
   /* FF_DEFER and FF_FAIL can arise only as a result of explicit commands
-  (:fail: in an alias file or "fail" in a filter). If a configured message was
-  supplied, allow it to be included in an SMTP response after verifying. */
+  (:defer: or :fail: in an alias file or "fail" in a filter). If a configured
+  message was supplied, allow it to be included in an SMTP response after
+  verifying. Remove any SMTP code if it is not allowed. */
 
   case FF_DEFER:
-  if (addr->message == NULL) addr->message = US"forced defer";
-    else addr->user_message = addr->message;
-  return DEFER;
+  yield = DEFER;
+  goto SORT_MESSAGE;
 
   case FF_FAIL:
   if ((xrc = sort_errors_and_headers(rblock, addr, verify, &addr_prop)) != OK)
     return xrc;
   add_generated(rblock, addr_new, addr, generated, &addr_prop, &ugid, pw);
+  yield = FAIL;
+
+  SORT_MESSAGE:
   if (addr->message == NULL)
-    addr->message = US"forced rejection";
+    addr->message = (yield == FAIL)? US"forced rejection" : US"forced defer";
   else
     {
+    int ovector[3];
+    if (ob->forbid_smtp_code &&
+        pcre_exec(regex_smtp_code, NULL, CS addr->message,
+        Ustrlen(addr->message), 0, PCRE_EOPT,
+        ovector, sizeof(ovector)/sizeof(int)) >= 0)
+      {
+      DEBUG(D_route) debug_printf("SMTP code at start of error message "
+        "is ignored because forbid_smtp_code is set\n");
+      addr->message += ovector[1];
+      }
     addr->user_message = addr->message;
     setflag(addr, af_pass_message);
     }
-  return FAIL;
+  return yield;
 
   /* As in the case of a system filter, a freeze does not happen after a manual
   thaw. In case deliveries were set up by the filter, we set the child count
diff -ur exim4-4.62.old/src/routers/redirect.h exim4-4.62/src/routers/redirect.h
--- exim4-4.62.old/src/routers/redirect.h	2006-04-28 10:32:22.000000000 +0000
+++ exim4-4.62/src/routers/redirect.h	2006-07-13 14:33:27.000000000 +0000
@@ -51,6 +51,7 @@
   BOOL  forbid_file;
   BOOL  forbid_filter_reply;
   BOOL  forbid_pipe;
+  BOOL  forbid_smtp_code;
   BOOL  hide_child_in_errmsg;
   BOOL  one_time;
   BOOL  qualify_preserve_domain;
diff -ur exim4-4.62.old/src/smtp_in.c exim4-4.62/src/smtp_in.c
--- exim4-4.62.old/src/smtp_in.c	2006-04-28 10:32:23.000000000 +0000
+++ exim4-4.62/src/smtp_in.c	2006-07-13 14:33:27.000000000 +0000
@@ -1767,7 +1767,8 @@
 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
 
@@ -1775,26 +1776,36 @@
 */
 
 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 (codelen > 3)
+  {
+  esc = code + 4;
+  esclen = codelen - 4;
+  }
+
 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)
     {
-    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
     {
-    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++;
     }
@@ -1814,13 +1825,18 @@
 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
@@ -1837,8 +1853,10 @@
 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;
+int codelen = 3;
+int ovector[3];
+uschar *smtp_code;
 uschar *lognl;
 uschar *sender_info = US"";
 uschar *what =
@@ -1853,6 +1871,41 @@
 
 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
@@ -1888,7 +1941,7 @@
       string_sprintf(": %s", sender_verified_failed->message));
 
   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"
@@ -1918,7 +1971,7 @@
 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,
@@ -1937,12 +1990,13 @@
         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
-    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.


More information about the Pkg-exim4-maintainers mailing list