Add extra sync checks after ACLs that might delay.
authorPhilip Hazel <ph10@hermes.cam.ac.uk>
Tue, 20 Feb 2007 15:58:02 +0000 (15:58 +0000)
committerPhilip Hazel <ph10@hermes.cam.ac.uk>
Tue, 20 Feb 2007 15:58:02 +0000 (15:58 +0000)
doc/doc-txt/ChangeLog
src/src/smtp_in.c
test/README
test/confs/0556 [new file with mode: 0644]
test/log/0556 [new file with mode: 0644]
test/rejectlog/0556 [new file with mode: 0644]
test/scripts/0000-Basic/0556 [new file with mode: 0644]
test/stdout/0556 [new file with mode: 0644]

index 96832f60430c6aacf2fa7451b6b38d6da2247b9f..6e61fa9b6786adeb4b6e5ea1ae83e8f9a3ebdda7 100644 (file)
@@ -1,4 +1,4 @@
-$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.483 2007/02/20 11:37:16 ph10 Exp $
+$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.484 2007/02/20 15:58:02 ph10 Exp $
 
 Change log file for Exim from version 4.21
 -------------------------------------------
@@ -123,6 +123,14 @@ PH/28 The $smtp_command and $smtp_command_argument variables were not correct
       address, for example: MAIL FROM:<foo@bar> SIZE=1234. The option settings
       were accidentally chopped off.
 
+PH/29 SMTP synchronization checks are implemented when a command is read -
+      there is a check that no more input is waiting when there shouldn't be
+      any. However, for some commands, a delay in an ACL can mean that it is
+      some time before the response is written. In this time, more input might
+      arrive, invalidly. So now there are extra checks after an ACL has run for
+      HELO/EHLO and after the predata ACL, and likewise for MAIL and RCPT when
+      pipelining has not been advertised.
+
 
 Exim version 4.66
 -----------------
index c9c5842b10ee0e861bca19b10974bac4998af7f3..d30d28280a94a3ae3325f3fadfe37814351929a3 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/smtp_in.c,v 1.54 2007/02/20 11:37:16 ph10 Exp $ */
+/* $Cambridge: exim/src/src/smtp_in.c,v 1.55 2007/02/20 15:58:02 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -469,6 +469,7 @@ exim_exit(EXIT_FAILURE);
 
 
 
+
 /*************************************************
 *           Read one command line                *
 *************************************************/
@@ -603,6 +604,60 @@ return OTHER_CMD;
 
 
 
+/*************************************************
+*          Recheck synchronization               *
+*************************************************/
+
+/* Synchronization checks can never be perfect because a packet may be on its
+way but not arrived when the check is done. Such checks can in any case only be
+done when TLS is not in use. Normally, the checks happen when commands are
+read: Exim ensures that there is no more input in the input buffer. In normal
+cases, the response to the command will be fast, and there is no further check.
+
+However, for some commands an ACL is run, and that can include delays. In those
+cases, it is useful to do another check on the input just before sending the
+response. This also applies at the start of a connection. This function does
+that check by means of the select() function, as long as the facility is not
+disabled or inappropriate. A failure of select() is ignored.
+
+When there is unwanted input, we read it so that it appears in the log of the
+error.
+
+Arguments: none
+Returns:   TRUE if all is well; FALSE if there is input pending
+*/
+
+static BOOL
+check_sync(void)
+{
+int fd, rc;
+fd_set fds;
+struct timeval tzero;
+
+if (!smtp_enforce_sync || sender_host_address == NULL ||
+    sender_host_notsocket || tls_active >= 0)
+  return TRUE;
+
+fd = fileno(smtp_in);
+FD_ZERO(&fds);
+FD_SET(fd, &fds);
+tzero.tv_sec = 0;
+tzero.tv_usec = 0;
+rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero);
+
+if (rc <= 0) return TRUE;     /* Not ready to read */
+rc = smtp_getc();
+if (rc < 0) return TRUE;      /* End of file or error */
+
+smtp_ungetc(rc);
+rc = smtp_inend - smtp_inptr;
+if (rc > 150) rc = 150;
+smtp_inptr[rc] = 0;
+return FALSE;
+}
+
+
+
 /*************************************************
 *          Forced closedown of call              *
 *************************************************/
