Mercurial > dovecot > core-2.2
diff src/lib/network.c @ 0:3b1985cbc908 HEAD
Initial revision
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Fri, 09 Aug 2002 12:15:38 +0300 |
parents | |
children | a3d77e73f99b |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/network.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,596 @@ +/* + network.c : Network stuff with IPv6 support + + Copyright (c) 1999-2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "network.h" + +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> +#include <sys/un.h> +#include <netinet/tcp.h> + +#define LISTEN_BACKLOG 8 + +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in sin; +#ifdef HAVE_IPV6 + struct sockaddr_in6 sin6; +#endif +}; + +#ifdef HAVE_IPV6 +# define SIZEOF_SOCKADDR(so) ((so).sa.sa_family == AF_INET6 ? \ + sizeof(so.sin6) : sizeof(so.sin)) +#else +# define SIZEOF_SOCKADDR(so) (sizeof(so.sin)) +#endif + +int net_ip_compare(IPADDR *ip1, IPADDR *ip2) +{ + if (ip1->family != ip2->family) + return 0; + +#ifdef HAVE_IPV6 + if (ip1->family == AF_INET6) + return memcmp(&ip1->ip, &ip2->ip, sizeof(ip1->ip)) == 0; +#endif + + return memcmp(&ip1->ip, &ip2->ip, 4) == 0; +} + + +/* copy IP to sockaddr */ +static inline void sin_set_ip(union sockaddr_union *so, const IPADDR *ip) +{ + if (ip == NULL) { +#ifdef HAVE_IPV6 + so->sin6.sin6_family = AF_INET6; + so->sin6.sin6_addr = in6addr_any; +#else + so->sin.sin_family = AF_INET; + so->sin.sin_addr.s_addr = INADDR_ANY; +#endif + return; + } + + so->sin.sin_family = ip->family; +#ifdef HAVE_IPV6 + if (ip->family == AF_INET6) + memcpy(&so->sin6.sin6_addr, &ip->ip, sizeof(ip->ip)); + else +#endif + memcpy(&so->sin.sin_addr, &ip->ip, 4); +} + +static inline void sin_get_ip(const union sockaddr_union *so, IPADDR *ip) +{ + ip->family = so->sin.sin_family; + +#ifdef HAVE_IPV6 + if (ip->family == AF_INET6) + memcpy(&ip->ip, &so->sin6.sin6_addr, sizeof(ip->ip)); + else +#endif + memcpy(&ip->ip, &so->sin.sin_addr, 4); +} + +static inline void sin_set_port(union sockaddr_union *so, int port) +{ +#ifdef HAVE_IPV6 + if (so->sin.sin_family == AF_INET6) + so->sin6.sin6_port = htons((unsigned short) port); + else +#endif + so->sin.sin_port = htons((unsigned short) port); +} + +static inline int sin_get_port(union sockaddr_union *so) +{ +#ifdef HAVE_IPV6 + if (so->sin.sin_family == AF_INET6) + return ntohs(so->sin6.sin6_port); +#endif + return ntohs(so->sin.sin_port); +} + +static inline void close_save_errno(int fd) +{ + int old_errno = errno; + close(fd); + errno = old_errno; +} + +/* Connect to socket with ip address */ +int net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip) +{ + union sockaddr_union so; + int fd, ret, opt = 1; + + if (my_ip != NULL && ip->family != my_ip->family) { + i_warning("net_connect_ip(): ip->family != my_ip->family"); + my_ip = NULL; + } + + /* create the socket */ + memset(&so, 0, sizeof(so)); + so.sin.sin_family = ip->family; + fd = socket(ip->family, SOCK_STREAM, 0); + + if (fd == -1) + return -1; + + /* set socket options */ + net_set_nonblock(fd, TRUE); + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)); + + /* set our own address */ + if (my_ip != NULL) { + sin_set_ip(&so, my_ip); + if (bind(fd, &so.sa, SIZEOF_SOCKADDR(so)) == -1) { + /* failed, set it back to INADDR_ANY */ + sin_set_ip(&so, NULL); + bind(fd, &so.sa, SIZEOF_SOCKADDR(so)); + } + } + + /* connect */ + sin_set_ip(&so, ip); + sin_set_port(&so, port); + ret = connect(fd, &so.sa, SIZEOF_SOCKADDR(so)); + +#ifndef WIN32 + if (ret < 0 && errno != EINPROGRESS) +#else + if (ret < 0 && WSAGetLastError() != WSAEWOULDBLOCK) +#endif + { + close_save_errno(fd); + return -1; + } + + return fd; +} + +int net_connect_unix(const char *path) +{ + struct sockaddr_un sa; + int fd, ret; + + if (strlen(path) > sizeof(sa.sun_path)-1) { + /* too long path */ + errno = EINVAL; + return -1; + } + + /* create the socket */ + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + return -1; + + /* set socket options */ + net_set_nonblock(fd, TRUE); + + /* connect */ + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + strcpy(sa.sun_path, path); + + ret = connect(fd, (struct sockaddr *) &sa, sizeof(sa)); + if (ret < 0 && errno != EINPROGRESS) { + close_save_errno(fd); + return -1; + } + + return fd; +} + +/* Disconnect socket */ +void net_disconnect(int fd) +{ + close(fd); +} + +/* Set socket blocking/nonblocking */ +void net_set_nonblock(int fd, int nonblock) +{ +#ifdef HAVE_FCNTL + if (fcntl(fd, F_SETFL, nonblock ? O_NONBLOCK : 0) < 0) + i_fatal("net_send_nonblock() failed: %m"); +#endif +} + +void net_set_cork(int fd, int cork) +{ +#ifdef TCP_CORK + setsockopt(fd, SOL_TCP, TCP_CORK, &cork, sizeof(cork)); +#endif +} + +/* Listen for connections on a socket. if `my_ip' is NULL, listen in any + address. */ +int net_listen(IPADDR *my_ip, int *port) +{ + union sockaddr_union so; + int ret, fd, opt = 1; + socklen_t len; + + i_assert(port != NULL); + + memset(&so, 0, sizeof(so)); + sin_set_port(&so, *port); + sin_set_ip(&so, my_ip); + + /* create the socket */ + fd = socket(so.sin.sin_family, SOCK_STREAM, 0); +#ifdef HAVE_IPV6 + if (fd == -1 && (errno == EINVAL || errno == EAFNOSUPPORT)) { + /* IPv6 is not supported by OS */ + so.sin.sin_family = AF_INET; + so.sin.sin_addr.s_addr = INADDR_ANY; + + fd = socket(AF_INET, SOCK_STREAM, 0); + } +#endif + if (fd == -1) + return -1; + + /* set socket options */ + net_set_nonblock(fd, TRUE); + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)); + + /* specify the address/port we want to listen in */ + ret = bind(fd, &so.sa, SIZEOF_SOCKADDR(so)); + if (ret >= 0) { + /* get the actual port we started listen */ + len = SIZEOF_SOCKADDR(so); + ret = getsockname(fd, &so.sa, &len); + if (ret >= 0) { + *port = sin_get_port(&so); + + /* start listening */ + if (listen(fd, LISTEN_BACKLOG) >= 0) + return fd; + } + + } + + /* error */ + close_save_errno(fd); + return -1; +} + +int net_listen_unix(const char *path) +{ + struct sockaddr_un sa; + int fd; + + if (strlen(path) > sizeof(sa.sun_path)-1) { + /* too long path */ + errno = EINVAL; + return -1; + } + + /* create the socket */ + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + return -1; + + /* set socket options */ + net_set_nonblock(fd, TRUE); + + /* bind */ + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + strcpy(sa.sun_path, path); + + if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) == 0) { + /* start listening */ + if (listen(fd, LISTEN_BACKLOG) == 0) + return fd; + } + + close_save_errno(fd); + return -1; +} + +/* Accept a connection on a socket */ +int net_accept(int fd, IPADDR *addr, int *port) +{ + union sockaddr_union so; + int ret; + socklen_t addrlen; + + i_assert(fd >= 0); + + addrlen = sizeof(so); + ret = accept(fd, &so.sa, &addrlen); + + if (ret < 0) + return -1; + + if (addr != NULL) sin_get_ip(&so, addr); + if (port != NULL) *port = sin_get_port(&so); + + net_set_nonblock(fd, TRUE); + return ret; +} + +/* Read data from socket, return number of bytes read, -1 = error */ +int net_receive(int fd, void *buf, unsigned int len) +{ + int ret; + + i_assert(fd >= 0); + i_assert(buf != NULL); + i_assert(len <= INT_MAX); + + ret = recv(fd, buf, len, 0); + if (ret == 0) + return -1; /* disconnected */ + + if (ret < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + + return ret; +} + +/* Transmit data, return number of bytes sent, -1 = error */ +int net_transmit(int fd, const void *data, unsigned int len) +{ + int ret; + + i_assert(fd >= 0); + i_assert(data != NULL); + i_assert(len <= INT_MAX); + + ret = send(fd, (void *) data, len, 0); + if (ret == -1 && (errno == EINTR || errno == EPIPE || errno == EAGAIN)) + return 0; + + return ret; +} + +/* Get IP addresses for host. ips contains ips_count of IPs, they don't need + to be free'd. Returns 0 = ok, others = error code for net_gethosterror() */ +int net_gethostbyname(const char *addr, IPADDR **ips, int *ips_count) +{ +#ifdef HAVE_IPV6 + union sockaddr_union *so; + struct addrinfo hints, *ai, *origai; + char hbuf[NI_MAXHOST]; + int host_error; +#else + struct hostent *hp; +#endif + int count; + + i_assert(addr != NULL); + i_assert(ips != NULL); + i_assert(ips_count != NULL); + + *ips = NULL; + *ips_count = 0; + +#ifdef HAVE_IPV6 + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + + /* save error to host_error for later use */ + host_error = getaddrinfo(addr, NULL, &hints, &ai); + if (host_error != 0) + return host_error; + + if (getnameinfo(ai->ai_addr, ai->ai_addrlen, hbuf, + sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0) + return 1; + + + /* get number of IPs */ + origai = ai; + for (count = 0; ai != NULL; ai = ai->ai_next) + count++; + + *ips_count = count; + *ips = t_malloc(sizeof(IPADDR) * count); + + count = 0; + for (ai = origai; ai != NULL; ai = ai->ai_next, count++) { + so = (union sockaddr_union *) ai->ai_addr; + + sin_get_ip(so, ips[count]); + } + freeaddrinfo(origai); +#else + hp = gethostbyname(addr); + if (hp == NULL) + return h_errno; + + /* get number of IPs */ + count = 0; + while (hp->h_addr_list[count] != NULL) + count++; + + *ips_count = count; + *ips = t_malloc(sizeof(IPADDR) * count); + + while (count > 0) { + count--; + + (*ips)[count].family = AF_INET; + memcpy(&(*ips)[count].ip, hp->h_addr_list[count], 4); + } +#endif + + return 0; +} + +/* Get socket address/port */ +int net_getsockname(int fd, IPADDR *addr, int *port) +{ + union sockaddr_union so; + socklen_t addrlen; + + i_assert(fd >= 0); + + addrlen = sizeof(so); + if (getsockname(fd, (struct sockaddr *) &so, &addrlen) == -1) + return -1; + + if (addr != NULL) sin_get_ip(&so, addr); + if (port != NULL) *port = sin_get_port(&so); + + return 0; +} + +int net_ip2host(IPADDR *ip, char *host) +{ +#ifdef HAVE_IPV6 + if (!inet_ntop(ip->family, &ip->ip, host, MAX_IP_LEN)) + return -1; +#else + unsigned long ip4; + + if (ip->family != AF_INET) { + strcpy(host, "0.0.0.0"); + return -1; + } + + ip4 = ntohl(ip->ip.s_addr); + i_snprintf(host, MAX_IP_LEN, "%lu.%lu.%lu.%lu", + (ip4 & 0xff000000UL) >> 24, + (ip4 & 0x00ff0000) >> 16, + (ip4 & 0x0000ff00) >> 8, + (ip4 & 0x000000ff)); +#endif + return 0; +} + +int net_host2ip(const char *host, IPADDR *ip) +{ + if (strchr(host, ':') != NULL) { + /* IPv6 */ + ip->family = AF_INET6; +#ifdef HAVE_IPV6 + if (inet_pton(AF_INET6, host, &ip->ip) == 0) + return -1; +#else + ip->ip.s_addr = 0; +#endif + } else { + /* IPv4 */ + ip->family = AF_INET; + if (inet_aton(host, (struct in_addr *) &ip->ip) == 0) + return -1; + } + + return 0; +} + +/* Get socket error */ +int net_geterror(int fd) +{ + int data; + socklen_t len = sizeof(data); + + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &data, &len) == -1) + return -1; + + return data; +} + +/* get error of net_gethostname() */ +const char *net_gethosterror(int error) +{ +#ifdef HAVE_IPV6 + i_assert(error != 0); + + if (error == 1) { + /* getnameinfo() failed */ + return strerror(errno); + } + + return gai_strerror(error); +#else + switch (error) { + case HOST_NOT_FOUND: + return "Host not found"; + case NO_ADDRESS: + return "No IP address found for name"; + case NO_RECOVERY: + return "A non-recovable name server error occurred"; + case TRY_AGAIN: + return "A temporary error on an authoritative name server"; + } + + /* unknown error */ + return NULL; +#endif +} + +/* return TRUE if host lookup failed because it didn't exist (ie. not + some error with name server) */ +int net_hosterror_notfound(int error) +{ +#ifdef HAVE_IPV6 + return error != 1 && (error == EAI_NONAME || error == EAI_NODATA); +#else + return error == HOST_NOT_FOUND || error == NO_ADDRESS; +#endif +} + +/* Get name of TCP service */ +char *net_getservbyport(int port) +{ + struct servent *entry; + + entry = getservbyport(htons((unsigned short) port), "tcp"); + return entry == NULL ? NULL : entry->s_name; +} + +int is_ipv4_address(const char *host) +{ + while (*host != '\0') { + if (*host != '.' && !i_isdigit(*host)) + return 0; + host++; + } + + return 1; +} + +int is_ipv6_address(const char *host) +{ + while (*host != '\0') { + if (*host != ':' && !i_isxdigit(*host)) + return 0; + host++; + } + + return 1; +}