view dcclib/get_port.c @ 0:c7f6b056b673

First import of vendor version
author Peter Gervai <grin@grin.hu>
date Tue, 10 Mar 2009 13:49:58 +0100
parents
children f6716cb00029
line wrap: on
line source

/* Distributed Checksum Clearinghouse
 *
 * convert a service name to a port number
 *
 * Copyright (c) 2008 by Rhyolite Software, LLC
 *
 * This agreement is not applicable to any entity which sells anti-spam
 * solutions to others or provides an anti-spam solution as part of a
 * security solution sold to other entities, or to a private network
 * which employs the DCC or uses data provided by operation of the DCC
 * but does not provide corresponding data to other users.
 *
 * Permission to use, copy, modify, and distribute this software without
 * changes for any purpose with or without fee is hereby granted, provided
 * that the above copyright notice and this permission notice appear in all
 * copies and any distributed versions or copies are either unchanged
 * or not called anything similar to "DCC" or "Distributed Checksum
 * Clearinghouse".
 *
 * Parties not eligible to receive a license under this agreement can
 * obtain a commercial license to use DCC by contacting Rhyolite Software
 * at sales@rhyolite.com.
 *
 * A commercial license would be for Distributed Checksum and Reputation
 * Clearinghouse software.  That software includes additional features.  This
 * free license for Distributed ChecksumClearinghouse Software does not in any
 * way grant permision to use Distributed Checksum and Reputation Clearinghouse
 * software
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC
 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 *
 * Rhyolite Software DCC 1.3.103-1.79 $Revision$
 */

#include "dcc_clnt.h"
#ifndef DCC_WIN32
#include <arpa/inet.h>			/* for AIX */
#endif


DCC_SOCKU dcc_hostaddrs[MAX_DCC_HOSTADDRS];
char dcc_host_canonname[DCC_MAXDOMAINLEN];
DCC_SOCKU *dcc_hostaddrs_end;


/* get port number
 *	Note that this function uses dcc_host_lock() and dcc_host_unlock() */
u_int					/* DCC_GET_PORT_INVALID or port # */
dcc_get_port(DCC_EMSG emsg,
	     const char *portname,
	     u_int def_port,		/* DCC_GET_PORT_INVALID or default */
	     const char *fnm, int lno)
{
	DCC_FNM_LNO_BUF fnm_buf;
	char *p;
	unsigned long l;
	struct servent *sp;
	u_int16_t port;


	if (portname[0] == '\0'
	    || !strcmp(portname, "-")) {
		if (def_port != DCC_GET_PORT_INVALID)
			return def_port;
		dcc_pemsg(EX_USAGE, emsg, "missing port%s",
			  fnm_lno(&fnm_buf, fnm, lno));
		return DCC_GET_PORT_INVALID;
	}

	/* first try a numeric port number, since that is common and
	 * the getservby* functions are so slow. */
	l = strtoul(portname, &p,0);
	if (*p == '\0' && l > 0 && l <= 65535)
		return htons((u_int16_t)l);

	dcc_host_lock();
	sp = getservbyname(portname, 0);
	if (sp) {
		port = sp->s_port;
		dcc_host_unlock();
		return port;
	}
	dcc_host_unlock();

	dcc_pemsg(EX_USAGE, emsg, "invalid port \"%s\"%s",
		  portname, fnm_lno(&fnm_buf, fnm, lno));
	return DCC_GET_PORT_INVALID;
}



static void
copy_hp_to_hostaddrs(const struct hostent *hp,
		     u_char family)
{
	DCC_SOCKU *sup, *sup1;
	int i;
	const void *v;

	if (hp->h_name && dcc_host_canonname[0] == '\0')
		BUFCPY(dcc_host_canonname, hp->h_name);

	sup = dcc_hostaddrs_end;
	for (i = 0;
	     (v = hp->h_addr_list[i]) != 0 && sup  <= LAST(dcc_hostaddrs);
	     ++i) {
		dcc_mk_su(sup, family, v, 0);
		/* deal with stuttering */
		sup1 = dcc_hostaddrs;
		for (;;) {
			if (sup1 >= sup) {
				++sup;
				break;
			}
			if (DCC_SU_EQ(sup1, sup))
				break;
			++sup1;
		}
	}
	dcc_hostaddrs_end = sup;
}