@@ -1760,30 +1815,14 @@ ss[ptr] = 0;  /* string_cat leaves room for this */
 /* Before we write the banner, check that there is no input pending, unless
 this synchronisation check is disabled. */
 
-if (smtp_enforce_sync && sender_host_address != NULL && !sender_host_notsocket)
+if (!check_sync())
   {
-  fd_set fds;
-  struct timeval tzero;
-  tzero.tv_sec = 0;
-  tzero.tv_usec = 0;
-  FD_ZERO(&fds);
-  FD_SET(fileno(smtp_in), &fds);
-  if (select(fileno(smtp_in) + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL,
-      &tzero) > 0)
-    {
-    int rc = read(fileno(smtp_in), smtp_inbuffer, in_buffer_size);
-    if (rc > 0)
-      {
-      if (rc > 150) rc = 150;
-      smtp_inbuffer[rc] = 0;
-      log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
-        "synchronization error (input sent without waiting for greeting): "
-        "rejected connection from %s input=\"%s\"", host_and_ident(TRUE),
-        string_printing(smtp_inbuffer));
-      smtp_printf("554 SMTP synchronization error\r\n");
-      return FALSE;
-      }
-    }
+  log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
+    "synchronization error (input sent without waiting for greeting): "
+    "rejected connection from %s input=\"%s\"", host_and_ident(TRUE),
+    string_printing(smtp_inptr));
+  smtp_printf("554 SMTP synchronization error\r\n");
+  return FALSE;
   }
 
 /* Now output the banner */
@@ -2731,7 +2770,8 @@ while (done <= 0)
     spf_init(sender_helo_name, sender_host_address);
 #endif
 
-    /* Apply an ACL check if one is defined */
+    /* 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)
       {
@@ -2743,6 +2783,7 @@ while (done <= 0)
         host_build_sender_fullhost();  /* Rebuild */
         break;
         }
+      else if (!check_sync()) goto SYNC_FAILURE;
       }
 
     /* Generate an OK reply. The default string includes the ident if present,
@@ -3236,10 +3277,16 @@ while (done <= 0)
         }
       }
 
-    /* Apply an ACL check if one is defined, before responding */
+    /* Apply an ACL check if one is defined, before responding. Afterwards,
+    when pipelining is not advertised, do another sync check in case the ACL
+    delayed and the client started sending in the meantime. */
 
-    rc = (acl_smtp_mail == NULL)? OK :
-      acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
+    if (acl_smtp_mail == NULL) rc = OK; else
+      {
+      rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
+      if (rc == OK && !pipelining_advertised && !check_sync())
+        goto SYNC_FAILURE;
+      }
 
     if (rc == OK || rc == DISCARD)
       {
@@ -3395,10 +3442,17 @@ while (done <= 0)
       }
 
     /* If the MAIL ACL discarded all the recipients, we bypass ACL checking
-    for them. Otherwise, check the access control list for this recipient. */
+    for them. Otherwise, check the access control list for this recipient. As
+    there may be a delay in this, re-check for a synchronization error
+    afterwards, unless pipelining was advertised. */
 
-    rc = recipients_discarded? DISCARD :
-      acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg, &log_msg);
+    if (recipients_discarded) rc = DISCARD; else
+      {
+      rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg,
+        &log_msg);
+      if (rc == OK && !pipelining_advertised && !check_sync())
+        goto SYNC_FAILURE;
+      }
 
     /* The ACL was happy */
 
@@ -3472,12 +3526,16 @@ while (done <= 0)
       break;
       }
 
+    /* If there is an ACL, re-check the synchronization afterwards, since the
+    ACL may have delayed. */
+
     if (acl_smtp_predata == NULL) rc = OK; else
       {
       enable_dollar_recipients = TRUE;
       rc = acl_check(ACL_WHERE_PREDATA, NULL, acl_smtp_predata, &user_msg,
         &log_msg);
       enable_dollar_recipients = FALSE;
+      if (rc == OK && !check_sync()) goto SYNC_FAILURE;
       }
 
     if (rc == OK)
@@ -3493,7 +3551,6 @@ while (done <= 0)
 
     else
       done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg);
