]> git.netwichtig.de Git - user/henk/code/exim.git/blobdiff - src/src/smtp_in.c
Check for lost connection or unexpected input at end of message.
[user/henk/code/exim.git] / src / src / smtp_in.c
index c9c5842b10ee0e861bca19b10974bac4998af7f3..dc96a9aa186541c37004774c31d3500fbe4aaddc 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.57 2007/04/13 15:13:47 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -120,12 +120,15 @@ static BOOL helo_seen;
 static BOOL helo_accept_junk;
 static BOOL count_nonmail;
 static BOOL pipelining_advertised;
+static BOOL rcpt_smtp_response_same;
+static BOOL rcpt_in_progress;
 static int  nonmail_command_count;
 static int  synprot_error_count;
 static int  unknown_command_count;
 static int  sync_cmd_limit;
 static int  smtp_write_error = 0;
 
+static uschar *rcpt_smtp_response;
 static uschar *smtp_data_buffer;
 static uschar *smtp_cmd_data;
 
@@ -330,6 +333,23 @@ return smtp_had_error;
 
 
 
+/*************************************************
+*      Test for characters in the SMTP buffer    *
+*************************************************/
+
+/* Used at the end of a message
+
+Arguments:     none
+Returns:       TRUE/FALSE
+*/
+
+BOOL
+smtp_buffered(void)
+{
+return smtp_inptr < smtp_inend;
+}
+
+
 
 /*************************************************
 *     Write formatted string to SMTP channel     *
@@ -368,28 +388,41 @@ DEBUG(D_receive)
   }
 
 va_start(ap, format);
+if (!string_vformat(big_buffer, big_buffer_size, format, ap))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()");
+  smtp_closedown(US"Unexpected error");
+  exim_exit(EXIT_FAILURE);
+  }
+va_end(ap);
 
-/* If in a TLS session we have to format the string, and then write it using a
-TLS function. */
+/* If this is the first output for a (non-batch) RCPT command, see if all RCPTs
+have had the same. Note: this code is also present in smtp_respond(). It would
+be tidier to have it only in one place, but when it was added, it was easier to
+do it that way, so as not to have to mess with the code for the RCPT command,
+which sometimes uses smtp_printf() and sometimes smtp_respond(). */
+
+if (rcpt_in_progress)
+  {
+  if (rcpt_smtp_response == NULL)
+    rcpt_smtp_response = string_copy(big_buffer);
+  else if (rcpt_smtp_response_same &&
+           Ustrcmp(rcpt_smtp_response, big_buffer) != 0)
+    rcpt_smtp_response_same = FALSE;
+  rcpt_in_progress = FALSE;
+  }
+
+/* Now write the string */
 
 #ifdef SUPPORT_TLS
 if (tls_active >= 0)
   {
-  if (!string_vformat(big_buffer, big_buffer_size, format, ap))
-    {
-    log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf");
-    smtp_closedown(US"Unexpected error");
-    exim_exit(EXIT_FAILURE);
-    }
   if (tls_write(big_buffer, Ustrlen(big_buffer)) < 0) smtp_write_error = -1;
   }
 else
 #endif
 
-/* Otherwise, just use the standard library function. */
-
-if (vfprintf(smtp_out, format, ap) < 0) smtp_write_error = -1;
-va_end(ap);
+if (fprintf(smtp_out, "%s", big_buffer) < 0) smtp_write_error = -1;
 }
 
 
@@ -469,6 +502,7 @@ exim_exit(EXIT_FAILURE);
 
 
 
+
 /*************************************************
 *           Read one command line                *
 *************************************************/
@@ -603,6 +637,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              *
 *************************************************/
@@ -906,6 +994,9 @@ message_linecount = 0;
 message_size = -1;
 acl_added_headers = NULL;
 queue_only_policy = FALSE;
+rcpt_smtp_response = NULL;
+rcpt_smtp_response_same = TRUE;
+rcpt_in_progress = 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 */
@@ -1304,6 +1395,7 @@ receive_getc = smtp_getc;
 receive_ungetc = smtp_ungetc;
 receive_feof = smtp_feof;
 receive_ferror = smtp_ferror;
+receive_smtp_buffered = smtp_buffered;
 smtp_inptr = smtp_inend = smtp_inbuffer;
 smtp_had_eof = smtp_had_error = 0;
 
@@ -1760,30 +1852,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 */
@@ -1915,6 +1991,24 @@ if (codelen > 4)
   esclen = codelen - 4;
   }
 