#ifdef USE_GETADDRINFO
static void
copy_ai_to_hostaddrs(const struct addrinfo *ai, u_char use_ipv6)
{
	DCC_SOCKU *sup, *sup1;

	if (ai->ai_canonname && dcc_host_canonname[0] == '\0')
		BUFCPY(dcc_host_canonname, ai->ai_canonname);

	for (sup = dcc_hostaddrs_end;
	     ai && sup  <= LAST(dcc_hostaddrs);
	     ai = ai->ai_next) {
		if (ai->ai_family == AF_INET) {
			if (use_ipv6 == 1)
				continue;
			dcc_mk_su(sup, AF_INET,
				  &((struct sockaddr_in *
				     )ai->ai_addr)->sin_addr, 0);
		} else if (ai->ai_family == AF_INET6) {
			if (use_ipv6 == 0)
				continue;
			dcc_mk_su(sup, AF_INET6,
				  &((struct sockaddr_in6 *
				     )ai->ai_addr)->sin6_addr, 0);
		} else {
			continue;
		}

		/* deal with stuttering */
		sup1 = dcc_hostaddrs;
		for (;;) {
			if (sup1 >= sup) {
				++sup;
				break;
			}
			if (DCC_SU_EQ(sup1, sup))
				break;
			++sup1;
		}
	}
	dcc_hostaddrs_end = sup;
}
#endif /* USE_GETADDRINFO */



/* Convert a host name to an IPv4 address by calling
 *	Rgethostbyname() or gethostbyname()
 * This must be protected with dcc_host_lock() and dcc_host_unlock(). */
static u_char
dcc_get_host_ipv4(const char *nm,	/* look for this name */
		  int *ep,		/* put errno or herrno here */
		  struct hostent *(WSAAPI fnc)(const char *))
{
	const struct hostent *hp;
	struct in_addr ipv4;

	if (!dcc_host_locked)
		dcc_logbad(EX_SOFTWARE, "dcc_get_host() not locked");

	dcc_host_canonname[0] = '\0';
	dcc_hostaddrs_end = &dcc_hostaddrs[0];

	/* First see if it is a number to avoid the MicroStupid stall
	 * when doing a gethostbyname() on a number */
	ipv4.s_addr = inet_addr(nm);
	if (ipv4.s_addr != INADDR_NONE) {
		dcc_mk_su(&dcc_hostaddrs[0], AF_INET, &ipv4, 0);
		dcc_hostaddrs_end = &dcc_hostaddrs[1];
		return 1;
	}

	hp = fnc(nm);
	if (!hp) {
		*ep = h_errno;
		return 0;
	}
	copy_hp_to_hostaddrs(hp, AF_INET);
	return 1;
}



/* This must be protected with dcc_host_lock()and dcc_host_unlock(). */
u_char					/* 0=failed */
dcc_get_host_SOCKS(const char *nm,	/* look for this name */
		   u_char use_ipv6,	/* 0=v4 1=v6 2=prefer v6 3=prefer v4 */
		   int *ep)		/* put errno or herrno here */
{
	int error1, error2;

	/* since there is no Rgetaddrinfo() or equivalent,
	 * use the normal resolver if we need an IPv6 address */
	switch (use_ipv6) {
	case 0:
		return dcc_get_host_ipv4(nm, ep, Rgethostbyname);
	case 1:
		return dcc_get_host(nm, 1, ep);
	case 2:
		if (dcc_get_host(nm, 1, &error1))
			return 1;
		return dcc_get_host_ipv4(nm, ep, Rgethostbyname);
	case 3:
	default:
		if (dcc_get_host_ipv4(nm, &error1, Rgethostbyname))
			return 1;
		if (dcc_get_host(nm, 1, &error2))
			return 1;
		*ep = error1;
		return 0;
	}
}



/* This must be protected with dcc_host_lock()and dcc_host_unlock().
 *	It does not assme that gethostbyname() or whatever is thread safe.
 *	This function is mentioned in dccifd/dccif-test/dccif-test.c
 *	and so cannot change lightly. */