-
     break;
 
 
@@ -3942,6 +3999,7 @@ while (done <= 0)
 
 
     case BADSYN_CMD:
+    SYNC_FAILURE:
     if (smtp_inend >= smtp_inbuffer + in_buffer_size)
       smtp_inend = smtp_inbuffer + in_buffer_size - 1;
     c = smtp_inend - smtp_inptr;
index 5c060974c136a783829d829188f78698c408dc3c..c1398329df67b430aeac93bf4c08eabe009c92ba 100644 (file)
@@ -1,4 +1,4 @@
-$Cambridge: exim/test/README,v 1.7 2007/01/31 16:52:12 ph10 Exp $
+$Cambridge: exim/test/README,v 1.8 2007/02/20 15:58:02 ph10 Exp $
 
 EXPORTABLE EXIM TEST SUITE
 --------------------------
@@ -6,7 +6,7 @@ EXPORTABLE EXIM TEST SUITE
 This document last updated for:
 
 Test Suite Version: 4.67
-Date: 31 January 2007
+Date: 20 February 2007
 
 
 BACKGROUND
@@ -765,9 +765,12 @@ as well as to the named file.
 
 This command runs the auxiliary "client" program that simulates an SMTP client.
 It is controlled by a script read from its standard input, details of which are
-given below. The only option is -t, which must be followed by a number, to
-specify the command timeout in seconds. The program connects to the given IP
-address and port, using the specified interface, if one is given.
+given below. There are two options. One is -t, which must be followed directly
+by a number, to specify the command timeout in seconds (e.g. -t5). The default
+timeout is 1 second. The other option is -tls-on-connect, which causes the
+client to try to start up a TLS session as soon as it has connected, without
+using the STARTTLS command. The client program connects to the given IP address
+and port, using the specified interface, if one is given.
 
 
   client-ssl [<options>] <ip address> <port> [<outgoing interface>] \
