/* Copyright (c) 1999-2016 Dovecot authors, see the included COPYING file */

#define _GNU_SOURCE /* For Linux's struct ucred */
#include "lib.h"
#include "fd-set-nonblock.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;
#ifdef HAVE_IPV6
	struct sockaddr_in6 sin6;
#endif
};

union sockaddr_union_unix {
	struct sockaddr sa;
	struct sockaddr_un un;
};

#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

#if !defined(HAVE_GETPEEREID) && !defined(SO_PEERCRED) && !defined(HAVE_GETPEERUCRED) && defined(MSG_WAITALL) && defined(LOCAL_CREDS)
#  define NEEDS_LOCAL_CREDS 1
#endif

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;

#ifdef HAVE_IPV6
	if (ip1->family == AF_INET6)
		return memcmp(&ip1->u.ip6, &ip2->u.ip6, sizeof(ip1->u.ip6));
#endif

	return memcmp(&ip1->u.ip4, &ip2->u.ip4, sizeof(ip1->u.ip4));
}

unsigned int net_ip_hash(const struct ip_addr *ip)
{
        const unsigned char *p;
	unsigned int len, g, h = 0;

#ifdef HAVE_IPV6
	if (ip->family == AF_INET6) {
		p = ip->u.ip6.s6_addr;
		len = sizeof(ip->u.ip6);
	} else
#endif
	{
		return ip->u.ip4.s_addr;
	}

	for (; len > 0; len--, p++) {
		h = (h << 4) + *p;
		if ((g = h & 0xf0000000UL)) {
			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) {
#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->u.ip6, sizeof(ip->u.ip6));
	else
#endif
		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. */
	memset(ip, 0, sizeof(*ip));

	ip->family = so->sin.sin_family;

#ifdef HAVE_IPV6
	if (ip->family == AF_INET6)
		memcpy(&ip->u.ip6, &so->sin6.sin6_addr, sizeof(ip->u.ip6));
	else
#endif
	if (ip->family == AF_INET)
		memcpy(&ip->u.ip4, &so->sin.sin_addr, sizeof(ip->u.ip4));
	else
		memset(&ip->u, 0, sizeof(ip->u));
}

static inline void sin_set_port(union sockaddr_union *so, in_port_t port)
{
#ifdef HAVE_IPV6
	if (so->sin.sin_family == AF_INET6)
                so->sin6.sin6_port = htons(port);
	else
#endif
		so->sin.sin_port = htons(port);
}

static inline in_port_t sin_get_port(union sockaddr_union *so)
{
#ifdef HAVE_IPV6
	if (so->sin.sin_family == AF_INET6)
		return ntohs(so->sin6.sin6_port);
#endif
	if (so->sin.sin_family == AF_INET)
		return ntohs(so->sin.sin_port);

	return 0;
}

#ifdef __FreeBSD__
static int
net_connect_ip_full_freebsd(const struct ip_addr *ip, in_port_t port,
			    const struct ip_addr *my_ip, int sock_type,
			    bool blocking);

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_full_freebsd(ip, port, my_ip, sock_type, blocking);
		if (fd != -1 || ++try == 5 ||
		    (errno != EADDRINUSE && errno != EACCES))
			break;
		/*
		   This may be just a temporary problem:

		   EADDRINUSE: busy
		   EACCES: pf may cause this if another connection used
		           the same port recently
		*/
	}
	return fd;
}
/* then some kludging: */
#define net_connect_ip_full net_connect_ip_full_freebsd
#endif

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)
{
	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_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;
}
#ifdef __FreeBSD__
#  undef net_connect_ip_full
#endif

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 */
	memset(&so, 0, sizeof(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;

	memset(&sa, 0, sizeof(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 = EINVAL;
#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;

	if (gettimeofday(&start, NULL) < 0)
		i_panic("gettimeofday() failed: %m");

	do {
		fd = net_connect_unix(path);
		if (fd != -1 || (errno != EAGAIN && errno != ECONNREFUSED))
			break;

		/* busy. wait for a while. */
		usleep(((rand() % 10) + 1) * 10000);
		if (gettimeofday(&now, NULL) < 0)
			i_panic("gettimeofday() failed: %m");
	} 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
}

void net_get_ip_any4(struct ip_addr *ip)
{
	ip->family = AF_INET;
	ip->u.ip4.s_addr = INADDR_ANY;
}

void net_get_ip_any6(struct ip_addr *ip)
{
#ifdef HAVE_IPV6
	ip->family = AF_INET6;
	ip->u.ip6 = in6addr_any;
#else
	memset(ip, 0, sizeof(struct ip_addr));
#endif
}

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;

	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 && 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);
	}
#endif
	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 &= ~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;

	memset(&sa, 0, sizeof(sa));
	sa.un.sun_family = AF_UNIX;
	if (i_strocpy(sa.un.sun_path, path, sizeof(sa.un.sun_path)) < 0) {
		/* too long path */
		errno = EOVERFLOW;
		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) {
			if (fd != -1) 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);

	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)
			memset(addr_r, 0, sizeof(*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;
}

ssize_t net_transmit(int fd, const void *data, size_t len)
{
        ssize_t ret;

	i_assert(fd >= 0);
	i_assert(len <= SSIZE_T_MAX);

	ret = send(fd, data, len, 0);
	if (ret == -1) {
		if (errno == EINTR || errno == EAGAIN)
			return 0;
		if (errno == EPIPE)
			return -2;
	}
        return ret;
}

int net_gethostbyname(const char *addr, struct ip_addr **ips,
		      unsigned int *ips_count)
{
	/* @UNSAFE */
#ifdef HAVE_IPV6
	union sockaddr_union *so;
	struct addrinfo hints, *ai, *origai;
	struct ip_addr ip;
	int host_error;
#else
	struct hostent *hp;
#endif
        int count;

	*ips = NULL;
        *ips_count = 0;

#ifdef HAVE_IPV6
	/* 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;
	}

	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;

        /* get number of IPs */
        origai = ai;
	for (count = 0; ai != NULL; ai = ai->ai_next)
                count++;

        *ips_count = count;
        *ips = t_malloc(sizeof(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);
#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(struct ip_addr) * count);

	while (count > 0) {
		count--;

		(*ips)[count].family = AF_INET;
		memcpy(&(*ips)[count].u.ip4, hp->h_addr_list[count],
		       sizeof((*ips)[count].u.ip4));
	}
#endif

	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;

	memset(&so, 0, sizeof(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);

	addrlen = sizeof(so);
	if (getsockname(fd, &so.sa, &addrlen) == -1)
		return -1;
	if (so.sin.sin_family == AF_UNIX) {
		if (addr != NULL)
			memset(addr, 0, sizeof(*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);

	addrlen = sizeof(so);
	if (getpeername(fd, &so.sa, &addrlen) == -1)
		return -1;
	if (so.sin.sin_family == AF_UNIX) {
		if (addr != NULL)
			memset(addr, 0, sizeof(*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);

	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 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;
	memset(&cdata.ch, 0, sizeof cdata.ch);

	memset(&msg, 0, sizeof 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)
{
#ifdef HAVE_IPV6
	char addr[MAX_IP_LEN+1];

	addr[MAX_IP_LEN] = '\0';
	if (inet_ntop(ip->family, &ip->u.ip6, addr, MAX_IP_LEN) == NULL)
		return "";

	return t_strdup(addr);
#else
	unsigned long ip4;

	if (ip->family != AF_INET)
		return "";

	ip4 = ntohl(ip->u.ip4.s_addr);
	return t_strdup_printf("%lu.%lu.%lu.%lu",
			       (ip4 & 0xff000000UL) >> 24,
			       (ip4 & 0x00ff0000) >> 16,
			       (ip4 & 0x0000ff00) >> 8,
			       (ip4 & 0x000000ff));
#endif
}

int net_addr2ip(const char *addr, struct ip_addr *ip)
{
	int ret;

	if (strchr(addr, ':') != NULL) {
		/* IPv6 */
#ifdef HAVE_IPV6
		T_BEGIN {
			if (addr[0] == '[') {
				/* allow [ipv6 addr] */
				unsigned int 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;
#else
		ip->u.ip4.s_addr = 0;
#endif
		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_ipv6_mapped_ipv4_convert(const struct ip_addr *src,
				 struct ip_addr *dest)
{
#ifdef HAVE_IPV6
	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;

	dest->family = AF_INET;
	memcpy(&dest->u.ip6, &src->u.ip6.s6_addr[3*4], 4);
	return 0;
#else
	return -1;
#endif
}

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)
{
#ifdef HAVE_IPV6
	i_assert(error != 0);

	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-recoverable name server error occurred";
	case TRY_AGAIN:
		return "A temporary error on an authoritative name server";
	default:
		return t_strdup_printf("Unknown error %d", error);
	}
#endif
}

int net_hosterror_notfound(int error)
{
#ifdef HAVE_IPV6
#ifdef EAI_NODATA /* NODATA is depricated */
	return error != 1 && (error == EAI_NONAME || error == EAI_NODATA);
#else
	return error != 1 && (error == EAI_NONAME);
#endif
#else
	return error == HOST_NOT_FOUND || error == NO_ADDRESS;
#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 {
#ifdef HAVE_IPV6
		ip1 = (const void *)&ip->u.ip6;
		ip2 = (const void *)&net_ip->u.ip6;
#else
		/* shouldn't get here */
		return FALSE;
#endif
	}

	/* 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;
}