u_char					/* 0=failed */
dcc_get_host(const char *nm,		/* look for this name */
	     u_char use_ipv6,		/* 0=v4 1=v6 2=prefer v6 3=prefer v4 */
	     int *ep)			/* put errno or herrno here */
{
#undef EXPANDED
#if defined(USE_GETIPNODEBYNAME) && !defined(EXPANDED) && !defined(NO_IPV6)
#define EXPANDED
	static struct hostent *hp;

	if (!dcc_host_locked)
		dcc_logbad(EX_SOFTWARE, "dcc_get_host() not locked");

	dcc_host_canonname[0] = '\0';
	dcc_hostaddrs_end = &dcc_hostaddrs[0];

	/* given a choice, return both IPv4 and IPv6 addresses */
	if (use_ipv6 == 0 || use_ipv6 == 3) {
		hp = getipnodebyname(nm, AF_INET, 0, ep);
		if (hp) {
			copy_hp_to_hostaddrs(hp, AF_INET);
			freehostent(hp);
		}
	}
	if (use_ipv6 != 0) {
		hp = getipnodebyname(nm, AF_INET6,0, ep);
		if (hp) {
			copy_hp_to_hostaddrs(hp, AF_INET6);
			freehostent(hp);
		}
	}
	if (use_ipv6 == 2) {
		hp = getipnodebyname(nm, AF_INET, 0, ep);
		if (hp) {
			copy_hp_to_hostaddrs(hp, AF_INET);
			freehostent(hp);
		}
	}
	if (dcc_hostaddrs_end != &dcc_hostaddrs[0])
		return 1;
	*ep = h_errno;
	return 0;
#endif
#if defined(USE_GETADDRINFO) && !defined(EXPANDED) && !defined(NO_IPV6)
#define EXPANDED
	static struct addrinfo hints;
	struct addrinfo *ai;
	int error = 0;

	if (!dcc_host_locked)
		dcc_logbad(EX_SOFTWARE, "dcc_get_host() not locked");

	dcc_host_canonname[0] = '\0';
	dcc_hostaddrs_end = &dcc_hostaddrs[0];

	hints.ai_flags = AI_CANONNAME;

	/* the FreeBSD version stutters trying to provide both UDP and
	 * TCP if you do not choose */
	hints.ai_protocol = IPPROTO_TCP;

	/* if you think AF_UNSPEC should provide IPv4 and IPv6, you evidently
	 * think wrong for at least the FreeBSD version, unless both flavors
	 * are in /etc/hosts */
	if (use_ipv6 == 0 || use_ipv6 == 3) {
		hints.ai_family = AF_INET;
		error = getaddrinfo(nm, 0, &hints, &ai);
		if (!error) {
			copy_ai_to_hostaddrs(ai, 0);
			freeaddrinfo(ai);
		}
	}
	if (use_ipv6 != 0) {
		hints.ai_family = AF_INET6;
		error = getaddrinfo(nm, 0, &hints, &ai);
		if (!error) {
			copy_ai_to_hostaddrs(ai, use_ipv6);
			freeaddrinfo(ai);
		}
	}
	if (use_ipv6 == 2) {
		hints.ai_family = AF_INET;
		error = getaddrinfo(nm, 0, &hints, &ai);
		if (!error) {
			copy_ai_to_hostaddrs(ai, 0);
			freeaddrinfo(ai);
		}
	}
	if (dcc_hostaddrs_end != &dcc_hostaddrs[0])
		return 1;
	*ep = error;
	return 0;
#endif
#ifndef EXPANDED
	/* this platform can only handle IPv4 */
	if (use_ipv6 == 1) {
		*ep = HOST_NOT_FOUND;
		return 0;
	}
	return dcc_get_host_ipv4(nm, ep, gethostbyname);
#endif
#undef EXPANDED
}



/* make socket address from an IP address, a family, and a port number */
DCC_SOCKU *
dcc_mk_su(DCC_SOCKU *su,		/* put it here */
	  int family,			/* AF_INET or AF_INET6 */
	  const void *addrp,		/* this IP address; 0=INADDR_ANY */
	  u_short port)
{
	memset(su, 0, sizeof(*su));	/* assume INADDR_ANY=0 */
	su->sa.sa_family = family;
	if (family == AF_INET) {
#ifdef HAVE_SA_LEN
		su->sa.sa_len = sizeof(struct sockaddr_in);
#endif
		su->ipv4.sin_port = port;
		if (addrp)
			memcpy(&su->ipv4.sin_addr, addrp,
			       sizeof(su->ipv4.sin_addr));
	} else {
#ifdef HAVE_SA_LEN
		su->sa.sa_len = sizeof(struct sockaddr_in6);
#endif
		su->ipv6.sin6_port = port;
		if (addrp)
			memcpy(&su->ipv6.sin6_addr, addrp,
			       sizeof(su->ipv6.sin6_addr));
	}

	return su;
}



/* strip leading and trailing white space */
static const char *
dcc_strip_white(const char *str, u_int *lenp)
{
	const char *end;
	char c;

	str += strspn(str, DCC_WHITESPACE);
	end = str+strlen(str);
	while (end > str) {
		c = *(end-1);
		if (c != ' ' && c != '\t' && c != '\r' && c != '\n')
			break;
		--end;
	}
	*lenp = end-str;
	return str;
}