diff --git a/test/confs/0556 b/test/confs/0556
new file mode 100644 (file)
index 0000000..7ed0105
--- /dev/null
@@ -0,0 +1,39 @@
+# Exim test configuration 0556
+
+ACL_MAIL=accept
+ACL_RCPT=accept
+ACL_PREDATA=accept
+PAH=127.0.0.1
+
+exim_path = EXIM_PATH
+host_lookup_order = bydns
+primary_hostname = myhost.test.ex
+rfc1413_query_timeout = 0s
+spool_directory = DIR/spool
+log_file_path = DIR/spool/log/%slog
+gecos_pattern = ""
+gecos_name = CALLER_NAME
+
+# ----- Main settings -----
+
+acl_smtp_predata = ACL_PREDATA
+acl_smtp_mail = ACL_MAIL
+acl_smtp_rcpt = ACL_RCPT
+pipelining_advertise_hosts = PAH
+
+queue_only
+
+# ----- ACL -----
+
+begin ACL
+
+check_predata:
+  accept     delay = 2s
+
+check_mail:
+  accept     delay = 2s
+
+check_rcpt:
+  accept     delay = 2s
+
+# End
diff --git a/test/log/0556 b/test/log/0556
new file mode 100644 (file)
index 0000000..5f1631c
--- /dev/null
@@ -0,0 +1,5 @@
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 SMTP protocol synchronization error (next input sent too soon: pipelining was advertised): rejected "data" H=(abcd) [127.0.0.1] next input="Start: sent early ...\r\n"
+1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225
+1999-03-02 09:44:33 SMTP protocol synchronization error (next input sent too soon: pipelining was not advertised): rejected "mail from:<userx@test.ex>" H=(abcd) [127.0.0.1] next input="rcpt to:<userx@test.ex>\r\n"
+1999-03-02 09:44:33 SMTP protocol synchronization error (next input sent too soon: pipelining was not advertised): rejected "rcpt to:<userx@test.ex>" H=(abcd) [127.0.0.1] next input="data\r\n"
diff --git a/test/rejectlog/0556 b/test/rejectlog/0556
new file mode 100644 (file)
index 0000000..2f0d87f
--- /dev/null
@@ -0,0 +1,3 @@
+1999-03-02 09:44:33 SMTP protocol synchronization error (next input sent too soon: pipelining was advertised): rejected "data" H=(abcd) [127.0.0.1] next input="Start: sent early ...\r\n"
+1999-03-02 09:44:33 SMTP protocol synchronization error (next input sent too soon: pipelining was not advertised): rejected "mail from:<userx@test.ex>" H=(abcd) [127.0.0.1] next input="rcpt to:<userx@test.ex>\r\n"
+1999-03-02 09:44:33 SMTP protocol synchronization error (next input sent too soon: pipelining was not advertised): rejected "rcpt to:<userx@test.ex>" H=(abcd) [127.0.0.1] next input="data\r\n"
diff --git a/test/scripts/0000-Basic/0556 b/test/scripts/0000-Basic/0556
new file mode 100644 (file)
index 0000000..54c3a86
--- /dev/null
@@ -0,0 +1,55 @@
+# SMTP synchronization checks before sending responses
+need_ipv4
+#
+exim -DSERVER=server -DACL_PREDATA=check_predata -bd -oX PORT_D
+****
+# The pause (+++ 1) in the middle of this is so that there is no pending
+# input when DATA is received, but we start sending the data itself too
+# early (the server will be waiting 2 seconds in the predata ACL).
+#
+client 127.0.0.1 PORT_D
+??? 220
+ehlo abcd
+??? 250-
+??? 250-
+??? 250-
+??? 250
+rset\r\nmail from:<userx@test.ex>\r\nrcpt to:<userx@test.ex>\r\ndata
++++ 1
+Start: sent early ...
+??? 250
+??? 250
+??? 250
+??? 554
+****
+sleep 1
+killdaemon
+# This time turn off pipelining to check MAIL and RCPT
+exim -DSERVER=server -DACL_MAIL=check_mail -DACL_RCPT=check_rcpt -DPAH= \
+     -bd -oX PORT_D
+****
+client -t5 127.0.0.1 PORT_D
+??? 220
+ehlo abcd
+??? 250-
+??? 250-
+??? 250
+mail from:<userx@test.ex>
++++ 1
+rcpt to:<userx@test.ex>
+??? 554
+****
+client -t5 127.0.0.1 PORT_D
+??? 220
+ehlo abcd
+??? 250-
+??? 250-
+??? 250
+mail from:<userx@test.ex>
+??? 250
+rcpt to:<userx@test.ex>
++++ 1
+data
+??? 554
+****
+killdaemon      
diff --git a/test/stdout/0556 b/test/stdout/0556
new file mode 100644 (file)
index 0000000..d8d60ab
--- /dev/null
@@ -0,0 +1,59 @@
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> ehlo abcd
+??? 250-
+<<< 250-myhost.test.ex Hello abcd [127.0.0.1]
+??? 250-
+<<< 250-SIZE 52428800
+??? 250-
+<<< 250-PIPELINING
+??? 250
+<<< 250 HELP
+>>> rset\r\nmail from:<userx@test.ex>\r\nrcpt to:<userx@test.ex>\r\ndata
++++ 1
+>>> Start: sent early ...
+??? 250
+<<< 250 Reset OK
+??? 250
+<<< 250 OK
+??? 250
+<<< 250 Accepted
+??? 554
+<<< 554 SMTP synchronization error
+End of script
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> ehlo abcd
+??? 250-
+<<< 250-myhost.test.ex Hello abcd [127.0.0.1]
+??? 250-
+<<< 250-SIZE 52428800
+??? 250
+<<< 250 HELP
+>>> mail from:<userx@test.ex>
++++ 1
+>>> rcpt to:<userx@test.ex>
+??? 554
+<<< 554 SMTP synchronization error
+End of script
+Connecting to 127.0.0.1 port 1225 ... connected
+??? 220
+<<< 220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
+>>> ehlo abcd
+??? 250-
+<<< 250-myhost.test.ex Hello abcd [127.0.0.1]
+??? 250-
+<<< 250-SIZE 52428800
+??? 250
+<<< 250 HELP
+>>> mail from:<userx@test.ex>
+??? 250
+<<< 250 OK
+>>> rcpt to:<userx@test.ex>
++++ 1
+>>> data
+??? 554
+<<< 554 SMTP synchronization error
+End of script