Teach SMTP input sync check ("input sent too soon") about SMTP input buffering
[exim.git] / src / src / smtp_in.c
index 4cda753e8550bf78bb55bf5841a4d75703fac5fc..8832908f38a61e3234ac435094a9ce1cc6438947 100644 (file)
@@ -343,6 +343,9 @@ if (!smtp_enforce_sync || sender_host_address == NULL ||
     sender_host_notsocket || tls_in.active >= 0)
   return TRUE;
 
+if (smtp_inptr < smtp_inend)
+  return FALSE;
+
 fd = fileno(smtp_in);
 FD_ZERO(&fds);
 FD_SET(fd, &fds);
@@ -400,6 +403,44 @@ log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS,
 
 
 
+/* Refill the buffer, and notify DKIM verification code.
+Return false for error or EOF.
+*/
+
+static BOOL
+smtp_refill(unsigned lim)
+{
+int rc, save_errno;
+if (!smtp_out) return FALSE;
+fflush(smtp_out);
+if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
+
+/* Limit amount read, so non-message data is not fed to DKIM */
+
+rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE, lim));
+save_errno = errno;
+alarm(0);
+if (rc <= 0)
+  {
+  /* Must put the error text in fixed store, because this might be during
+  header reading, where it releases unused store above the header. */
+  if (rc < 0)
+    {
+    smtp_had_error = save_errno;
+    smtp_read_error = string_copy_malloc(
+      string_sprintf(" (error: %s)", strerror(save_errno)));
+    }
+  else smtp_had_eof = 1;
+  return FALSE;
+  }
+#ifndef DISABLE_DKIM
+dkim_exim_verify_feed(smtp_inbuffer, rc);
+#endif
+smtp_inend = smtp_inbuffer + rc;
+smtp_inptr = smtp_inbuffer;
+return TRUE;
+}
+
 /*************************************************
 *          SMTP version of getc()                *
 *************************************************/
@@ -417,39 +458,28 @@ int
 smtp_getc(unsigned lim)
 {
 if (smtp_inptr >= smtp_inend)
-  {
-  int rc, save_errno;
-  if (!smtp_out) return EOF;
-  fflush(smtp_out);
-  if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
-
-  /* Limit amount read, so non-message data is not fed to DKIM */
-
-  rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE, lim));
-  save_errno = errno;
-  alarm(0);
-  if (rc <= 0)
-    {
-    /* Must put the error text in fixed store, because this might be during
-    header reading, where it releases unused store above the header. */
-    if (rc < 0)
-      {
-      smtp_had_error = save_errno;
-      smtp_read_error = string_copy_malloc(
-        string_sprintf(" (error: %s)", strerror(save_errno)));
-      }
-    else smtp_had_eof = 1;
+  if (!smtp_refill(lim))
     return EOF;
-    }
-#ifndef DISABLE_DKIM
-  dkim_exim_verify_feed(smtp_inbuffer, rc);
-#endif
-  smtp_inend = smtp_inbuffer + rc;
-  smtp_inptr = smtp_inbuffer;
-  }
 return *smtp_inptr++;
 }
 
+uschar *
+smtp_getbuf(unsigned * len)
+{
+unsigned size;
+uschar * buf;
+
+if (smtp_inptr >= smtp_inend)
+  if (!smtp_refill(*len))
+    { *len = 0; return NULL; }
+
+if ((size = smtp_inend - smtp_inptr) > *len) size = *len;
+buf = smtp_inptr;
+smtp_inptr += size;
+*len = size;
+return buf;
+}
+
 void
 smtp_get_cache(void)
 {
@@ -493,6 +523,7 @@ for(;;)
     return lwr_receive_getc(chunking_data_left--);
 
   receive_getc = lwr_receive_getc;
+  receive_getbuf = lwr_receive_getbuf;
   receive_ungetc = lwr_receive_ungetc;
 #ifndef DISABLE_DKIM
   dkim_save = dkim_collect_input;
@@ -504,12 +535,15 @@ for(;;)
 
   if (!pipelining_advertised && !check_sync())
     {
+    unsigned n = smtp_inend - smtp_inptr;
+    if (n > 32) n = 32;
+
     incomplete_transaction_log(US"sync failure");
     log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
       "(next input sent too soon: pipelining was not advertised): "
       "rejected \"%s\" %s next input=\"%s\"",
       smtp_cmd_buffer, host_and_ident(TRUE),
-      string_printing(smtp_inptr));
+      string_printing(string_copyn(smtp_inptr, n)));
       (void) synprot_error(L_smtp_protocol_error, 554, NULL,
        US"SMTP synchronization error");
     goto repeat_until_rset;
