net.c revision b5ab29780f74cf88212a547ebbe3b6bc0cb867c5
/* Copyright (c) 1999-2017 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>
#if defined(HAVE_UCRED_H)
# include <ucred.h> /* for getpeerucred() */
#elif defined(HAVE_SYS_UCRED_H)
#endif
union sockaddr_union {
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
};
union sockaddr_union_unix {
struct sockaddr_un un;
};
#if !defined(HAVE_GETPEEREID) && !defined(SO_PEERCRED) && !defined(HAVE_GETPEERUCRED) && defined(MSG_WAITALL) && defined(LOCAL_CREDS)
# define NEEDS_LOCAL_CREDS 1
#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
{
}
{
}
{
const unsigned char *p;
unsigned int len, g, h = 0;
} else
{
}
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
{
return;
}
else
}
static inline void
{
/* IP structs may be sent across processes. Clear the whole struct
first to make sure it won't leak any data across processes. */
else
else
}
{
else
}
{
return 0;
}
{
union sockaddr_union so;
i_warning("net_connect_ip(): ip->family != my_ip->family");
}
/* create the socket */
if (fd == -1) {
i_error("socket() failed: %m");
return -1;
}
/* set socket options */
if (sock_type == SOCK_STREAM)
if (!blocking)
/* set our own address */
i_close_fd(&fd);
return -1;
}
}
/* connect */
#ifndef WIN32
#else
#endif
{
i_close_fd(&fd);
return -1;
}
return fd;
}
bool blocking)
{
for (try = 0;;) {
(errno != EADDRNOTAVAIL
#ifdef __FreeBSD__
/* busy */
&& errno != EADDRINUSE
/* pf may cause this if another connection used
the same port recently */
#endif
))
break;
}
return fd;
}
{
}
{
}
{
}
{
union sockaddr_union so;
int fd;
/* create the socket */
if (fd == -1) {
i_error("socket() failed: %m");
return -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;
/* too long path */
#ifdef ENAMETOOLONG
#else
#endif
return -1;
}
/* create the socket */
if (fd == -1) {
return -1;
}
/* connect */
i_close_fd(&fd);
return -1;
}
#ifdef NEEDS_LOCAL_CREDS
{
int on = 1;
i_error("setsockopt(LOCAL_CREDS) failed: %m");
return -1;
}
}
#endif
return fd;
}
{
int fd;
i_panic("gettimeofday() failed: %m");
do {
break;
/* busy. wait for a while. */
i_panic("gettimeofday() failed: %m");
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. */
i_error("net_disconnect() failed: %m");
}
{
}
{
#ifdef TCP_CORK
#else
errno = ENOPROTOOPT;
return -1;
#endif
}
{
}
{
int opt;
return -1;
}
}
{
int opt;
return -1;
}
}
const struct ip_addr net_ip4_any = {
};
const struct ip_addr net_ip6_any = {
.u.ip6 = IN6ADDR_ANY_INIT
};
const struct ip_addr net_ip4_loopback = {
};
const struct ip_addr net_ip6_loopback = {
.u.ip6 = IN6ADDR_LOOPBACK_INIT
};
{
enum net_listen_flags flags = 0;
}
{
union sockaddr_union so;
/* create the socket */
/* IPv6 is not supported by OS */
}
if (fd == -1) {
i_error("socket() failed: %m");
return -1;
}
/* set socket options */
if ((*flags & NET_LISTEN_FLAG_REUSEPORT) != 0) {
#ifdef SO_REUSEPORT
#endif
}
/* If using IPv6, bind only to IPv6 if possible. This avoids
ambiguities with IPv4-mapped IPv6 addresses. */
#ifdef IPV6_V6ONLY
opt = 1;
}
#endif
if (ret < 0) {
if (errno != EADDRINUSE) {
i_error("bind(%s, %u) failed: %m",
}
} else {
/* get the actual port we started listen */
if (ret >= 0) {
/* start listening */
return fd;
if (errno != EADDRINUSE)
i_error("listen() failed: %m");
}
}
/* error */
i_close_fd(&fd);
return -1;
}
{
union {
struct sockaddr_un un;
} sa;
int fd;
/* too long path */
return -1;
}
/* create the socket */
if (fd == -1) {
i_error("socket() failed: %m");
return -1;
}
#ifdef NEEDS_LOCAL_CREDS
{
int on = 1;
i_error("setsockopt(LOCAL_CREDS) failed: %m");
return -1;
}
}
#endif
/* bind */
if (errno != EADDRINUSE)
} else {
/* start listening */
return fd;
if (errno != EADDRINUSE)
i_error("listen() failed: %m");
}
i_close_fd(&fd);
return -1;
}
{
unsigned int i = 0;
int fd;
return -1;
/* see if it really exists */
errno = EADDRINUSE;
return -1;
}
/* delete and try again */
if (i_unlink_if_exists(path) < 0) {
errno = EADDRINUSE;
return -1;
}
}
return fd;
}
{
union sockaddr_union so;
int ret;
if (ret < 0) {
return -1;
else
return -2;
}
} else {
}
return ret;
}
{
if (ret == 0) {
/* disconnected */
errno = 0;
return -2;
}
return 0;
/* treat as disconnection */
return -2;
}
}
return ret;
}
{
if (ret == -1) {
return 0;
return -2;
}
return ret;
}
unsigned int *ips_count)
{
/* @UNSAFE */
union sockaddr_union *so;
int host_error;
int count;
*ips_count = 0;
/* support [ipv6] style addresses here so they work globally */
*ips_count = 1;
return 0;
}
/* save error to host_error for later use */
if (host_error != 0)
return host_error;
/* get number of IPs */
count++;
count = 0;
}
return 0;
}
{
union sockaddr_union so;
char hbuf[NI_MAXHOST];
int ret;
if (ret != 0)
return ret;
return 0;
}
{
union sockaddr_union so;
return -1;
} else {
}
return 0;
}
{
union sockaddr_union so;
return -1;
} else {
}
return 0;
}
{
union sockaddr_union_unix so;
return -1;
return -1;
}
return 0;
}
{
#if defined(SO_PEERCRED)
# if defined(HAVE_STRUCT_SOCKPEERCRED)
/* OpenBSD (may also provide getpeereid, but we also want pid) */
struct sockpeercred ucred;
# else
/* Linux */
# endif
i_error("getsockopt(SO_PEERCRED) failed: %m");
return -1;
}
return 0;
#elif defined(LOCAL_PEEREID)
/* NetBSD (may also provide getpeereid, but we also want pid) */
i_error("getsockopt(LOCAL_PEEREID) failed: %m");
return -1;
}
return 0;
#elif defined(HAVE_GETPEEREID)
/* OSX 10.4+, FreeBSD 4.6+, OpenBSD 3.0+, NetBSD 5.0+ */
i_error("getpeereid() failed: %m");
return -1;
}
return 0;
#elif defined(LOCAL_PEERCRED)
/* Older FreeBSD */
i_error("getsockopt(LOCAL_PEERCRED) failed: %m");
return -1;
}
return -1;
}
return 0;
#elif defined(HAVE_GETPEERUCRED)
/* Solaris */
i_error("getpeerucred() failed: %m");
return -1;
}
return -1;
}
return 0;
/* NetBSD < 5 */
int i, n, on;
struct {
char buf[110];
} cdata;
for (i = 0; i < 10; i++) {
break;
usleep(100);
}
if (n < 0) {
i_error("recvmsg() failed: %m");
return -1;
}
return 0;
#else
return -1;
#endif
}
{
return "";
}
{
int ret;
/* IPv6 */
T_BEGIN {
if (addr[0] == '[') {
/* allow [ipv6 addr] */
}
} T_END;
if (ret == 0)
return -1;
} else {
/* IPv4 */
return -1;
}
return 0;
}
{
uintmax_t l;
if (str_to_uintmax(str, &l) < 0)
return -1;
if (l == 0 || l > (in_port_t)-1)
return -1;
return 0;
}
{
uintmax_t l;
if (str_to_uintmax(str, &l) < 0)
return -1;
if (l > (in_port_t)-1)
return -1;
return 0;
}
{
const char *p, *host;
if (str[0] == '[') {
/* [IPv6] address, possibly followed by :port */
if (p == NULL)
return -1;
} else {
/* host or IPv6 address */
*port_r = default_port;
return 0;
}
}
if (p[0] == '\0') {
*port_r = default_port;
return 0;
}
if (p[0] != ':')
return -1;
return -1;
return 0;
}
{
port);
return 0;
}
{
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff };
if (!IPADDR_IS_V6(src))
return -1;
return -1;
return 0;
}
int net_geterror(int fd)
{
int data;
/* 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)
{
return gai_strerror(error);
}
int net_hosterror_notfound(int error)
{
#ifdef EAI_NODATA /* NODATA is depricated */
#else
#endif
}
{
}
bool is_ipv4_address(const char *addr)
{
while (*addr != '\0') {
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') {
break;
return FALSE;
}
addr++;
}
return TRUE;
}
unsigned int *bits_r)
{
const char *p;
if (p != NULL)
return -1;
if (p == NULL) {
/* full IP address must match */
} else {
/* get the network mask */
return -1;
}
return 0;
}
{
unsigned int pos, i;
/* IPv4 address mapped disguised as IPv6 address */
}
anything */
return FALSE;
}
/* one is IPv6 and one is IPv4 */
return FALSE;
}
if (IPADDR_IS_V4(ip)) {
} else {
}
/* check first the full 32bit ints */
return FALSE;
}
/* check the last full bytes */
return FALSE;
}
/* check the last bits, they're reversed in bytes */
return FALSE;
}
return TRUE;
}