Copyright year bumps for substantive changes 2017
[users/heiko/exim.git] / src / src / smtp_out.c
index d5bf262beead4813f5f782bb9373f68ba71ead81..2373e493cc4835bef83c82cd880d1b16175106dd 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. */
 
 /* A number of functions for driving outgoing SMTP calls. */
@@ -144,29 +144,65 @@ return TRUE;
 static void
 tfo_out_check(int sock)
 {
-# ifdef TCP_INFO
+# if defined(TCP_INFO) && defined(EXIM_HAVE_TCPI_UNACKED)
 struct tcp_info tinfo;
 socklen_t len = sizeof(tinfo);
 
 if (getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0)
   {
-  /* This is a somewhat dubious detection method; totally undocumented so likely
-  to fail in future kernels.  There seems to be no documented way. */
-
-  if (tinfo.tcpi_unacked > 1)
+  switch (tcp_out_fastopen)
     {
-    DEBUG(D_transport|D_v) debug_printf("TCP_FASTOPEN mode connection\n");
-    tcp_out_fastopen = TRUE;
+      /* This is a somewhat dubious detection method; totally undocumented so likely
+      to fail in future kernels.  There seems to be no documented way.  What we really
+      want to know is if the server sent smtp-banner data before our ACK of his SYN,ACK
+      hit him.  What this (possibly?) detects is whether we sent a TFO cookie with our
+      SYN, as distinct from a TFO request.  This gets a false-positive when the server
+      key is rotated; we send the old one (which this test sees) but the server returns
+      the new one and does not send its SMTP banner before we ACK his SYN,ACK.
+       To force that rotation case:
+       '# echo -n "00000000-00000000-00000000-0000000" >/proc/sys/net/ipv4/tcp_fastopen_key'
+      The kernel seems to be counting unack'd packets. */
+
+    case 1:
+      if (tinfo.tcpi_unacked > 1)
+       {
+       DEBUG(D_transport|D_v)
+         debug_printf("TCP_FASTOPEN tcpi_unacked %d\n", tinfo.tcpi_unacked);
+       tcp_out_fastopen = 2;
+       }
+      break;
+
+#ifdef notdef          /* This seems to always fire, meaning that we cannot tell
+                       whether the server accepted data we sent.  For now assume
+                       that it did. */
+
+      /* If there was data-on-SYN but we had to retrasnmit it, declare no TFO */
+
+    case 2:
+      if (!(tinfo.tcpi_options & TCPI_OPT_SYN_DATA))
+       {
+       DEBUG(D_transport|D_v) debug_printf("TFO: had to retransmit\n");
+       tcp_out_fastopen = 0;
+       }
+      break;
+#endif
     }
+
   }
 # endif
 }
 #endif
 
 
+/* Arguments as for smtp_connect(), plus
+  early_data   if non-NULL, data to be sent - preferably in the TCP SYN segment
+
+Returns:      connected socket number, or -1 with errno set
+*/
+
 int
 smtp_sock_connect(host_item * host, int host_af, int port, uschar * interface,
-  transport_instance * tb, int timeout)
+  transport_instance * tb, int timeout, const blob * early_data)
 {
 smtp_transport_options_block * ob =
   (smtp_transport_options_block *)tb->options_block;
@@ -176,7 +212,8 @@ int dscp_level;
 int dscp_option;
 int sock;
 int save_errno = 0;
-BOOL fastopen = FALSE;
+const blob * fastopen_blob = NULL;
+
 
 #ifndef DISABLE_EVENT
 deliver_host_address = host->address;
@@ -209,10 +246,6 @@ if (dscp && dscp_lookup(dscp, host_af, &dscp_level, &dscp_option, &dscp_value))
     (void) setsockopt(sock, dscp_level, dscp_option, &dscp_value, sizeof(dscp_value));
   }
 
-#ifdef TCP_FASTOPEN
-if (verify_check_given_host (&ob->hosts_try_fastopen, host) == OK) fastopen = TRUE;
-#endif
-
 /* Bind to a specific interface if requested. Caller must ensure the interface
 is the same type (IPv4 or IPv6) as the outgoing address. */
 
@@ -225,10 +258,22 @@ if (interface && ip_bind(sock, host_af, interface, 0) < 0)
   }
 
 /* Connect to the remote host, and add keepalive to the socket before returning
-it, if requested. */
+it, if requested.  If the build supports TFO, request it - and if the caller
+requested some early-data then include that in the TFO request. */
 
-else if (ip_connect(sock, host_af, host->address, port, timeout, fastopen) < 0)
-  save_errno = errno;
+else
+  {
+#ifdef TCP_FASTOPEN
+  if (verify_check_given_host(&ob->hosts_try_fastopen, host) == OK)
+    fastopen_blob = early_data ? early_data : &tcp_fastopen_nodata;
+#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)
+    if (send(sock, early_data->data, early_data->len, 0) < 0)
+      save_errno = errno;
+  }
 
 /* Either bind() or connect() failed */
 
@@ -264,7 +309,7 @@ else
     }
   if (ob->keepalive) ip_keepalive(sock, host->address, TRUE);
 #ifdef TCP_FASTOPEN
-  tfo_out_check(sock);
+  if (fastopen_blob) tfo_out_check(sock);
 #endif
   return sock;
   }
@@ -336,7 +381,7 @@ if (ob->socks_proxy)
   return socks_sock_connect(host, host_af, port, interface, tb, timeout);
 #endif
 
-return smtp_sock_connect(host, host_af, port, interface, tb, timeout);
+return smtp_sock_connect(host, host_af, port, interface, tb, timeout, NULL);
 }