@@ -594,6 +628,7 @@ next_cmd:
          }
 
       receive_getc = bdat_getc;
+      receive_getbuf = bdat_getbuf;
       receive_ungetc = bdat_ungetc;
 #ifndef DISABLE_DKIM
       dkim_collect_input = dkim_save;
@@ -604,14 +639,28 @@ next_cmd:
   }
 }
 
+uschar *
+bdat_getbuf(unsigned * len)
+{
+uschar * buf;
+
+if (chunking_data_left <= 0)
+  { *len = 0; return NULL; }
+
+if (*len > chunking_data_left) *len = chunking_data_left;
+buf = lwr_receive_getbuf(len); /* Either smtp_getbuf or tls_getbuf */
+chunking_data_left -= *len;
+return buf;
+}
+
 void
 bdat_flush_data(void)
 {
-while (chunking_data_left > 0)
-  if (lwr_receive_getc(chunking_data_left--) < 0)
-    break;
+unsigned n = chunking_data_left;
+(void) bdat_getbuf(&n);
 
 receive_getc = lwr_receive_getc;
+receive_getbuf = lwr_receive_getbuf;
 receive_ungetc = lwr_receive_ungetc;
 
 if (chunking_state != CHUNKING_LAST)
@@ -949,43 +998,45 @@ Return the amount read.
 static int
 swallow_until_crlf(int fd, uschar *base, int already, int capacity)
 {
-  uschar *to = base + already;
-  uschar *cr;
-  int have = 0;
-  int ret;
-  int last = 0;
-
-  /* For "PROXY UNKNOWN\r\n" we, at time of writing, expect to have read
-  up through the \r; for the _normal_ case, we haven't yet seen the \r. */
-  cr = memchr(base, '\r', already);
-  if (cr != NULL)
-    {
-    if ((cr - base) < already - 1)
-      {
-      /* \r and presumed \n already within what we have; probably not
-      actually proxy protocol, but abort cleanly. */
-      return 0;
-      }
-    /* \r is last character read, just need one more. */
-    last = 1;
-    }
+uschar *to = base + already;
+uschar *cr;
+int have = 0;
+int ret;
+int last = 0;
+
+/* For "PROXY UNKNOWN\r\n" we, at time of writing, expect to have read
+up through the \r; for the _normal_ case, we haven't yet seen the \r. */
 
-  while (capacity > 0)
+cr = memchr(base, '\r', already);
+if (cr != NULL)
+  {
+  if ((cr - base) < already - 1)
     {
-    do { ret = recv(fd, to, 1, 0); } while (ret == -1 && errno == EINTR);
-    if (ret == -1)
-      return -1;
-    have++;
-    if (last)
-      return have;
-    if (*to == '\r')
-      last = 1;
-    capacity--;
-    to++;
+    /* \r and presumed \n already within what we have; probably not
+    actually proxy protocol, but abort cleanly. */
+    return 0;
     }
-  // reached end without having room for a final newline, abort
-  errno = EOVERFLOW;
-  return -1;
+  /* \r is last character read, just need one more. */
+  last = 1;
+  }
+
+while (capacity > 0)
+  {
+  do { ret = recv(fd, to, 1, 0); } while (ret == -1 && errno == EINTR);
+  if (ret == -1)
+    return -1;
+  have++;
+  if (last)
+    return have;
+  if (*to == '\r')
+    last = 1;
+  capacity--;
+  to++;
+  }
+
+/* reached end without having room for a final newline, abort */
+errno = EOVERFLOW;
+return -1;
 }
 
 /*************************************************
@@ -1093,9 +1144,9 @@ if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tv, sizeof(tv)) < 0)
 do
   {
   /* The inbound host was declared to be a Proxy Protocol host, so
-     don't do a PEEK into the data, actually slurp up enough to be
-     "safe". Can't take it all because TLS-on-connect clients follow
-     immediately with TLS handshake. */
+  don't do a PEEK into the data, actually slurp up enough to be
+  "safe". Can't take it all because TLS-on-connect clients follow
+  immediately with TLS handshake. */
   ret = recv(fd, &hdr, PROXY_INITIAL_READ, 0);
   }
   while (ret == -1 && errno == EINTR);
