Chunking: do not treat the first lonely dot special. CVE-2017-16944, Bug 2201
[exim.git] / src / src / smtp_in.c
index 91b1c7e6dd96b6114dac0c55a5cca9fee1ce0352..02075404dc8eb05ab25fdafd9d1ff0280bd07279 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for handling an incoming SMTP call. */
@@ -304,7 +304,6 @@ static int     smtp_had_error;
 
 
 /* forward declarations */
-int bdat_ungetc(int ch);
 static int smtp_read_command(BOOL check_sync, unsigned buffer_lim);
 static int synprot_error(int type, int code, uschar *data, uschar *errmess);
 static void smtp_quit_handler(uschar **, uschar **);
@@ -486,12 +485,17 @@ uschar * log_msg;
 
 for(;;)
   {
+#ifndef DISABLE_DKIM
+  BOOL dkim_save;
+#endif
+
   if (chunking_data_left > 0)
     return lwr_receive_getc(chunking_data_left--);
 
   receive_getc = lwr_receive_getc;
   receive_ungetc = lwr_receive_ungetc;
 #ifndef DISABLE_DKIM
+  dkim_save = dkim_collect_input;
   dkim_collect_input = FALSE;
 #endif
 
@@ -592,7 +596,7 @@ next_cmd:
       receive_getc = bdat_getc;
       receive_ungetc = bdat_ungetc;
 #ifndef DISABLE_DKIM
-      dkim_collect_input = TRUE;
+      dkim_collect_input = dkim_save;
 #endif
       break;   /* to top of main loop */
       }
@@ -921,6 +925,69 @@ return proxy_session;
 }
 
 
+/*************************************************
+*    Read data until newline or end of buffer    *
+*************************************************/
+/* While SMTP is server-speaks-first, TLS is client-speaks-first, so we can't
+read an entire buffer and assume there will be nothing past a proxy protocol
+header.  Our approach normally is to use stdio, but again that relies upon
+"STARTTLS\r\n" and a server response before the client starts TLS handshake, or
+reading _nothing_ before client TLS handshake.  So we don't want to use the
+usual buffering reads which may read enough to block TLS starting.
+
+So unfortunately we're down to "read one byte at a time, with a syscall each,
+and expect a little overhead", for all proxy-opened connections which are v1,
+just to handle the TLS-on-connect case.  Since SSL functions wrap the
+underlying fd, we can't assume that we can feed them any already-read content.
+
+We need to know where to read to, the max capacity, and we'll read until we
+get a CR and one more character.  Let the caller scream if it's CR+!LF.
+
+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;
+    }
+
+  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;
+}
+
 /*************************************************
 *         Setup host for proxy protocol          *
 *************************************************/
@@ -973,6 +1040,36 @@ struct sockaddr_in tmpaddr;
 char tmpip6[INET6_ADDRSTRLEN];
 struct sockaddr_in6 tmpaddr6;
 
+/* We can't read "all data until end" because while SMTP is
+server-speaks-first, the TLS handshake is client-speaks-first, so for
+TLS-on-connect ports the proxy protocol header will usually be immediately
+followed by a TLS handshake, and with N TLS libraries, we can't reliably
+reinject data for reading by those.  So instead we first read "enough to be
+safely read within the header, and figure out how much more to read".
+For v1 we will later read to the end-of-line, for v2 we will read based upon
+the stated length.
+
+The v2 sig is 12 octets, and another 4 gets us the length, so we know how much
+data is needed total.  For v1, where the line looks like:
+PROXY TCPn L3src L3dest SrcPort DestPort \r\n
+
+However, for v1 there's also `PROXY UNKNOWN\r\n` which is only 15 octets.
+We seem to support that.  So, if we read 14 octets then we can tell if we're
+v2 or v1.  If we're v1, we can continue reading as normal.
+
+If we're v2, we can't slurp up the entire header.  We need the length in the
+15th & 16th octets, then to read everything after that.
+
+So to safely handle v1 and v2, with client-sent-first supported correctly,
+we have to do a minimum of 3 read calls, not 1.  Eww.
+*/
+
+#define PROXY_INITIAL_READ 14
+#define PROXY_V2_HEADER_SIZE 16
+#if PROXY_INITIAL_READ > PROXY_V2_HEADER_SIZE
+# error Code bug in sizes of data to read for proxy usage
+#endif
+
 int get_ok = 0;
 int size, ret;
 int fd = fileno(smtp_in);