/* get a socket address from a dotted quad or IPv6 string */
u_char
dcc_str2ip(DCC_SOCKU *su, const char *str)
{
#ifndef NO_IPV6
	u_int len;
	char buf[INET6_ADDRSTRLEN];
#endif

#ifdef HAVE_INET_ATON
	if (0 < inet_aton(str, &su->ipv4.sin_addr)) {
		su->sa.sa_family = AF_INET;
		return 1;
	}
#else
	u_int addr = inet_addr(str);
	if (su->ipv4.sin_addr.s_addr != INADDR_NONE) {
		su->ipv4.sin_addr.s_addr = addr;
		su->sa.sa_family = AF_INET;
		return 1;
	}
#endif

#ifndef NO_IPV6
	/* Try IPv6 only after failing to understand the address as IPv4.
	 *
	 * inet_pton() does not like blanks or terminal '\n'
	 * It is also too smart by half and assumes that it is
	 * given a pointer to struct sockaddr.  When it decodes
	 * an IPv4 address, it sticks it 4 bytes before the
	 * start of the IPv6 buffer it is given. */
	str = dcc_strip_white(str, &len);
	if (len == 0 || len >= sizeof(buf))
		return 0;
	memcpy(buf, str, len);
	buf[len] = '\0';
	if (0 < inet_pton(AF_INET6, buf, &su->ipv6.sin6_addr)) {
		su->sa.sa_family = AF_INET6;
		return 1;
	}
#endif
	return 0;
}



void
dcc_bits2mask(struct in6_addr *mask, int bits)
{
	int wordno, i;

	for (wordno = 0; wordno < 4; ++wordno) {
		i = bits - wordno*32;
		if (i >= 32) {
			mask->s6_addr32[wordno] = 0xffffffff;
			continue;
		}
		if (i <= 0) {
			mask->s6_addr32[wordno] = 0;
		} else {
			mask->s6_addr32[wordno] = 0xffffffff << (32-i);
		}
		mask->s6_addr32[wordno] = htonl(mask->s6_addr32[wordno]);
	}
}



/* get an IPv6 address and netmask */
int					/* # of bits, 0=not address, -1 error */
dcc_str2cidr(DCC_EMSG emsg,
	     struct in6_addr *addr6,
	     struct in6_addr *mask6,
	     u_char *is_ipv6,
	     const char *str,
	     const char *fnm, int lno)
{
	DCC_FNM_LNO_BUF fnm_buf;
	char addrstr[INET6_ADDRSTRLEN];
	DCC_SOCKU su;
	struct in6_addr mask6_loc;
	const char *bitsp;
	char *p;
	u_int str_len, addr_len, bits_len;
	int n, bits, wordno;

	str = dcc_strip_white(str, &str_len);
	bitsp = strchr(str, '/');

	if (!bitsp) {
		addr_len = str_len;
	} else {
		addr_len = bitsp - str;
	}
	if (addr_len == 0 || addr_len >= ISZ(addrstr))
		return 0;		/* not an IP address */
	memcpy(addrstr, str, addr_len);
	addrstr[addr_len] = '\0';
	if (!dcc_str2ip(&su, addrstr))
		return 0;		/* not an IP address */

	if (!bitsp) {
		if (su.sa.sa_family == AF_INET6) {
			bitsp = "128";
			bits_len = 3;
		} else {
			bitsp = "32";
			bits_len = 2;
		}
		bits = 128;
	} else {
		bits_len = str_len - addr_len - 1;
		n = strtoul(++bitsp, &p, 10);
		bits = n;
		if (su.sa.sa_family == AF_INET)
			bits += 128-32;
		if (p < bitsp+bits_len || bits > 128 || n == 0) {
			dcc_pemsg(EX_NOHOST, emsg,
				  "invalid CIDR block length \"%.*s\"%s",
				  str_len, str, fnm_lno(&fnm_buf, fnm, lno));
			return -1;
		}
	}

	if (su.sa.sa_family == AF_INET6) {
		*addr6 = su.ipv6.sin6_addr;
		if (is_ipv6)
			*is_ipv6 = 1;
	} else {
		dcc_ipv4toipv6(addr6, su.ipv4.sin_addr);
		if (is_ipv6)
			*is_ipv6 = 0;
	}

	if (!mask6)
		mask6 = &mask6_loc;
	dcc_bits2mask(mask6, bits);
	for (wordno = 0; wordno < 4; ++wordno) {
		if ((addr6->s6_addr32[wordno]
		     & ~mask6->s6_addr32[wordno]) != 0) {
			dcc_pemsg(EX_NOHOST, emsg,
				  "%s does not start on a"
				  " %.*s-bit CIDR boundary%s",
				  addrstr, bits_len, bitsp,
				  fnm_lno(&fnm_buf, fnm, lno));
			return -1;
		}
	}

	return bits;
}