@@ -1121,9 +1172,9 @@ if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
   ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
 
   /* May 2014: haproxy combined the version and command into one byte to
-     allow two full bytes for the length field in order to proxy SSL
-     connections.  SSL Proxy is not supported in this version of Exim, but
-     must still separate values here. */
+  allow two full bytes for the length field in order to proxy SSL
+  connections.  SSL Proxy is not supported in this version of Exim, but
+  must still separate values here. */
 
   if (ver != 0x02)
     {
@@ -1270,7 +1321,7 @@ else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0)
   DEBUG(D_receive) debug_printf("Detected PROXYv1 header\n");
   DEBUG(D_receive) debug_printf("Bytes read not within PROXY header: %d\n", ret - size);
   /* Step through the string looking for the required fields. Ensure
-     strict adherence to required formatting, exit for any error. */
+  strict adherence to required formatting, exit for any error. */
   p += 5;
   if (!isspace(*(p++)))
     {
@@ -1885,7 +1936,6 @@ smtp_reset(void *reset_point)
 recipients_list = NULL;
 rcpt_count = rcpt_defer_count = rcpt_fail_count =
   raw_recipients_count = recipients_count = recipients_list_max = 0;
-cancel_cutthrough_connection("smtp reset");
 message_linecount = 0;
 message_size = -1;
 acl_added_headers = NULL;
@@ -2016,6 +2066,7 @@ bsmtp_transaction_linecount = receive_linecount;
 
 if ((receive_feof)()) return 0;   /* Treat EOF as QUIT */
 
+cancel_cutthrough_connection(TRUE, US"smtp_setup_batch_msg");
 smtp_reset(reset_point);                /* Reset for start of message */
 
 /* Deal with SMTP commands. This loop is exited by setting done to a POSITIVE
@@ -2040,6 +2091,7 @@ while (done <= 0)
     /* Fall through */
 
     case RSET_CMD:
+    cancel_cutthrough_connection(TRUE, US"RSET received");
     smtp_reset(reset_point);
     bsmtp_transaction_linecount = receive_linecount;
     break;
@@ -2063,6 +2115,7 @@ while (done <= 0)
 
     /* Reset to start of message */
 
+    cancel_cutthrough_connection(TRUE, US"MAIL received");
     smtp_reset(reset_point);
 
     /* Apply SMTP rewrite */
@@ -2328,6 +2381,7 @@ if (!(smtp_inbuffer = (uschar *)malloc(IN_BUFFER_SIZE)))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malloc() failed for SMTP input buffer");
 
 receive_getc = smtp_getc;
+receive_getbuf = smtp_getbuf;
 receive_get_cache = smtp_get_cache;
 receive_ungetc = smtp_ungetc;
 receive_feof = smtp_feof;
@@ -2815,10 +2869,13 @@ this synchronisation check is disabled. */
 
 if (!check_sync())
   {
+  unsigned n = smtp_inend - smtp_inptr;
+  if (n > 32) n = 32;
+
   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));
+    string_printing(string_copyn(smtp_inptr, n)));
   smtp_printf("554 SMTP synchronization error\r\n");
   return FALSE;
   }
@@ -4251,6 +4308,7 @@ while (done <= 0)
          : pnormal)
        + (tls_in.active >= 0 ? pcrpted : 0)
        ];
+    cancel_cutthrough_connection(TRUE, US"sent EHLO response");
     smtp_reset(reset_point);
     toomany = FALSE;
     break;   /* HELO/EHLO */
@@ -4305,6 +4363,7 @@ while (done <= 0)
     /* Reset for start of message - even if this is going to fail, we
     obviously need to throw away any previous data. */
 
+    cancel_cutthrough_connection(TRUE, US"MAIL received");
     smtp_reset(reset_point);
     toomany = FALSE;
     sender_data = recipient_data = NULL;
@@ -4969,6 +5028,7 @@ while (done <= 0)
                                    (int)chunking_state, chunking_data_left);
 
       lwr_receive_getc = receive_getc;
+      lwr_receive_getbuf = receive_getbuf;
       lwr_receive_ungetc = receive_ungetc;
       receive_getc = bdat_getc;
       receive_ungetc = bdat_ungetc;
@@ -5160,6 +5220,7 @@ while (done <= 0)
     do an implied RSET when STARTTLS is received. */
 
     incomplete_transaction_log(US"STARTTLS");
+    cancel_cutthrough_connection(TRUE, US"STARTTLS received");
     smtp_reset(reset_point);
     toomany = FALSE;
     cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE;
@@ -5296,6 +5357,7 @@ while (done <= 0)
 
     case RSET_CMD:
     smtp_rset_handler();
+    cancel_cutthrough_connection(TRUE, US"RSET received");
     smtp_reset(reset_point);
     toomany = FALSE;
     break;