diff options
Diffstat (limited to 'src/lib/net.c')
-rw-r--r-- | src/lib/net.c | 1237 |
1 files changed, 1237 insertions, 0 deletions
diff --git a/src/lib/net.c b/src/lib/net.c new file mode 100644 index 0000000..fe69faf --- /dev/null +++ b/src/lib/net.c @@ -0,0 +1,1237 @@ +/* Copyright (c) 1999-2018 Dovecot authors, see the included COPYING file */ + +#define _GNU_SOURCE /* For Linux's struct ucred */ +#include "lib.h" +#include "time-util.h" +#include "net.h" + +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> +#include <sys/un.h> +#include <netinet/tcp.h> +#if defined(HAVE_UCRED_H) +# include <ucred.h> /* for getpeerucred() */ +#elif defined(HAVE_SYS_UCRED_H) +# include <sys/ucred.h> /* for FreeBSD struct xucred */ +#endif + +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +}; + +union sockaddr_union_unix { + struct sockaddr sa; + struct sockaddr_un un; +}; + +#define SIZEOF_SOCKADDR(so) ((so).sa.sa_family == AF_INET6 ? \ + sizeof(so.sin6) : sizeof(so.sin)) + +#if !defined(HAVE_GETPEEREID) && !defined(SO_PEERCRED) && !defined(HAVE_GETPEERUCRED) && defined(MSG_WAITALL) && defined(LOCAL_CREDS) +# define NEEDS_LOCAL_CREDS 1 +#else +# undef NEEDS_LOCAL_CREDS +#endif + +/* If connect() fails with EADDRNOTAVAIL (or some others on FreeBSD), retry it + this many times. + + This is needed on busy systems kernel may assign the same source port to two + sockets at bind() stage, which is what we generally want to allow more than + 64k outgoing connections to different destinations. However, at bind() stage + the kernel doesn't know the destination yet. So it's possible that it + assigns the same source port to two (or more) sockets that have the same + destination IP+port as well. In this case connect() will fail with + EADDRNOTAVAIL. We'll need to retry this and hope that the next attempt won't + conflict. */ +#define MAX_CONNECT_RETRIES 20 + +bool net_ip_compare(const struct ip_addr *ip1, const struct ip_addr *ip2) +{ + return net_ip_cmp(ip1, ip2) == 0; +} + +int net_ip_cmp(const struct ip_addr *ip1, const struct ip_addr *ip2) +{ + if (ip1->family != ip2->family) + return ip1->family - ip2->family; + + switch (ip1->family) { + case AF_INET6: + return memcmp(&ip1->u.ip6, &ip2->u.ip6, sizeof(ip1->u.ip6)); + case AF_INET: + return memcmp(&ip1->u.ip4, &ip2->u.ip4, sizeof(ip1->u.ip4)); + default: + break; + } + return 0; +} + +unsigned int net_ip_hash(const struct ip_addr *ip) +{ + const unsigned char *p; + unsigned int len, g, h = 0; + + if (ip->family == AF_INET6) { + p = ip->u.ip6.s6_addr; + len = sizeof(ip->u.ip6); + } else + { + return ip->u.ip4.s_addr; + } + + for (; len > 0; len--, p++) { + h = (h << 4) + *p; + if ((g = h & 0xf0000000UL) != 0) { + h = h ^ (g >> 24); + h = h ^ g; + } + } + + return h; +} + +/* copy IP to sockaddr */ +static inline void +sin_set_ip(union sockaddr_union *so, const struct ip_addr *ip) +{ + if (ip == NULL) { + so->sin6.sin6_family = AF_INET6; + so->sin6.sin6_addr = in6addr_any; + return; + } + + so->sin.sin_family = ip->family; + if (ip->family == AF_INET6) + memcpy(&so->sin6.sin6_addr, &ip->u.ip6, sizeof(ip->u.ip6)); + else + memcpy(&so->sin.sin_addr, &ip->u.ip4, sizeof(ip->u.ip4)); +} + +static inline void +sin_get_ip(const union sockaddr_union *so, struct ip_addr *ip) +{ + /* IP structs may be sent across processes. Clear the whole struct + first to make sure it won't leak any data across processes. */ + i_zero(ip); + + ip->family = so->sin.sin_family; + + if (ip->family == AF_INET6) + memcpy(&ip->u.ip6, &so->sin6.sin6_addr, sizeof(ip->u.ip6)); + else + if (ip->family == AF_INET) + memcpy(&ip->u.ip4, &so->sin.sin_addr, sizeof(ip->u.ip4)); + else + i_zero(&ip->u); +} + +static inline void sin_set_port(union sockaddr_union *so, in_port_t port) +{ + if (so->sin.sin_family == AF_INET6) + so->sin6.sin6_port = htons(port); + else + so->sin.sin_port = htons(port); +} + +static inline in_port_t sin_get_port(union sockaddr_union *so) +{ + if (so->sin.sin_family == AF_INET6) + return ntohs(so->sin6.sin6_port); + if (so->sin.sin_family == AF_INET) + return ntohs(so->sin.sin_port); + + return 0; +} + +static int net_connect_ip_once(const struct ip_addr *ip, in_port_t port, + const struct ip_addr *my_ip, int sock_type, bool blocking) +{ + 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 */ + i_zero(&so); + so.sin.sin_family = ip->family; + fd = socket(ip->family, sock_type, 0); + + if (fd == -1) { + i_error("socket() failed: %m"); + return -1; + } + + /* set socket options */ + (void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + if (sock_type == SOCK_STREAM) + (void)setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)); + if (!blocking) + net_set_nonblock(fd, TRUE); + + /* set our own address */ + if (my_ip != NULL) { + sin_set_ip(&so, my_ip); + if (bind(fd, &so.sa, SIZEOF_SOCKADDR(so)) == -1) { + i_error("bind(%s) failed: %m", net_ip2addr(my_ip)); + i_close_fd(&fd); + return -1; + } + } + + /* 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 + { + i_close_fd(&fd); + return -1; + } + + return fd; +} + +static int net_connect_ip_full(const struct ip_addr *ip, in_port_t port, + const struct ip_addr *my_ip, int sock_type, + bool blocking) +{ + int fd, try; + + for (try = 0;;) { + fd = net_connect_ip_once(ip, port, my_ip, sock_type, blocking); + if (fd != -1 || try++ >= MAX_CONNECT_RETRIES || + (errno != EADDRNOTAVAIL +#ifdef __FreeBSD__ + /* busy */ + && errno != EADDRINUSE + /* pf may cause this if another connection used + the same port recently */ + && errno != EACCES +#endif + )) + break; + } + return fd; +} + +int net_connect_ip(const struct ip_addr *ip, in_port_t port, + const struct ip_addr *my_ip) +{ + return net_connect_ip_full(ip, port, my_ip, SOCK_STREAM, FALSE); +} + +int net_connect_ip_blocking(const struct ip_addr *ip, in_port_t port, + const struct ip_addr *my_ip) +{ + return net_connect_ip_full(ip, port, my_ip, SOCK_STREAM, TRUE); +} + +int net_connect_udp(const struct ip_addr *ip, in_port_t port, + const struct ip_addr *my_ip) +{ + return net_connect_ip_full(ip, port, my_ip, SOCK_DGRAM, FALSE); +} + +int net_try_bind(const struct ip_addr *ip) +{ + union sockaddr_union so; + int fd; + + /* create the socket */ + i_zero(&so); + so.sin.sin_family = ip->family; + fd = socket(ip->family, SOCK_STREAM, 0); + if (fd == -1) { + i_error("socket() failed: %m"); + return -1; + } + + sin_set_ip(&so, ip); + if (bind(fd, &so.sa, SIZEOF_SOCKADDR(so)) == -1) { + i_close_fd(&fd); + return -1; + } + i_close_fd(&fd); + return 0; +} + +int net_connect_unix(const char *path) +{ + union sockaddr_union_unix sa; + int fd, ret; + + i_zero(&sa); + sa.un.sun_family = AF_UNIX; + if (i_strocpy(sa.un.sun_path, path, sizeof(sa.un.sun_path)) < 0) { + /* too long path */ +#ifdef ENAMETOOLONG + errno = ENAMETOOLONG; +#else + errno = EOVERFLOW; +#endif + return -1; + } + + /* create the socket */ + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + i_error("socket(%s) failed: %m", path); + return -1; + } + + net_set_nonblock(fd, TRUE); + + /* connect */ + ret = connect(fd, &sa.sa, sizeof(sa)); + if (ret < 0 && errno != EINPROGRESS) { + i_close_fd(&fd); + return -1; + } + +#ifdef NEEDS_LOCAL_CREDS + { + int on = 1; + if (setsockopt(fd, 0, LOCAL_CREDS, &on, sizeof on)) { + i_error("setsockopt(LOCAL_CREDS) failed: %m"); + return -1; + } + } +#endif + + return fd; +} + +int net_connect_unix_with_retries(const char *path, unsigned int msecs) +{ + struct timeval start, now; + int fd; + + i_gettimeofday(&start); + + do { + fd = net_connect_unix(path); + if (fd != -1 || (errno != EAGAIN && errno != ECONNREFUSED)) + break; + + /* busy. wait for a while. */ + usleep(i_rand_minmax(1, 10) * 10000); + i_gettimeofday(&now); + } while (timeval_diff_msecs(&now, &start) < (int)msecs); + return fd; +} + +void net_disconnect(int fd) +{ + /* FreeBSD's close() fails with ECONNRESET if socket still has unsent + data in transmit buffer. We don't care. */ + if (close(fd) < 0 && errno != ECONNRESET) + i_error("net_disconnect() failed: %m"); +} + +void net_set_nonblock(int fd, bool nonblock) +{ + fd_set_nonblock(fd, nonblock); +} + +int net_set_cork(int fd ATTR_UNUSED, bool cork ATTR_UNUSED) +{ +#ifdef TCP_CORK + int val = cork; + + return setsockopt(fd, IPPROTO_TCP, TCP_CORK, &val, sizeof(val)); +#else + errno = ENOPROTOOPT; + return -1; +#endif +} + +int net_set_tcp_nodelay(int fd, bool nodelay) +{ + int val = nodelay; + + return setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)); +} + +int net_set_tcp_quickack(int fd ATTR_UNUSED, bool quickack ATTR_UNUSED) +{ +#ifdef TCP_QUICKACK + int val = quickack; + + return setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &val, sizeof(val)); +#else + errno = ENOPROTOOPT; + return -1; +#endif +} + +int net_set_send_buffer_size(int fd, size_t size) +{ + int opt; + + if (size > INT_MAX) { + errno = EINVAL; + return -1; + } + opt = (int)size; + return setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)); +} + +int net_set_recv_buffer_size(int fd, size_t size) +{ + int opt; + + if (size > INT_MAX) { + errno = EINVAL; + return -1; + } + opt = (int)size; + return setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt)); +} + +const struct ip_addr net_ip4_any = { + .family = AF_INET, + .u.ip4.s_addr = INADDR_ANY +}; + +const struct ip_addr net_ip6_any = { + .family = AF_INET6, + .u.ip6 = IN6ADDR_ANY_INIT +}; + +const struct ip_addr net_ip4_loopback = { + .family = AF_INET, + .u.ip4.s_addr = INADDR_LOOPBACK +}; + +const struct ip_addr net_ip6_loopback = { + .family = AF_INET6, + .u.ip6 = IN6ADDR_LOOPBACK_INIT +}; + +int net_listen(const struct ip_addr *my_ip, in_port_t *port, int backlog) +{ + enum net_listen_flags flags = 0; + + return net_listen_full(my_ip, port, &flags, backlog); +} + +int net_listen_full(const struct ip_addr *my_ip, in_port_t *port, + enum net_listen_flags *flags, int backlog) +{ + union sockaddr_union so; + int ret, fd, opt = 1; + socklen_t len; + + i_zero(&so); + sin_set_port(&so, *port); + sin_set_ip(&so, my_ip); + + /* create the socket */ + fd = socket(so.sin.sin_family, SOCK_STREAM, 0); + if (fd == -1 && my_ip == NULL && + (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); + } + if (fd == -1) { + i_error("socket() failed: %m"); + return -1; + } + + /* set socket options */ + (void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + (void)setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)); + + if ((*flags & NET_LISTEN_FLAG_REUSEPORT) != 0) { +#ifdef SO_REUSEPORT + if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, + &opt, sizeof(opt)) < 0) +#endif + *flags &= ENUM_NEGATE(NET_LISTEN_FLAG_REUSEPORT); + } + + /* If using IPv6, bind only to IPv6 if possible. This avoids + ambiguities with IPv4-mapped IPv6 addresses. */ +#ifdef IPV6_V6ONLY + if (so.sin.sin_family == AF_INET6) { + opt = 1; + (void)setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); + } +#endif + /* specify the address/port we want to listen in */ + ret = bind(fd, &so.sa, SIZEOF_SOCKADDR(so)); + if (ret < 0) { + if (errno != EADDRINUSE) { + i_error("bind(%s, %u) failed: %m", + my_ip == NULL ? "" : net_ip2addr(my_ip), *port); + } + } else { + /* 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, backlog) >= 0) + return fd; + + if (errno != EADDRINUSE) + i_error("listen() failed: %m"); + } + } + + /* error */ + i_close_fd(&fd); + return -1; +} + +int net_listen_unix(const char *path, int backlog) +{ + union { + struct sockaddr sa; + struct sockaddr_un un; + } sa; + int fd; + + i_zero(&sa); + sa.un.sun_family = AF_UNIX; + if (i_strocpy(sa.un.sun_path, path, sizeof(sa.un.sun_path)) < 0) { + /* too long path */ +#ifdef ENAMETOOLONG + errno = ENAMETOOLONG; +#else + errno = EOVERFLOW; +#endif + return -1; + } + + /* create the socket */ + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + i_error("socket() failed: %m"); + return -1; + } + +#ifdef NEEDS_LOCAL_CREDS + { + int on = 1; + if (setsockopt(fd, 0, LOCAL_CREDS, &on, sizeof on)) { + i_error("setsockopt(LOCAL_CREDS) failed: %m"); + return -1; + } + } +#endif + + /* bind */ + if (bind(fd, &sa.sa, sizeof(sa)) < 0) { + if (errno != EADDRINUSE) + i_error("bind(%s) failed: %m", path); + } else { + /* start listening */ + if (listen(fd, backlog) == 0) + return fd; + + if (errno != EADDRINUSE) + i_error("listen() failed: %m"); + } + + i_close_fd(&fd); + return -1; +} + +int net_listen_unix_unlink_stale(const char *path, int backlog) +{ + unsigned int i = 0; + int fd; + + while ((fd = net_listen_unix(path, backlog)) == -1) { + if (errno != EADDRINUSE || ++i == 2) + return -1; + + /* see if it really exists */ + fd = net_connect_unix(path); + if (fd != -1 || errno != ECONNREFUSED) { + i_close_fd(&fd); + errno = EADDRINUSE; + return -1; + } + + /* delete and try again */ + if (i_unlink_if_exists(path) < 0) { + errno = EADDRINUSE; + return -1; + } + } + return fd; +} + +int net_accept(int fd, struct ip_addr *addr_r, in_port_t *port_r) +{ + union sockaddr_union so; + int ret; + socklen_t addrlen; + + i_assert(fd >= 0); + + i_zero(&so); + addrlen = sizeof(so); + ret = accept(fd, &so.sa, &addrlen); + + if (ret < 0) { + if (errno == EAGAIN || errno == ECONNABORTED) + return -1; + else + return -2; + } + if (so.sin.sin_family == AF_UNIX) { + if (addr_r != NULL) + i_zero(addr_r); + if (port_r != NULL) *port_r = 0; + } else { + if (addr_r != NULL) sin_get_ip(&so, addr_r); + if (port_r != NULL) *port_r = sin_get_port(&so); + } + return ret; +} + +ssize_t net_receive(int fd, void *buf, size_t len) +{ + ssize_t ret; + + i_assert(fd >= 0); + i_assert(len <= SSIZE_T_MAX); + + ret = read(fd, buf, len); + if (ret == 0) { + /* disconnected */ + errno = 0; + return -2; + } + + if (unlikely(ret < 0)) { + if (errno == EINTR || errno == EAGAIN) + return 0; + + if (errno == ECONNRESET || errno == ETIMEDOUT) { + /* treat as disconnection */ + return -2; + } + } + + return ret; +} + +int net_gethostbyname(const char *addr, struct ip_addr **ips, + unsigned int *ips_count) +{ + /* @UNSAFE */ + union sockaddr_union *so; + struct addrinfo hints, *ai, *origai; + struct ip_addr ip; + int host_error; + int count; + + *ips = NULL; + *ips_count = 0; + + /* support [ipv6] style addresses here so they work globally */ + if (addr[0] == '[' && net_addr2ip(addr, &ip) == 0) { + *ips_count = 1; + *ips = t_new(struct ip_addr, 1); + **ips = ip; + return 0; + } + + i_zero(&hints); + 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; + + /* get number of IPs */ + origai = ai; + for (count = 0; ai != NULL; ai = ai->ai_next) + count++; + + *ips_count = count; + *ips = t_new(struct ip_addr, 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); + + return 0; +} + +int net_gethostbyaddr(const struct ip_addr *ip, const char **name_r) +{ + union sockaddr_union so; + socklen_t addrlen = sizeof(so); + char hbuf[NI_MAXHOST]; + int ret; + + i_zero(&so); + sin_set_ip(&so, ip); + ret = getnameinfo(&so.sa, addrlen, hbuf, sizeof(hbuf), NULL, 0, + NI_NAMEREQD); + if (ret != 0) + return ret; + + *name_r = t_strdup(hbuf); + return 0; +} + +int net_getsockname(int fd, struct ip_addr *addr, in_port_t *port) +{ + union sockaddr_union so; + socklen_t addrlen; + + i_assert(fd >= 0); + + i_zero(&so); + addrlen = sizeof(so); + if (getsockname(fd, &so.sa, &addrlen) == -1) + return -1; + if (so.sin.sin_family == AF_UNIX) { + if (addr != NULL) + i_zero(addr); + if (port != NULL) *port = 0; + } else { + if (addr != NULL) sin_get_ip(&so, addr); + if (port != NULL) *port = sin_get_port(&so); + } + return 0; +} + +int net_getpeername(int fd, struct ip_addr *addr, in_port_t *port) +{ + union sockaddr_union so; + socklen_t addrlen; + + i_assert(fd >= 0); + + i_zero(&so); + addrlen = sizeof(so); + if (getpeername(fd, &so.sa, &addrlen) == -1) + return -1; + if (so.sin.sin_family == AF_UNIX) { + if (addr != NULL) + i_zero(addr); + if (port != NULL) *port = 0; + } else { + if (addr != NULL) sin_get_ip(&so, addr); + if (port != NULL) *port = sin_get_port(&so); + } + return 0; +} + +int net_getunixname(int fd, const char **name_r) +{ + union sockaddr_union_unix so; + socklen_t addrlen = sizeof(so); + + i_zero(&so); + if (getsockname(fd, &so.sa, &addrlen) < 0) + return -1; + if (so.un.sun_family != AF_UNIX) { + errno = ENOTSOCK; + return -1; + } + *name_r = t_strdup(so.un.sun_path); + return 0; +} + +int net_getunixcred(int fd, struct net_unix_cred *cred_r) +{ +#if defined(SO_PEERCRED) +# if defined(HAVE_STRUCT_SOCKPEERCRED) + /* OpenBSD (may also provide getpeereid, but we also want pid) */ + struct sockpeercred ucred; +# else + /* Linux */ + struct ucred ucred; +# endif + socklen_t len = sizeof(ucred); + + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) < 0) { + i_error("getsockopt(SO_PEERCRED) failed: %m"); + return -1; + } + cred_r->uid = ucred.uid; + cred_r->gid = ucred.gid; + cred_r->pid = ucred.pid; + return 0; +#elif defined(LOCAL_PEEREID) + /* NetBSD (may also provide getpeereid, but we also want pid) */ + struct unpcbid ucred; + socklen_t len = sizeof(ucred); + + if (getsockopt(fd, 0, LOCAL_PEEREID, &ucred, &len) < 0) { + i_error("getsockopt(LOCAL_PEEREID) failed: %m"); + return -1; + } + + cred_r->uid = ucred.unp_euid; + cred_r->gid = ucred.unp_egid; + cred_r->pid = ucred.unp_pid; + return 0; +#elif defined(HAVE_GETPEEREID) + /* OSX 10.4+, FreeBSD 4.6+, OpenBSD 3.0+, NetBSD 5.0+ */ + if (getpeereid(fd, &cred_r->uid, &cred_r->gid) < 0) { + i_error("getpeereid() failed: %m"); + return -1; + } + cred_r->pid = (pid_t)-1; + return 0; +#elif defined(LOCAL_PEERCRED) + /* Older FreeBSD */ + struct xucred ucred; + socklen_t len = sizeof(ucred); + + if (getsockopt(fd, 0, LOCAL_PEERCRED, &ucred, &len) < 0) { + i_error("getsockopt(LOCAL_PEERCRED) failed: %m"); + return -1; + } + + if (ucred.cr_version != XUCRED_VERSION) { + errno = EINVAL; + return -1; + } + + cred_r->uid = ucred.cr_uid; + cred_r->gid = ucred.cr_gid; + cred_r->pid = (pid_t)-1; + return 0; +#elif defined(HAVE_GETPEERUCRED) + /* Solaris */ + ucred_t *ucred = NULL; + + if (getpeerucred(fd, &ucred) < 0) { + i_error("getpeerucred() failed: %m"); + return -1; + } + cred_r->uid = ucred_geteuid(ucred); + cred_r->gid = ucred_getrgid(ucred); + cred_r->pid = ucred_getpid(ucred); + ucred_free(ucred); + + if (cred_r->uid == (uid_t)-1 || + cred_r->gid == (gid_t)-1) { + errno = EINVAL; + return -1; + } + return 0; +#elif defined(NEEDS_LOCAL_CREDS) + /* NetBSD < 5 */ + int i, n, on; + struct iovec iov; + struct msghdr msg; + struct { + struct cmsghdr ch; + char buf[110]; + } cdata; + struct sockcred *sc; + + iov.iov_base = (char *)&on; + iov.iov_len = 1; + + sc = (struct sockcred *)cdata.buf; + sc->sc_uid = sc->sc_euid = sc->sc_gid = sc->sc_egid = -1; + i_zero(&cdata.ch); + + i_zero(&msg); + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = &cdata; + msg.msg_controllen = sizeof(cdata.ch) + sizeof(cdata.buf); + + for (i = 0; i < 10; i++) { + n = recvmsg(fd, &msg, MSG_WAITALL | MSG_PEEK); + if (n >= 0 || errno != EAGAIN) + break; + usleep(100); + } + if (n < 0) { + i_error("recvmsg() failed: %m"); + return -1; + } + cred_r->uid = sc->sc_euid; + cred_r->gid = sc->sc_egid; + cred_r->pid = (pid_t)-1; + return 0; +#else + errno = EINVAL; + return -1; +#endif +} + +const char *net_ip2addr(const struct ip_addr *ip) +{ + char *addr = t_malloc_no0(MAX_IP_LEN+1); + + if (inet_ntop(ip->family, &ip->u.ip6, addr, MAX_IP_LEN) == NULL) + return ""; + + return addr; +} + +static bool net_addr2ip_inet4_fast(const char *addr, struct ip_addr *ip) +{ + uint8_t *saddr = (void *)&ip->u.ip4.s_addr; + unsigned int i, num; + + if (str_parse_uint(addr, &num, &addr) < 0) + return FALSE; + if (*addr == '\0' && num <= 0xffffffff) { + /* single-number IPv4 address */ + ip->u.ip4.s_addr = htonl(num); + ip->family = AF_INET; + return TRUE; + } + + /* try to parse as a.b.c.d */ + i = 0; + for (;;) { + if (num >= 256) + return FALSE; + saddr[i] = num; + if (i == 3) + break; + i++; + if (*addr != '.') + return FALSE; + addr++; + if (str_parse_uint(addr, &num, &addr) < 0) + return FALSE; + } + if (*addr != '\0') + return FALSE; + ip->family = AF_INET; + return TRUE; +} + +int net_addr2ip(const char *addr, struct ip_addr *ip) +{ + int ret; + + if (net_addr2ip_inet4_fast(addr, ip)) + return 0; + + if (strchr(addr, ':') != NULL) { + /* IPv6 */ + T_BEGIN { + if (addr[0] == '[') { + /* allow [ipv6 addr] */ + size_t len = strlen(addr); + if (addr[len-1] == ']') + addr = t_strndup(addr+1, len-2); + } + ret = inet_pton(AF_INET6, addr, &ip->u.ip6); + } T_END; + if (ret == 0) + return -1; + ip->family = AF_INET6; + } else { + /* IPv4 */ + if (inet_aton(addr, &ip->u.ip4) == 0) + return -1; + ip->family = AF_INET; + } + return 0; +} + +int net_str2port(const char *str, in_port_t *port_r) +{ + uintmax_t l; + + if (str_to_uintmax(str, &l) < 0) + return -1; + + if (l == 0 || l > (in_port_t)-1) + return -1; + *port_r = (in_port_t)l; + return 0; +} + +int net_str2port_zero(const char *str, in_port_t *port_r) +{ + uintmax_t l; + + if (str_to_uintmax(str, &l) < 0) + return -1; + + if (l > (in_port_t)-1) + return -1; + *port_r = (in_port_t)l; + return 0; +} + +int net_str2hostport(const char *str, in_port_t default_port, + const char **host_r, in_port_t *port_r) +{ + const char *p, *host; + in_port_t port; + + if (str[0] == '[') { + /* [IPv6] address, possibly followed by :port */ + p = strchr(str, ']'); + if (p == NULL) + return -1; + host = t_strdup_until(str+1, p++); + } else { + p = strchr(str, ':'); + if (p == NULL || strchr(p+1, ':') != NULL) { + /* host or IPv6 address */ + *host_r = str; + *port_r = default_port; + return 0; + } + host = t_strdup_until(str, p); + } + if (p[0] == '\0') { + *host_r = host; + *port_r = default_port; + return 0; + } + if (p[0] != ':') + return -1; + if (net_str2port(p+1, &port) < 0) + return -1; + *host_r = host; + *port_r = port; + return 0; +} + +int net_ipport2str(const struct ip_addr *ip, in_port_t port, const char **str_r) +{ + if (!IPADDR_IS_V4(ip) && !IPADDR_IS_V6(ip)) return -1; + + *str_r = t_strdup_printf("%s%s%s:%u", + IPADDR_IS_V6(ip) ? "[" : "", + net_ip2addr(ip), + IPADDR_IS_V6(ip) ? "]" : "", + port); + return 0; +} + +int net_ipv6_mapped_ipv4_convert(const struct ip_addr *src, + struct ip_addr *dest) +{ + static uint8_t v4_prefix[] = + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff }; + + if (!IPADDR_IS_V6(src)) + return -1; + if (memcmp(src->u.ip6.s6_addr, v4_prefix, sizeof(v4_prefix)) != 0) + return -1; + + i_zero(dest); + dest->family = AF_INET; + memcpy(&dest->u.ip6, &src->u.ip6.s6_addr[3*4], 4); + return 0; +} + +int net_geterror(int fd) +{ + int data; + socklen_t len = sizeof(data); + + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &data, &len) == -1) { + /* we're now really returning the getsockopt()'s error code + instead of the socket's, but normally we should never get + here anyway. */ + return errno; + } + + return data; +} + +const char *net_gethosterror(int error) +{ + i_assert(error != 0); + + return gai_strerror(error); +} + +enum net_hosterror_type net_get_hosterror_type(int error) +{ + const struct { + int error; + enum net_hosterror_type type; + } error_map[] = { +#ifdef EAI_ADDRFAMILY /* Obsoleted by RFC 2553bis-02 */ + { EAI_ADDRFAMILY, NET_HOSTERROR_TYPE_NOT_FOUND }, +#endif + { EAI_AGAIN, NET_HOSTERROR_TYPE_NAMESERVER }, + { EAI_BADFLAGS, NET_HOSTERROR_TYPE_INTERNAL_ERROR }, + { EAI_FAIL, NET_HOSTERROR_TYPE_NAMESERVER }, + { EAI_FAMILY, NET_HOSTERROR_TYPE_INTERNAL_ERROR }, + { EAI_MEMORY, NET_HOSTERROR_TYPE_INTERNAL_ERROR }, +#ifdef EAI_NODATA /* Obsoleted by RFC 2553bis-02 */ + { EAI_NODATA, NET_HOSTERROR_TYPE_NOT_FOUND }, +#endif + { EAI_NONAME, NET_HOSTERROR_TYPE_NOT_FOUND }, + { EAI_SERVICE, NET_HOSTERROR_TYPE_INTERNAL_ERROR }, + { EAI_SOCKTYPE, NET_HOSTERROR_TYPE_INTERNAL_ERROR }, + { EAI_SYSTEM, NET_HOSTERROR_TYPE_INTERNAL_ERROR }, + }; + for (unsigned int i = 0; i < N_ELEMENTS(error_map); i++) { + if (error_map[i].error == error) + return error_map[i].type; + } + + /* shouldn't happen? assume internal error */ + return NET_HOSTERROR_TYPE_INTERNAL_ERROR; +} + +int net_hosterror_notfound(int error) +{ +#ifdef EAI_NODATA /* NODATA is depricated */ + return (error != 1 && (error == EAI_NONAME || error == EAI_NODATA)) ? 1 : 0; +#else + return (error != 1 && (error == EAI_NONAME)) ? 1 : 0; +#endif +} + +const char *net_getservbyport(in_port_t port) +{ + struct servent *entry; + + entry = getservbyport(htons(port), "tcp"); + return entry == NULL ? NULL : entry->s_name; +} + +bool is_ipv4_address(const char *addr) +{ + while (*addr != '\0') { + if (*addr != '.' && !i_isdigit(*addr)) + return FALSE; + addr++; + } + + return TRUE; +} + +bool is_ipv6_address(const char *addr) +{ + bool have_prefix = FALSE; + + if (*addr == '[') { + have_prefix = TRUE; + addr++; + } + while (*addr != '\0') { + if (*addr != ':' && !i_isxdigit(*addr)) { + if (have_prefix && *addr == ']' && addr[1] == '\0') + break; + return FALSE; + } + addr++; + } + + return TRUE; +} + +int net_parse_range(const char *network, struct ip_addr *ip_r, + unsigned int *bits_r) +{ + const char *p; + unsigned int bits, max_bits; + + p = strchr(network, '/'); + if (p != NULL) + network = t_strdup_until(network, p++); + + if (net_addr2ip(network, ip_r) < 0) + return -1; + + max_bits = IPADDR_BITS(ip_r); + if (p == NULL) { + /* full IP address must match */ + bits = max_bits; + } else { + /* get the network mask */ + if (str_to_uint(p, &bits) < 0 || bits > max_bits) + return -1; + } + *bits_r = bits; + return 0; +} + +bool net_is_in_network(const struct ip_addr *ip, + const struct ip_addr *net_ip, unsigned int bits) +{ + struct ip_addr tmp_ip; + const uint32_t *ip1, *ip2; + uint32_t mask, i1, i2; + unsigned int pos, i; + + if (net_ipv6_mapped_ipv4_convert(ip, &tmp_ip) == 0) { + /* IPv4 address mapped disguised as IPv6 address */ + ip = &tmp_ip; + } + + if (ip->family == 0 || net_ip->family == 0) { + /* non-IPv4/IPv6 address (e.g. UNIX socket) never matches + anything */ + return FALSE; + } + if (IPADDR_IS_V4(ip) != IPADDR_IS_V4(net_ip)) { + /* one is IPv6 and one is IPv4 */ + return FALSE; + } + i_assert(IPADDR_IS_V6(ip) == IPADDR_IS_V6(net_ip)); + + if (IPADDR_IS_V4(ip)) { + ip1 = &ip->u.ip4.s_addr; + ip2 = &net_ip->u.ip4.s_addr; + } else { + ip1 = (const void *)&ip->u.ip6; + ip2 = (const void *)&net_ip->u.ip6; + } + + /* check first the full 32bit ints */ + for (pos = 0, i = 0; pos + 32 <= bits; pos += 32, i++) { + if (ip1[i] != ip2[i]) + return FALSE; + } + i1 = htonl(ip1[i]); + i2 = htonl(ip2[i]); + + /* check the last full bytes */ + for (mask = 0xff000000; pos + 8 <= bits; pos += 8, mask >>= 8) { + if ((i1 & mask) != (i2 & mask)) + return FALSE; + } + + /* check the last bits, they're reversed in bytes */ + bits -= pos; + for (mask = 0x80000000 >> (pos % 32); bits > 0; bits--, mask >>= 1) { + if ((i1 & mask) != (i2 & mask)) + return FALSE; + } + return TRUE; +} |