@@ -996,18 +1093,32 @@ 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 it up. */
-  ret = recv(fd, &hdr, sizeof(hdr), 0);
+     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);
 
 if (ret == -1)
   goto proxyfail;
 
-if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
+/* For v2, handle reading the length, and then the rest. */
+if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
   {
-  uint8_t ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
-  uint8_t cmd = (hdr.v2.ver_cmd & 0x0f);
+  int retmore;
+  uint8_t ver;
+
+  /* First get the length fields. */
+  do
+    {
+    retmore = recv(fd, (uschar*)&hdr + ret, PROXY_V2_HEADER_SIZE - PROXY_INITIAL_READ, 0);
+    } while (retmore == -1 && errno == EINTR);
+  if (retmore == -1)
+    goto proxyfail;
+  ret += retmore;
+
+  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
@@ -1019,15 +1130,43 @@ if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
     DEBUG(D_receive) debug_printf("Invalid Proxy Protocol version: %d\n", ver);
     goto proxyfail;
     }
-  DEBUG(D_receive) debug_printf("Detected PROXYv2 header\n");
+
   /* The v2 header will always be 16 bytes per the spec. */
   size = 16 + ntohs(hdr.v2.len);
-  if (ret < size)
+  DEBUG(D_receive) debug_printf("Detected PROXYv2 header, size %d (limit %d)\n",
+      size, (int)sizeof(hdr));
+
+  /* We should now have 16 octets (PROXY_V2_HEADER_SIZE), and we know the total
+  amount that we need.  Double-check that the size is not unreasonable, then
+  get the rest. */
+  if (size > sizeof(hdr))
     {
-    DEBUG(D_receive) debug_printf("Truncated or too large PROXYv2 header (%d/%d)\n",
-                                  ret, size);
+    DEBUG(D_receive) debug_printf("PROXYv2 header size unreasonably large; security attack?\n");
     goto proxyfail;
     }
