* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for doing things with sockets. With the advent of IPv6 this has
got messier, so that it's worth pulling out the code into separate functions
-that other parts of Exim can call, expecially as there are now several
+that other parts of Exim can call, especially as there are now several
different places in the code where sockets are used. */
* Bind socket to interface and port *
*************************************************/
-/* This function binds a socket to a local interface address and port. For a
-wildcard IPv6 bind, the address is ":".
-
-Arguments:
- sock the socket
- af AF_INET or AF_INET6 - the socket type
- address the IP address, in text form
- port the IP port (host order)
-
-Returns: the result of bind()
-*/
-
int
-ip_bind(int sock, int af, uschar *address, int port)
+ip_addr(void * sin_, int af, const uschar * address, int port)
{
-int s_len;
-union sockaddr_46 sin;
-memset(&sin, 0, sizeof(sin));
+union sockaddr_46 * sin = sin_;
+memset(sin, 0, sizeof(*sin));
/* Setup code when using an IPv6 socket. The wildcard address is ":", to
ensure an IPv6 socket is used. */
{
if (address[0] == ':' && address[1] == 0)
{
- sin.v6.sin6_family = AF_INET6;
- sin.v6.sin6_addr = in6addr_any;
+ sin->v6.sin6_family = AF_INET6;
+ sin->v6.sin6_addr = in6addr_any;
}
else
- {
- ip_addrinfo(address, &sin.v6); /* Panic-dies on error */
- }
- sin.v6.sin6_port = htons(port);
- s_len = sizeof(sin.v6);
+ ip_addrinfo(address, &sin->v6); /* Panic-dies on error */
+ sin->v6.sin6_port = htons(port);
+ return sizeof(sin->v6);
}
else
#else /* HAVE_IPv6 */
/* Setup code when using IPv4 socket. The wildcard address is "". */
{
- sin.v4.sin_family = AF_INET;
- sin.v4.sin_port = htons(port);
- s_len = sizeof(sin.v4);
- if (address[0] == 0)
- sin.v4.sin_addr.s_addr = (S_ADDR_TYPE)INADDR_ANY;
- else
- sin.v4.sin_addr.s_addr = (S_ADDR_TYPE)inet_addr(CS address);
+ sin->v4.sin_family = AF_INET;
+ sin->v4.sin_port = htons(port);
+ sin->v4.sin_addr.s_addr = address[0] == 0
+ ? (S_ADDR_TYPE)INADDR_ANY
+ : (S_ADDR_TYPE)inet_addr(CS address);
+ return sizeof(sin->v4);
}
+}
-/* Now we can call the bind() function */
+
+/* This function binds a socket to a local interface address and port. For a
+wildcard IPv6 bind, the address is ":".
+
+Arguments:
+ sock the socket
+ af AF_INET or AF_INET6 - the socket type
+ address the IP address, in text form
+ port the IP port (host order)
+
+Returns: the result of bind()
+*/
+
+int
+ip_bind(int sock, int af, uschar *address, int port)
+{
+union sockaddr_46 sin;
+int s_len = ip_addr(&sin, af, address, port);
return bind(sock, (struct sockaddr *)&sin, s_len);
}
+/*************************************************
+*************************************************/
+
+#ifdef EXIM_TFO_PROBE
+void
+tfo_probe(void)
+{
+# ifdef TCP_FASTOPEN
+int sock, backlog = 5;
+
+if ( (sock = socket(SOCK_STREAM, AF_INET, 0)) < 0
+ && setsockopt(sock, IPPROTO_TCP, TCP_FASTOPEN, &backlog, sizeof(backlog))
+ )
+ f.tcp_fastopen_ok = TRUE;
+close(sock);
+# endif
+}
+#endif
+
+
/*************************************************
* Connect socket to remote host *
*************************************************/
address the remote address, in text form
port the remote port
timeout a timeout (zero for indefinite timeout)
+ fastopen_blob non-null iff TCP_FASTOPEN can be used; may indicate early-data to
+ be sent in SYN segment
Returns: 0 on success; -1 on failure, with errno set
*/
int
-ip_connect(int sock, int af, const uschar *address, int port, int timeout)
+ip_connect(int sock, int af, const uschar *address, int port, int timeout,
+ const blob * fastopen_blob)
{
struct sockaddr_in s_in4;
struct sockaddr *s_ptr;
/* If no connection timeout is set, just call connect() without setting a
timer, thereby allowing the inbuilt OS timeout to operate. */
+callout_address = string_sprintf("[%s]:%d", address, port);
sigalrm_seen = FALSE;
if (timeout > 0) alarm(timeout);
-rc = connect(sock, s_ptr, s_len);
+
+#if defined(TCP_FASTOPEN) && defined(MSG_FASTOPEN)
+/* TCP Fast Open, if the system has a cookie from a previous call to
+this peer, can send data in the SYN packet. The peer can send data
+before it gets our ACK of its SYN,ACK - the latter is useful for
+the SMTP banner. Other (than SMTP) cases of TCP connections can
+possibly use the data-on-syn, so support that too.
+
+This is a Linux implementation. It might be useable on FreeBSD; I have
+not checked. I think MacOS has a "connectx" call for this purpose,
+rather than using "sendto" ? */
+
+if (fastopen_blob && f.tcp_fastopen_ok)
+ {
+ if ((rc = sendto(sock, fastopen_blob->data, fastopen_blob->len,
+ MSG_FASTOPEN | MSG_DONTWAIT, s_ptr, s_len)) >= 0)
+ /* seen for with-data, experimental TFO option, with-cookie case */
+ /* seen for with-data, proper TFO opt, with-cookie case */
+ {
+ DEBUG(D_transport|D_v)
+ debug_printf("non-TFO mode connection attempt to %s, %lu data\n",
+ address, (unsigned long)fastopen_blob->len);
+ /*XXX also seen on successful TFO, sigh */
+ tcp_out_fastopen = fastopen_blob->len > 0 ? 2 : 1;
+ }
+ else if (errno == EINPROGRESS) /* expected if we had no cookie for peer */
+ /* seen for no-data, proper TFO option, both cookie-request and with-cookie cases */
+ /* apparently no visibility of the diffference at this point */
+ /* seen for with-data, proper TFO opt, cookie-req */
+ /* with netwk delay, post-conn tcp_info sees unacked 1 for R, 2 for C; code in smtp_out.c */
+ /* ? older Experimental TFO option behaviour ? */
+ { /* queue unsent data */
+ DEBUG(D_transport|D_v) debug_printf("TFO mode sendto, %s data: EINPROGRESS\n",
+ fastopen_blob->len > 0 ? "with" : "no");
+ if (!fastopen_blob->data)
+ {
+ tcp_out_fastopen = 1; /* we tried; unknown if useful yet */
+ rc = 0;
+ }
+ else
+ rc = send(sock, fastopen_blob->data, fastopen_blob->len, 0);
+ }
+ else if(errno == EOPNOTSUPP)
+ {
+ DEBUG(D_transport)
+ debug_printf("Tried TCP Fast Open but apparently not enabled by sysctl\n");
+ goto legacy_connect;
+ }
+ }
+else
+#endif
+ {
+legacy_connect:
+ DEBUG(D_transport|D_v) if (fastopen_blob)
+ debug_printf("non-TFO mode connection attempt to %s, %lu data\n",
+ address, (unsigned long)fastopen_blob->len);
+ if ((rc = connect(sock, s_ptr, s_len)) >= 0)
+ if ( fastopen_blob && fastopen_blob->data && fastopen_blob->len
+ && send(sock, fastopen_blob->data, fastopen_blob->len, 0) < 0)
+ rc = -1;
+ }
+
save_errno = errno;
alarm(0);
can't think of any other way of doing this. It converts a connection refused
into a timeout if the timeout is set to 999999. */
-if (running_in_test_harness)
+if (f.running_in_test_harness && save_errno == ECONNREFUSED && timeout == 999999)
{
- if (save_errno == ECONNREFUSED && timeout == 999999)
- {
- rc = -1;
- save_errno = EINTR;
- sigalrm_seen = TRUE;
- }
+ rc = -1;
+ save_errno = EINTR;
+ sigalrm_seen = TRUE;
}
/* Success */
-if (rc >= 0) return 0;
+if (rc >= 0)
+ return 0;
/* A failure whose error code is "Interrupted system call" is in fact
an externally applied timeout if the signal handler has been run. */
-errno = (save_errno == EINTR && sigalrm_seen)? ETIMEDOUT : save_errno;
+errno = save_errno == EINTR && sigalrm_seen ? ETIMEDOUT : save_errno;
return -1;
}
Arguments:
type SOCK_DGRAM or SOCK_STREAM
af AF_INET6 or AF_INET for the socket type
- address the remote address, in text form
+ hostname host name, or ip address (as text)
portlo,porthi the remote port range
timeout a timeout
- connhost if not NULL, host_item filled in with connection details
+ connhost if not NULL, host_item to be filled in with connection details
errstr pointer for allocated string on error
+ fastopen_blob with SOCK_STREAM, if non-null, request TCP Fast Open.
+ Additionally, optional early-data to send
Return:
socket fd, or -1 on failure (having allocated an error string)
*/
int
ip_connectedsocket(int type, const uschar * hostname, int portlo, int porthi,
- int timeout, host_item * connhost, uschar ** errstr)
+ int timeout, host_item * connhost, uschar ** errstr, const blob * fastopen_blob)
{
int namelen, port;
host_item shost;
if (hostname[0] == '[' &&
hostname[namelen - 1] == ']')
{
- uschar * host = string_copy(hostname);
- host[namelen - 1] = 0;
- host++;
+ uschar * host = string_copyn(hostname+1, namelen-2);
if (string_is_ip_address(host, NULL) == 0)
{
*errstr = string_sprintf("malformed IP address \"%s\"", hostname);
/* Otherwise check for an unadorned IP address */
else if (string_is_ip_address(hostname, NULL) != 0)
- shost.name = shost.address = string_copy(hostname);
+ shost.name = shost.address = string_copyn(hostname, namelen);
/* Otherwise lookup IP address(es) from the name */
else
{
- shost.name = string_copy(hostname);
- if (host_find_byname(&shost, NULL, HOST_FIND_QUALIFY_SINGLE, NULL,
- FALSE) != HOST_FOUND)
+ shost.name = string_copyn(hostname, namelen);
+ if (host_find_byname(&shost, NULL, HOST_FIND_QUALIFY_SINGLE,
+ NULL, FALSE) != HOST_FOUND)
{
*errstr = string_sprintf("no IP address found for host %s", shost.name);
return -1;
/* Try to connect to the server - test each IP till one works */
-for (h = &shost; h != NULL; h = h->next)
+for (h = &shost; h; h = h->next)
{
- fd = (Ustrchr(h->address, ':') != 0)
- ? (fd6 < 0) ? (fd6 = ip_socket(type, af = AF_INET6)) : fd6
- : (fd4 < 0) ? (fd4 = ip_socket(type, af = AF_INET )) : fd4;
+ fd = Ustrchr(h->address, ':') != 0
+ ? fd6 < 0 ? (fd6 = ip_socket(type, af = AF_INET6)) : fd6
+ : fd4 < 0 ? (fd4 = ip_socket(type, af = AF_INET )) : fd4;
if (fd < 0)
{
}
for(port = portlo; port <= porthi; port++)
- if (ip_connect(fd, af, h->address, port, timeout) == 0)
+ if (ip_connect(fd, af, h->address, port, timeout, fastopen_blob) == 0)
{
if (fd != fd6) close(fd6);
if (fd != fd4) close(fd4);
- if (connhost) {
+ if (connhost)
+ {
h->port = port;
*connhost = *h;
connhost->next = NULL;
}
+/*XXX TFO? */
+int
+ip_tcpsocket(const uschar * hostport, uschar ** errstr, int tmo)
+{
+int scan;
+uschar hostname[256];
+unsigned int portlow, porthigh;
+
+/* extract host and port part */
+scan = sscanf(CS hostport, "%255s %u-%u", hostname, &portlow, &porthigh);
+if (scan != 3)
+ {
+ if (scan != 2)
+ {
+ *errstr = string_sprintf("invalid socket '%s'", hostport);
+ return -1;
+ }
+ porthigh = portlow;
+ }
+
+return ip_connectedsocket(SOCK_STREAM, hostname, portlow, porthigh,
+ tmo, NULL, errstr, NULL);
+}
+
+int
+ip_unixsocket(const uschar * path, uschar ** errstr)
+{
+int sock;
+struct sockaddr_un server;
+
+if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ {
+ *errstr = US"can't open UNIX socket.";
+ return -1;
+ }
+
+callout_address = string_copy(path);
+server.sun_family = AF_UNIX;
+Ustrncpy(server.sun_path, path, sizeof(server.sun_path)-1);
+server.sun_path[sizeof(server.sun_path)-1] = '\0';
+if (connect(sock, (struct sockaddr *) &server, sizeof(server)) < 0)
+ {
+ int err = errno;
+ (void)close(sock);
+ *errstr = string_sprintf("unable to connect to UNIX socket (%s): %s",
+ path, strerror(err));
+ return -1;
+ }
+return sock;
+}
+
+int
+ip_streamsocket(const uschar * spec, uschar ** errstr, int tmo)
+{
+return *spec == '/'
+ ? ip_unixsocket(spec, errstr) : ip_tcpsocket(spec, errstr, tmo);
+}
+
/*************************************************
* Set keepalive on a socket *
*************************************************/
*/
void
-ip_keepalive(int sock, uschar *address, BOOL torf)
+ip_keepalive(int sock, const uschar *address, BOOL torf)
{
int fodder = 1;
if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE,
- (uschar *)(&fodder), sizeof(fodder)) != 0)
+ US (&fodder), sizeof(fodder)) != 0)
log_write(0, LOG_MAIN, "setsockopt(SO_KEEPALIVE) on connection %s %s "
"failed: %s", torf? "to":"from", address, strerror(errno));
}
* Receive from a socket with timeout *
*************************************************/
-/* The timeout is implemented using select(), and we loop to cover select()
-getting interrupted, and the possibility of select() returning with a positive
-result but no ready descriptor. Is this in fact possible?
-
+/*
Arguments:
- sock the socket
- buffer to read into
- bufsize the buffer size
- timeout the timeout
-
-Returns: > 0 => that much data read
- <= 0 on error or EOF; errno set - zero for EOF
+ fd the file descriptor
+ timeout the timeout, seconds
+Returns: TRUE => ready for i/o
+ FALSE => timed out, or other error
*/
-
-int
-ip_recv(int sock, uschar *buffer, int buffsize, int timeout)
+BOOL
+fd_ready(int fd, int timeout)
{
fd_set select_inset;
-struct timeval tv;
time_t start_recv = time(NULL);
+int time_left = timeout;
int rc;
+if (time_left <= 0)
+ {
+ errno = ETIMEDOUT;
+ return FALSE;
+ }
/* Wait until the socket is ready */
-for (;;)
+do
{
+ struct timeval tv = { .tv_sec = time_left, .tv_usec = 0 };
FD_ZERO (&select_inset);
- FD_SET (sock, &select_inset);
- tv.tv_sec = timeout;
- tv.tv_usec = 0;
+ FD_SET (fd, &select_inset);
- DEBUG(D_transport) debug_printf("waiting for data on socket\n");
- rc = select(sock + 1, (SELECT_ARG2_TYPE *)&select_inset, NULL, NULL, &tv);
+ /*DEBUG(D_transport) debug_printf("waiting for data on fd\n");*/
+ rc = select(fd + 1, (SELECT_ARG2_TYPE *)&select_inset, NULL, NULL, &tv);
/* If some interrupt arrived, just retry. We presume this to be rare,
but it can happen (e.g. the SIGUSR1 signal sent by exiwhat causes
Aug 2004: Somebody set up a cron job that ran exiwhat every 2 minutes, making
the interrupt not at all rare. Since the timeout is typically more than 2
minutes, the effect was to block the timeout completely. To prevent this
- happening again, we do an explicit time test. */
+ happening again, we do an explicit time test and adjust the timeout
+ accordingly */
if (rc < 0 && errno == EINTR)
{
DEBUG(D_transport) debug_printf("EINTR while waiting for socket data\n");
- if (time(NULL) - start_recv < timeout) continue;
- DEBUG(D_transport) debug_printf("total wait time exceeds timeout\n");
- }
- /* Handle a timeout, and treat any other select error as a timeout, including
- an EINTR when we have been in this loop for longer than timeout. */
+ /* Watch out, 'continue' jumps to the condition, not to the loops top */
+ time_left = timeout - (time(NULL) - start_recv);
+ if (time_left > 0) continue;
+ }
if (rc <= 0)
{
errno = ETIMEDOUT;
- return -1;
+ return FALSE;
}
- /* If the socket is ready, break out of the loop. */
-
- if (FD_ISSET(sock, &select_inset)) break;
+ /* Checking the FD_ISSET is not enough, if we're interrupted, the
+ select_inset may still contain the 'input'. */
}
+while (rc < 0 || !FD_ISSET(fd, &select_inset));
+return TRUE;
+}
+
+/* The timeout is implemented using select(), and we loop to cover select()
+getting interrupted, and the possibility of select() returning with a positive
+result but no ready descriptor. Is this in fact possible?
+
+Arguments:
+ cctx the connection context (socket fd, possibly TLS context)
+ buffer to read into
+ bufsize the buffer size
+ timeout the timeout
+
+Returns: > 0 => that much data read
+ <= 0 on error or EOF; errno set - zero for EOF
+*/
+
+int
+ip_recv(client_conn_ctx * cctx, uschar * buffer, int buffsize, int timeout)
+{
+int rc;
+
+if (!fd_ready(cctx->sock, timeout))
+ return -1;
/* The socket is ready, read from it (via TLS if it's active). On EOF (i.e.
close down of the connection), set errno to zero; otherwise leave it alone. */
#ifdef SUPPORT_TLS
-if (tls_out.active == sock)
- rc = tls_read(FALSE, buffer, buffsize);
-else if (tls_in.active == sock)
- rc = tls_read(TRUE, buffer, buffsize);
+if (cctx->tls_ctx) /* client TLS */
+ rc = tls_read(cctx->tls_ctx, buffer, buffsize);
+else if (tls_in.active.sock == cctx->sock) /* server TLS */
+ rc = tls_read(NULL, buffer, buffsize);
else
#endif
- rc = recv(sock, buffer, buffsize, 0);
+ rc = recv(cctx->sock, buffer, buffsize, 0);
if (rc > 0) return rc;
if (rc == 0) errno = 0;
return TRUE;
}
else if (c > 0)
- {
first = middle + 1;
- }
else
- {
last = middle;
- }
}
return FALSE;
}
/* End of ip.c */
+/* vi: aw ai sw=2
+*/