X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/bb7b9411e1b4f95418bed7b35035186e261063a6..0e42da1b15894b66e3726d8ce0e41e25bdc21149:/src/src/ip.c diff --git a/src/src/ip.c b/src/src/ip.c index d6e4e7ad8..91b74e20e 100644 --- a/src/src/ip.c +++ b/src/src/ip.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2013 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions for doing things with sockets. With the advent of IPv6 this has @@ -64,11 +64,11 @@ Returns: nothing - failure provokes a panic-die */ static void -ip_addrinfo(uschar *address, struct sockaddr_in6 *saddr) +ip_addrinfo(const uschar *address, struct sockaddr_in6 *saddr) { #ifdef IPV6_USE_INET_PTON - if (inet_pton(AF_INET6, CS address, &saddr->sin6_addr) != 1) + if (inet_pton(AF_INET6, CCS address, &saddr->sin6_addr) != 1) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "unable to parse \"%s\" as an " "IP address", address); saddr->sin6_family = AF_INET6; @@ -81,7 +81,7 @@ ip_addrinfo(uschar *address, struct sockaddr_in6 *saddr) hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICHOST; - if ((rc = getaddrinfo(CS address, NULL, &hints, &res)) != 0 || res == NULL) + if ((rc = getaddrinfo(CCS address, NULL, &hints, &res)) != 0 || res == NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "unable to parse \"%s\" as an " "IP address: %s", address, (rc == 0)? "NULL result returned" : gai_strerror(rc)); @@ -172,13 +172,13 @@ Arguments: af AF_INET6 or AF_INET for the socket type address the remote address, in text form port the remote port - timeout a timeout + timeout a timeout (zero for indefinite timeout) Returns: 0 on success; -1 on failure, with errno set */ int -ip_connect(int sock, int af, uschar *address, int port, int timeout) +ip_connect(int sock, int af, const uschar *address, int port, int timeout) { struct sockaddr_in s_in4; struct sockaddr *s_ptr; @@ -208,7 +208,7 @@ IPv6 support. */ memset(&s_in4, 0, sizeof(s_in4)); s_in4.sin_family = AF_INET; s_in4.sin_port = htons(port); - s_in4.sin_addr.s_addr = (S_ADDR_TYPE)inet_addr(CS address); + s_in4.sin_addr.s_addr = (S_ADDR_TYPE)inet_addr(CCS address); s_ptr = (struct sockaddr *)&s_in4; s_len = sizeof(s_in4); } @@ -249,6 +249,111 @@ return -1; +/************************************************* +* Create connected socket to remote host * +*************************************************/ + +/* Create a socket and connect to host (name or number, ipv6 ok) + at one of port-range. + +Arguments: + type SOCK_DGRAM or SOCK_STREAM + af AF_INET6 or AF_INET for the socket type + address the remote address, in text form + portlo,porthi the remote port range + timeout a timeout + connhost if not NULL, host_item filled in with connection details + errstr pointer for allocated string on error + +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 namelen, port; +host_item shost; +host_item *h; +int af = 0, fd, fd4 = -1, fd6 = -1; + +shost.next = NULL; +shost.address = NULL; +shost.port = portlo; +shost.mx = -1; + +namelen = Ustrlen(hostname); + +/* Anything enclosed in [] must be an IP address. */ + +if (hostname[0] == '[' && + hostname[namelen - 1] == ']') + { + uschar * host = string_copy(hostname); + host[namelen - 1] = 0; + host++; + if (string_is_ip_address(host, NULL) == 0) + { + *errstr = string_sprintf("malformed IP address \"%s\"", hostname); + return -1; + } + shost.name = shost.address = host; + } + +/* Otherwise check for an unadorned IP address */ + +else if (string_is_ip_address(hostname, NULL) != 0) + shost.name = shost.address = string_copy(hostname); + +/* 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) + { + *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) + { + 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) + { + *errstr = string_sprintf("failed to create socket: %s", strerror(errno)); + goto bad; + } + + for(port = portlo; port <= porthi; port++) + if (ip_connect(fd, af, h->address, port, timeout) == 0) + { + if (fd != fd6) close(fd6); + if (fd != fd4) close(fd4); + if (connhost) { + h->port = port; + *connhost = *h; + connhost->next = NULL; + } + return fd; + } + } + +*errstr = string_sprintf("failed to connect to any address for %s: %s", + hostname, strerror(errno)); + +bad: + close(fd4); close(fd6); return -1; +} + + /************************************************* * Set keepalive on a socket * *************************************************/ @@ -279,39 +384,37 @@ if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, * 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; -int start_recv = time(NULL); +time_t start_recv = time(NULL); int rc; +if (timeout <= 0) + { + errno = ETIMEDOUT; + return FALSE; + } /* Wait until the socket is ready */ for (;;) { FD_ZERO (&select_inset); - FD_SET (sock, &select_inset); + FD_SET (fd, &select_inset); tv.tv_sec = timeout; tv.tv_usec = 0; - 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 @@ -335,13 +438,37 @@ for (;;) 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; + if (FD_ISSET(fd, &select_inset)) break; } +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: + 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 +*/ + +int +ip_recv(int sock, uschar *buffer, int buffsize, int timeout) +{ +int rc; + +if (!fd_ready(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. */