diff 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 diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dcclib/get_port.c	Tue Mar 10 13:49:58 2009 +0100
@@ -0,0 +1,673 @@
+/* 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;
+		}
+	}
+}