/* Create and bind a UDP socket */
int					/* -1=possible IPv6 problem, 0=failed */
dcc_udp_bind(DCC_EMSG emsg,
	     SOCKET *socp,		/* INVALID_SOCKET or existing socket */
	     const DCC_SOCKU *sup,
	     int *retry_secsp)		/* -1=1 retry for anonymous port,
					 * 0=no retry, >0=seconds trying */
{
	DCC_SOCKU su;
#ifdef DCC_WIN32
	u_long on;
#endif
	int tenths, result;

	if (*socp == INVALID_SOCKET) {
#ifdef NO_IPV6
		if (sup->sa.sa_family == AF_INET6) {
			dcc_pemsg(EX_OSERR, emsg,
				  "attempt to create IPv6 UDP socket");
			return -1;
		}
#endif
		*socp = socket(sup->sa.sa_family, SOCK_DGRAM, 0);
		if (*socp == INVALID_SOCKET) {
			/* Let the caller try again if this system does not do
			 * IPv6 but IPv6 support has not been compiled out.
			 * It would be reasonable to use
			 * (errno == EPFNOSUPPORT || errno == EPROTONOSUPPORT)
			 * but some systems including Linux are unreasonable. */
			if (sup->sa.sa_family == AF_INET6) {
				dcc_pemsg(EX_OSERR, emsg,
					  "socket(UDP IPv6): %s", ERROR_STR());
				return -1;
			} else {
				dcc_pemsg(EX_OSERR, emsg,
					  "socket(UDP IPv4): %s", ERROR_STR());
				return 0;
			}
		}
	}

#ifdef DCC_WIN32
	on = 1;
	if (SOCKET_ERROR == ioctlsocket(*socp, FIONBIO, &on)) {
		dcc_pemsg(EX_OSERR, emsg, "ioctlsocket(UDP, FIONBIO): %s",
			  ERROR_STR());
		closesocket(*socp);
		*socp = INVALID_SOCKET;
		return 0;
	}
#else
	if (0 > fcntl(*socp, F_SETFD, FD_CLOEXEC)) {
		dcc_pemsg(EX_OSERR, emsg, "fcntl(UDP, FD_CLOEXEC): %s",
			  ERROR_STR());
		closesocket(*socp);
		*socp = INVALID_SOCKET;
		return 0;
	}
	if (-1 == fcntl(*socp, F_SETFL,
			fcntl(*socp, F_GETFL, 0) | O_NONBLOCK)) {
		dcc_pemsg(EX_OSERR, emsg, "fcntl(UDP O_NONBLOCK): %s",
			  ERROR_STR());
		closesocket(*socp);
		*socp = INVALID_SOCKET;
		return 0;
	}
#endif

	tenths = 10;
	for (;;) {
		if (SOCKET_ERROR != bind(*socp, &sup->sa, DCC_SU_LEN(sup)))
			return 1;

		if (errno == EADDRINUSE
		    && retry_secsp && *retry_secsp < 0
		    && sup != &su
		    && *DCC_SU_PORTP(sup) != 0) {
			/* If the initial number of seconds to retry was <0,
			 * then make one last try for a new ephemeral port */
			su = *sup;
			*DCC_SU_PORTP(&su) = 0;
			sup = &su;
			continue;
		}

		if (errno != EADDRINUSE
		    || !retry_secsp || *retry_secsp <= 0) {
			/* no retries were allowed or we have exhasuted them */
#ifndef NO_IPV6
			if (sup->sa.sa_family == AF_INET6
			    && (errno == EPFNOSUPPORT
				|| errno == EPROTONOSUPPORT))
				result = -1;
			else
#endif
				result = 0;
			dcc_pemsg(EX_OSERR, emsg, "bind(UDP %s): %s",
				  dcc_su2str_err(sup), ERROR_STR());
			closesocket(*socp);
			*socp = INVALID_SOCKET;
			return result;
		}

		usleep(100*1000);
		if (!--tenths) {
			--*retry_secsp;
			tenths = 10;
		}
	}
}