Apply Jakob Hirsch's patch for arbitrary ACL variable names, tidied up
[exim.git] / src / src / smtp_in.c
index e832358363fdf7da7059ced753d6dace571eebbc..614a3ffe0fb49f15db057e2770c031dc71186b0b 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/smtp_in.c,v 1.37 2006/03/16 11:14:46 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    *
@@ -523,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 */
@@ -790,8 +793,6 @@ return TRUE;
 
 
 
 
 
 
-
-
 /*************************************************
 *         Reset for new message                  *
 *************************************************/
 /*************************************************
 *         Reset for new message                  *
 *************************************************/
@@ -806,7 +807,6 @@ 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 =
 store_reset(reset_point);
 recipients_list = NULL;
 rcpt_count = rcpt_defer_count = rcpt_fail_count =
@@ -852,9 +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. */
 
-/* The message variables follow the connection variables. */
+/* Reset message ACL variables */
 
 
-for (i = 0; i < ACL_MVARS; i++) acl_var[ACL_CVARS + i] = NULL;
+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
@@ -1141,7 +1141,7 @@ BOOL
 smtp_start_session(void)
 {
 int size = 256;
 smtp_start_session(void)
 {
 int size = 256;
-int i, ptr;
+int ptr;
 uschar *p, *s, *ss;
 
 /* Default values for certain variables */
 uschar *p, *s, *ss;
 
 /* Default values for certain variables */
@@ -1156,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
@@ -1166,7 +1169,7 @@ tls_advertised = FALSE;
 
 /* Reset ACL connection variables */
 
 
 /* Reset ACL connection variables */
 
-for (i = 0; i < ACL_CVARS; 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. */
 
@@ -1205,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: "
@@ -1459,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
@@ -1746,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
 
@@ -1754,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++;
     }
@@ -1793,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
@@ -1816,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 =
@@ -1832,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
@@ -1867,7 +1944,7 @@ if (sender_verified_failed != NULL &&
       string_sprintf(": %s", sender_verified_failed->message));
 
   if (rc == FAIL && sender_verified_failed->user_message != NULL)
       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"
         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"
@@ -1897,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,
@@ -1916,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.