* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* A number of functions for driving outgoing SMTP calls. */
{
uschar *pstring = expand_string(rstring);
-if (pstring == NULL)
+if (!pstring)
{
addr->transport_return = PANIC;
addr->message = string_sprintf("failed to expand \"%s\" (\"port\" option) "
else
{
struct servent *smtp_service = getservbyname(CS pstring, "tcp");
- if (smtp_service == NULL)
+ if (!smtp_service)
{
addr->transport_return = PANIC;
addr->message = string_sprintf("TCP port \"%s\" is not defined for %s",
+#ifdef TCP_FASTOPEN
+static void
+tfo_out_check(int sock)
+{
+# 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)
+ {
+ switch (tcp_out_fastopen)
+ {
+ /* 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;
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;
(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. */
}
/* 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 */
return -1;
}
if (ob->keepalive) ip_keepalive(sock, host->address, TRUE);
+#ifdef TCP_FASTOPEN
+ if (fastopen_blob) tfo_out_check(sock);
+#endif
return sock;
}
}
+
+
+
+
+void
+smtp_port_for_connect(host_item * host, int port)
+{
+if (host->port != PORT_NONE)
+ {
+ HDEBUG(D_transport|D_acl|D_v)
+ debug_printf_indent("Transport port=%d replaced by host-specific port=%d\n", port,
+ host->port);
+ port = host->port;
+ }
+else host->port = port; /* Set the port actually used */
+}
+
+
/*************************************************
* Connect to remote host *
*************************************************/
non-IPv6 systems, to enable the code to be less messy. However, on such systems
host->address will always be an IPv4 address.
-The port field in the host item is used if it is set (usually router from SRV
-records or elsewhere). In other cases, the default passed as an argument is
-used, and the host item is updated with its value.
-
Arguments:
- host host item containing name and address (and sometimes port)
+ host host item containing name and address and port
host_af AF_INET or AF_INET6
- port default remote port to connect to, in host byte order, for those
- hosts whose port setting is PORT_NONE
interface outgoing interface address or NULL
timeout timeout value or 0
tb transport
*/
int
-smtp_connect(host_item *host, int host_af, int port, uschar *interface,
+smtp_connect(host_item *host, int host_af, uschar *interface,
int timeout, transport_instance * tb)
{
+int port = host->port;
#ifdef SUPPORT_SOCKS
smtp_transport_options_block * ob =
(smtp_transport_options_block *)tb->options_block;
#endif
-if (host->port != PORT_NONE)
- {
- HDEBUG(D_transport|D_acl|D_v)
- debug_printf_indent("Transport port=%d replaced by host-specific port=%d\n", port,
- host->port);
- port = host->port;
- }
-else host->port = port; /* Set the port actually used */
-
callout_address = string_sprintf("[%s]:%d", host->address, port);
HDEBUG(D_transport|D_acl|D_v)
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);
}
Argument:
outblock the SMTP output block
- mode more-expected, or plain
+ mode further data expected, or plain
Returns: TRUE if OK, FALSE on error, with errno set
*/
{
int rc;
int n = outblock->ptr - outblock->buffer;
+BOOL more = mode == SCMD_MORE;
HDEBUG(D_transport|D_acl) debug_printf_indent("cmd buf flush %d bytes%s\n", n,
- mode == SCMD_MORE ? " (with MORE annotation)" : "");
+ more ? " (more expected)" : "");
#ifdef SUPPORT_TLS
if (tls_out.active == outblock->sock)
- rc = tls_write(FALSE, outblock->buffer, n);
+ rc = tls_write(FALSE, outblock->buffer, n, more);
else
#endif
rc = send(outblock->sock, outblock->buffer, n,
#ifdef MSG_MORE
- mode == SCMD_MORE ? MSG_MORE : 0
+ more ? MSG_MORE : 0
#else
0
#endif