+/* If this is the first output for a (non-batch) RCPT command, see if all RCPTs
+have had the same. Note: this code is also present in smtp_printf(). It would
+be tidier to have it only in one place, but when it was added, it was easier to
+do it that way, so as not to have to mess with the code for the RCPT command,
+which sometimes uses smtp_printf() and sometimes smtp_respond(). */
+
+if (rcpt_in_progress)
+  {
+  if (rcpt_smtp_response == NULL)
+    rcpt_smtp_response = string_copy(msg);
+  else if (rcpt_smtp_response_same &&
+           Ustrcmp(rcpt_smtp_response, msg) != 0)
+    rcpt_smtp_response_same = FALSE;
+  rcpt_in_progress = FALSE;
+  }
+
+/* Not output the message, splitting it up into multiple lines if necessary. */
+
 for (;;)
   {
   uschar *nl = Ustrchr(msg, '\n');
@@ -2084,6 +2178,9 @@ unless the sender_verify_fail log selector has been turned off. */
 if (sender_verified_failed != NULL &&
     !testflag(sender_verified_failed, af_sverify_told))
   {
+  BOOL save_rcpt_in_progress = rcpt_in_progress;
+  rcpt_in_progress = FALSE;  /* So as not to treat these as the error */
+
   setflag(sender_verified_failed, af_sverify_told);
 
   if (rc != FAIL || (log_extra_selector & LX_sender_verify_fail) != 0)
@@ -2113,6 +2210,8 @@ if (sender_verified_failed != NULL &&
           "Verification failed for <%s>\n%s",
         sender_verified_failed->address,
         sender_verified_failed->user_message));
+
+  rcpt_in_progress = save_rcpt_in_progress;
   }
 
 /* Sort out text for logging */
@@ -2731,7 +2830,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 +2843,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 +3337,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)
       {
@@ -3257,17 +3364,15 @@ while (done <= 0)
     break;
 
 
-    /* The RCPT command requires an address as an operand. All we do
-    here is to parse it for syntactic correctness. There may be any number
-    of RCPT commands, specifying multiple senders. We build them all into
-    a data structure that is in argc/argv format. The start/end values
-    given by parse_extract_address are not used, as we keep only the
-    extracted address. */
+    /* The RCPT command requires an address as an operand. There may be any
+    number of RCPT commands, specifying multiple recipients. We build them all
+    into a data structure. The start/end values given by parse_extract_address
+    are not used, as we keep only the extracted address. */
 
     case RCPT_CMD:
     HAD(SCH_RCPT);
     rcpt_count++;
-    was_rcpt = TRUE;
+    was_rcpt = rcpt_in_progress = TRUE;
 
     /* There must be a sender address; if the sender was rejected and
     pipelining was advertised, we assume the client was pipelining, and do not
@@ -3395,10 +3500,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 */
 
@@ -3450,14 +3562,29 @@ while (done <= 0)
         DATA command.
 
     The example in the pipelining RFC 2920 uses 554, but I use 503 here
-    because it is the same whether pipelining is in use or not. */
+    because it is the same whether pipelining is in use or not.
+
+    If all the RCPT commands that precede DATA provoked the same error message
+    (often indicating some kind of system error), it is helpful to include it
+    with the DATA rejection (an idea suggested by Tony Finch). */
 
     case DATA_CMD:
     HAD(SCH_DATA);
     if (!discarded && recipients_count <= 0)
       {
+      if (rcpt_smtp_response_same && rcpt_smtp_response != NULL)
+        {
+        uschar *code = US"503";
+        int len = Ustrlen(rcpt_smtp_response);
+        smtp_respond(code, 3, FALSE, US"All RCPT commands were rejected with "
+          "this error:");
+        /* Responses from smtp_printf() will have \r\n on the end */
+        if (len > 2 && rcpt_smtp_response[len-2] == '\r')
+          rcpt_smtp_response[len-2] = 0;
+        smtp_respond(code, 3, FALSE, rcpt_smtp_response);
+        }
       if (pipelining_advertised && last_was_rcpt)
-        smtp_printf("503 valid RCPT command must precede DATA\r\n");
+        smtp_printf("503 Valid RCPT command must precede DATA\r\n");
       else
         done = synprot_error(L_smtp_protocol_error, 503, NULL,
           US"valid RCPT command must precede DATA");
@@ -3472,12 +3599,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 +3624,6 @@ while (done <= 0)
 
     else
       done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg);
-
     break;
 
 
@@ -3942,6 +4072,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;