SECURITY: Leave a clean smtp_out input buffer even in case of read error
[exim.git] / src / src / smtp_out.c
index 86c3e412775cc53d3feed2f5112864257af69d13..f103c2752ebd76c23dbec4e704ea6be2e4525a78 100644 (file)
@@ -67,6 +67,7 @@ if (is_tainted(expint))
 Uskip_whitespace(&expint);
 if (!*expint) return TRUE;
 
+/* we just tested to ensure no taint, so big_buffer is ok */
 while ((iface = string_nextinlist(&expint, &sep, big_buffer,
           big_buffer_size)))
   {
@@ -158,20 +159,32 @@ tfo_out_check(int sock)
 {
 # ifdef __FreeBSD__
 struct tcp_info tinfo;
-int val;
-socklen_t len = sizeof(val);
+socklen_t len = sizeof(tinfo);
 
-/* The observability as of 12.1 is not useful as a client, only telling us that
-a TFO option was used on SYN.  It could have been a TFO-R, or ignored by the
-server. */
+/* A getsockopt TCP_FASTOPEN unfortunately returns "was-used" for a TFO/R as
+well as a TFO/C.  Use what we can of the Linux hack below; reliability issues ditto. */
+switch (tcp_out_fastopen)
+  {
+  case TFO_ATTEMPTED_NODATA:
+    if (  getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0
+       && tinfo.tcpi_state == TCPS_SYN_SENT
+       && tinfo.__tcpi_unacked > 0
+       )
+      {
+      DEBUG(D_transport|D_v)
+       debug_printf("TCP_FASTOPEN tcpi_unacked %d\n", tinfo.__tcpi_unacked);
+      tcp_out_fastopen = TFO_USED_NODATA;
+      }
+    break;
+  /*
+  case TFO_ATTEMPTED_DATA:
+  case TFO_ATTEMPTED_DATA:
+       if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA)   XXX no equvalent as of 12.2
+  */
+  }
 
-/*
-if (tcp_out_fastopen == TFO_ATTEMPTED_NODATA || tcp_out_fastopen == TFO_ATTEMPTED_DATA)
-  if (getsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN, &val, &len) == 0 && val != 0) {}
-*/
 switch (tcp_out_fastopen)
   {
-  case TFO_ATTEMPTED_NODATA:   tcp_out_fastopen = TFO_USED_NODATA; break;
   case TFO_ATTEMPTED_DATA:     tcp_out_fastopen = TFO_USED_DATA; break;
   default: break; /* compiler quietening */
   }
@@ -233,9 +246,18 @@ switch (tcp_out_fastopen)
 #endif
 
 
-/* Arguments as for smtp_connect(), plus
-  early_data   if non-NULL, idenmpotent data to be sent -
+/* Arguments:
+  host        host item containing name and address and port
+  host_af     AF_INET or AF_INET6
+  port       TCP port number
+  interface   outgoing interface address or NULL
+  tb          transport
+  timeout     timeout value or 0
+  early_data   if non-NULL, idempotent data to be sent -
                preferably in the TCP SYN segment
+             Special case: non-NULL but with NULL blob.data - caller is
+             client-data-first (eg. TLS-on-connect) and a lazy-TCP-connect is
+             acceptable.
 
 Returns:      connected socket number, or -1 with errno set
 */
@@ -305,23 +327,45 @@ early-data but no TFO support, send it after connecting. */
 else
   {
 #ifdef TCP_FASTOPEN
+  /* See if TCP Fast Open usable.  Default is a traditional 3WHS connect */
   if (verify_check_given_host(CUSS &ob->hosts_try_fastopen, host) == OK)
-    fastopen_blob = early_data ? early_data : &tcp_fastopen_nodata;
+    {
+    if (!early_data)
+      fastopen_blob = &tcp_fastopen_nodata;    /* TFO, with no data */
+    else if (early_data->data)
+      fastopen_blob = early_data;              /* TFO, with data */
+# ifdef TCP_FASTOPEN_CONNECT
+    else
+      {                                                /* expecting client data */
+      debug_printf(" set up lazy-connect\n");
+      setsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, US &on, sizeof(on));
+      /* fastopen_blob = NULL;          lazy TFO, triggered by data write */
+      }
+# endif
+    }
 #endif
 
   if (ip_connect(sock, host_af, host->address, port, timeout, fastopen_blob) < 0)
     save_errno = errno;
   else if (early_data && !fastopen_blob && early_data->data && early_data->len)
     {
+    /* We had some early-data to send, but couldn't do TFO */
     HDEBUG(D_transport|D_acl|D_v)
       debug_printf("sending %ld nonTFO early-data\n", (long)early_data->len);
 
-#ifdef TCP_QUICKACK
+#ifdef TCP_QUICKACK_notdef
     (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
 #endif
     if (send(sock, early_data->data, early_data->len, 0) < 0)
       save_errno = errno;
     }
+#ifdef TCP_QUICKACK_notdef
+  /* Under TFO (with openssl & pipe-conn; testcase 4069, as of
+  5.10.8-100.fc32.x86_64) this seems to be inop.
+  Perhaps overwritten when we (client) go -> ESTABLISHED on seeing the 3rd-ACK?
+  For that case, added at smtp_reap_banner(). */
+  (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+#endif
   }
 
 /* Either bind() or connect() failed */
@@ -375,7 +419,7 @@ smtp_port_for_connect(host_item * host, int port)
 {
 if (host->port != PORT_NONE)
   {
-  HDEBUG(D_transport|D_acl|D_v)
+  HDEBUG(D_transport|D_acl|D_v) if (port != host->port)
     debug_printf_indent("Transport port=%d replaced by host-specific port=%d\n", port,
       host->port);
   port = host->port;
@@ -396,6 +440,9 @@ host->address will always be an IPv4 address.
 Arguments:
   sc         details for making connection: host, af, interface, transport
   early_data  if non-NULL, data to be sent - preferably in the TCP SYN segment
+             Special case: non-NULL but with NULL blob.data - caller is
+             client-data-first (eg. TLS-on-connect) and a lazy-TCP-connect is
+             acceptable.
 
 Returns:      connected socket number, or -1 with errno set
 */
@@ -609,10 +656,12 @@ if (format)
       while (!isspace(*p)) p++;
       while (isspace(*p)) p++;
       }
-    while (*p != 0) *p++ = '*';
+    while (*p) *p++ = '*';
     }
 
-  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP>> %s\n", big_buffer);
+  HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("  SMTP%c> %s\n",
+    mode == SCMD_BUFFER ? '|' : mode == SCMD_MORE ? '+' : '>',
+    big_buffer);
   }
 
 if (mode != SCMD_BUFFER)
@@ -643,7 +692,7 @@ Arguments:
   timelimit deadline for reading the lime, seconds past epoch
 
 Returns:    length of a line that has been put in the buffer
-            -1 otherwise, with errno set
+            -1 otherwise, with errno set, and inblock->ptr adjusted
 */
 
 static int
@@ -684,6 +733,7 @@ for (;;)
       {
       *p = 0;                     /* Leave malformed line for error message */
       errno = ERRNO_SMTPFORMAT;
+      inblock->ptr = ptr;
       return -1;
       }
     }
@@ -709,6 +759,7 @@ for (;;)
 /* Get here if there has been some kind of recv() error; errno is set, but we
 ensure that the result buffer is empty before returning. */
 
+inblock->ptr = inblock->ptrend = inblock->buffer;
 *buffer = 0;
 return -1;
 }