+
+  do
+    {
+    do
+      {
+      retmore = recv(fd, (uschar*)&hdr + ret, size-ret, 0);
+      } while (retmore == -1 && errno == EINTR);
+    if (retmore == -1)
+      goto proxyfail;
+    ret += retmore;
+    DEBUG(D_receive) debug_printf("PROXYv2: have %d/%d required octets\n", ret, size);
+    } while (ret < size);
+
+  } /* end scope for getting rest of data for v2 */
+
+/* At this point: if PROXYv2, we've read the exact size required for all data;
+if PROXYv1 then we've read "less than required for any valid line" and should
+read the rest". */
+
+if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
+  {
+  uint8_t cmd = (hdr.v2.ver_cmd & 0x0f);
+
   switch (cmd)
     {
     case 0x01: /* PROXY command */
@@ -1105,20 +1244,31 @@ if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
   }
 else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0)
   {
-  uschar *p = string_copy(hdr.v1.line);
-  uschar *end = memchr(p, '\r', ret - 1);
+  uschar *p;
+  uschar *end;
   uschar *sp;     /* Utility variables follow */
   int     tmp_port;
+  int     r2;
   char   *endc;
 
-  if (!end || end[1] != '\n')
+  /* get the rest of the line */
+  r2 = swallow_until_crlf(fd, (uschar*)&hdr, ret, sizeof(hdr)-ret);
+  if (r2 == -1)
+    goto proxyfail;
+  ret += r2;
+
+  p = string_copy(hdr.v1.line);
+  end = memchr(p, '\r', ret - 1);
+
+  if (!end || (end == (uschar*)&hdr + ret) || end[1] != '\n')
     {
     DEBUG(D_receive) debug_printf("Partial or invalid PROXY header\n");
     goto proxyfail;
     }
   *end = '\0'; /* Terminate the string */
-  size = end + 2 - hdr.v1.line; /* Skip header + CRLF */
+  size = end + 2 - p; /* Skip header + CRLF */
   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. */
   p += 5;
@@ -1215,6 +1365,7 @@ else
   {
   /* Wrong protocol */
   DEBUG(D_receive) debug_printf("Invalid proxy protocol version negotiation\n");
+  (void) swallow_until_crlf(fd, (uschar*)&hdr, ret, sizeof(hdr)-ret);
   goto proxyfail;
   }
 
@@ -1731,7 +1882,6 @@ Returns:    nothing
 static void
 smtp_reset(void *reset_point)
 {
-store_reset(reset_point);
 recipients_list = NULL;
 rcpt_count = rcpt_defer_count = rcpt_fail_count =
   raw_recipients_count = recipients_count = recipients_list_max = 0;
@@ -1754,7 +1904,12 @@ submission_mode = FALSE;                             /* Can be set by ACL */
 suppress_local_fixups = suppress_local_fixups_default; /* Can be set by ACL */
 active_local_from_check = local_from_check;          /* Can be set by ACL */
 active_local_sender_retain = local_sender_retain;    /* Can be set by ACL */
-sender_address = NULL;
+sending_ip_address = NULL;
+return_path = sender_address = NULL;
+sender_data = NULL;                                 /* Can be set by ACL */
+deliver_localpart_orig = NULL;
+deliver_domain_orig = NULL;
+callout_address = NULL;
 submission_name = NULL;                              /* Can be set by ACL */
 raw_sender = NULL;                  /* After SMTP rewrite, before qualifying */
 sender_address_unrewritten = NULL;  /* Set only after verify rewrite */
@@ -1767,6 +1922,7 @@ authenticated_sender = NULL;
 bmi_run = 0;
 bmi_verdicts = NULL;
 #endif
+dnslist_domain = dnslist_matched = NULL;
 #ifndef DISABLE_DKIM
 dkim_signers = NULL;
 dkim_disable_verify = FALSE;
@@ -1774,6 +1930,7 @@ dkim_collect_input = FALSE;
 #endif
 dsn_ret = 0;
 dsn_envid = NULL;
+deliver_host = deliver_host_address = NULL;    /* Can be set by ACL */
 #ifndef DISABLE_PRDR
 prdr_requested = FALSE;
 #endif
@@ -1800,13 +1957,13 @@ acl_var_m = NULL;
 not the first message in an SMTP session and the previous message caused them
 to be referenced in an ACL. */
 
-if (message_body != NULL)
+if (message_body)
   {
   store_free(message_body);
   message_body = NULL;
   }
 
-if (message_body_end != NULL)
+if (message_body_end)
   {
   store_free(message_body_end);
   message_body_end = NULL;
@@ -1816,12 +1973,13 @@ if (message_body_end != NULL)
 repetition in the same message, but it seems right to repeat them for different
 messages. */
 
-while (acl_warn_logged != NULL)
+while (acl_warn_logged)
   {
   string_item *this = acl_warn_logged;
   acl_warn_logged = acl_warn_logged->next;
   store_free(this);
   }
+store_reset(reset_point);
 }
 
 
@@ -4322,9 +4480,13 @@ while (done <= 0)
         case ENV_MAIL_OPT_UTF8:
          if (smtputf8_advertised)
            {
+           int old_pool = store_pool;
+
            DEBUG(D_receive) debug_printf("smtputf8 requested\n");
            message_smtputf8 = allow_utf8_domains = TRUE;
+           store_pool = POOL_PERM;
            received_protocol = string_sprintf("utf8%s", received_protocol);
+           store_pool = old_pool;
            }
          break;
 #endif
@@ -4793,16 +4955,23 @@ while (done <= 0)
       DEBUG(D_receive) debug_printf("chunking state %d, %d bytes\n",
                                    (int)chunking_state, chunking_data_left);
 
+      /* push the current receive_* function on the "stack", and
+      replace them by bdat_getc(), which in turn will use the lwr_receive_*
+      functions to do the dirty work. */
       lwr_receive_getc = receive_getc;
       lwr_receive_ungetc = receive_ungetc;
+
       receive_getc = bdat_getc;
       receive_ungetc = bdat_ungetc;
 
+      dot_ends = FALSE;
+
       goto DATA_BDAT;
       }
 
     case DATA_CMD:
     HAD(SCH_DATA);
+    dot_ends = TRUE;
 
     DATA_BDAT:         /* Common code for DATA and BDAT */
     if (!discarded && recipients_count <= 0)