/* Distributed Checksum Clearinghouse
 *
 * convert a service name to a port number
 *
 * Copyright (c) 2005 by Rhyolite Software
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE
 * 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.2.74-1.55 $Revision$
 */

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


DCC_SOCKU dcc_hostaddrs[MAX_DCC_HOSTADDRS];
DCC_SOCKU *dcc_hostaddrs_end;


static u_char
copy_hp_to_hostaddrs(const struct hostent *hp,
		     u_char use_ipv6)
{
	DCC_SOCKU *sup;
	struct in6_addr addr6;
	struct in_addr addr;
	const void *v;
	int i;

	i = 0;
	sup = dcc_hostaddrs;
	while (i < DIM(dcc_hostaddrs)) {
		v = hp->h_addr_list[i];
		if (!v)
			break;

		if (hp->h_addrtype == AF_INET) {
			if (use_ipv6 == 1) {
				dcc_ipv4toipv6(&addr6, *(struct in_addr *)v);
				dcc_mk_su(sup, AF_INET6, &addr6, 0);

			} else {
				dcc_mk_su(sup, AF_INET, v, 0);
			}

		} else if (hp->h_addrtype == AF_INET6) {
			if (use_ipv6 == 1) {
				dcc_mk_su(sup, AF_INET6, v, 0);

			} else {
				if (!dcc_ipv6toipv4(&addr,
						    (struct in6_addr *)v))
					continue;
				dcc_mk_su(sup, AF_INET, &addr, 0);
			}

		} else {
			continue;
		}

		++i;
		++sup;
	}
	dcc_hostaddrs_end = sup;

	return i > 0;
}



#ifdef USE_GETADDRINFO
static u_char
copy_ai_to_hostaddrs(const struct addrinfo *ai,
		     u_char use_ipv6)
{
	DCC_SOCKU *sup;
	struct in6_addr addr6;
	struct in_addr addr;
	int i;

	i = 0;
	sup = dcc_hostaddrs;
	while (i < DIM(dcc_hostaddrs) && ai) {
		if (ai->ai_family == AF_INET) {
			if (use_ipv6 == 1) {
				dcc_ipv4toipv6(&addr6,
					       ((struct sockaddr_in *
						 )ai->ai_addr)->sin_addr);
				dcc_mk_su(sup, AF_INET6, &addr6, 0);

			} else {
				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 == 1) {
				dcc_mk_su(sup, AF_INET6,
					  &((struct sockaddr_in6 *
					    )ai->ai_addr)->sin6_addr, 0);

			} else {
				if (!dcc_ipv6toipv4(&addr,
						    &((struct sockaddr_in6 *
						       )ai->ai_addr)
						    ->sin6_addr))
					continue;
				dcc_mk_su(sup, AF_INET, &addr, 0);
			}

		} else {
			continue;
		}

		ai = ai->ai_next;
		++i;
		++sup;
	}
	dcc_hostaddrs_end = sup;

	return i > 0;
}
#endif



/* 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 lineno)
{
	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_lineno(fnm, lineno));
		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_lineno(fnm, lineno));
	return DCC_GET_PORT_INVALID;
}



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

#ifdef NO_IPV6
	if (use_ipv6 == 2)
		use_ipv6 = 0;
#endif
	if (!dcc_host_locked)
		dcc_logbad(EX_SOFTWARE, "dcc_get_host() not locked");


	/* 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) {
		if (use_ipv6) {
			dcc_hostaddrs[0].sa.sa_family = AF_INET6;
			dcc_ipv4toipv6(&dcc_hostaddrs[0].ipv6.sin6_addr, ipv4);
		} else {
			dcc_hostaddrs[0].sa.sa_family = AF_INET;
			dcc_hostaddrs[0].ipv4.sin_addr = ipv4;
		}
		dcc_hostaddrs_end = &dcc_hostaddrs[1];
		return 1;
	}

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

	return copy_hp_to_hostaddrs(hp, use_ipv6);
}



/* 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=IPv4, 1=require IPv6, 2=guess */
		   int *ep)		/* put errno or herrno here */
{
	/* since there is no Rgetaddrinfo() or equivalent,
	 * use the normal resolver if we need an IPv6 address */
	if (use_ipv6 == 1)
		return dcc_get_host(nm, use_ipv6, ep);
	return dcc_get_host_ipv4(nm, use_ipv6, ep, Rgethostbyname);
}



/* This must be protected with dcc_host_lock()and dcc_host_unlock().
 *	It does not assme that gethostbyname() or whatever is thread safe.
 */
u_char					/* 0=failed */
dcc_get_host(const char *nm,		/* look for this name */
	     u_char use_ipv6,		/* 0=IPv4, 1=require IPv6, 2=guess */
	     int *ep)			/* put errno or herrno here */
{
#ifdef NO_IPV6
	return dcc_get_host_ipv4(nm, use_ipv6, ep, gethostbyname);
#endif
#ifdef USE_GETIPNODEBYNAME
	static struct hostent *hp;
	u_char result;

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

	if (use_ipv6 == 2) {
		hp = getipnodebyname(nm, AF_INET6,
				     AI_V4MAPPED | AI_ADDRCONFIG, ep);
		if (hp) {
			result = copy_hp_to_hostaddrs(hp, 1);
			freehostent(hp);
			if (result)
				return 1;
		}
		use_ipv6 = 0;
	}
	if (use_ipv6)
		hp = getipnodebyname(nm, AF_INET6, AI_V4MAPPED|AI_ALL, ep);
	else
		hp = getipnodebyname(nm, AF_INET, 0, ep);
	if (!hp)
		return 0;
	result = copy_hp_to_hostaddrs(hp, use_ipv6);
	freehostent(hp);
	return result;
#endif
#ifdef USE_GETADDRINFO
	static struct addrinfo hints;
	struct addrinfo *ai;
	int error;
	u_char result;

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

	if (use_ipv6 == 2) {
		hints.ai_family = AF_INET6;
		if (!getaddrinfo(nm, 0, &hints, &ai)) {
			result = copy_ai_to_hostaddrs(ai, 1);
			freeaddrinfo(ai);
			if (result)
				return 1;
		}
		use_ipv6 = 0;
	}
	hints.ai_family = use_ipv6 ? 0 : AF_INET;
	error = getaddrinfo(nm, 0, &hints, &ai);
	if (error)
		return 0;
	*ep = error;
	result = copy_ai_to_hostaddrs(ai, use_ipv6);
	freeaddrinfo(ai);
	return result;
#endif
}



/* 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 DCC_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 DCC_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,
	     const char *str,
	     const char *fnm, int lineno)
{
	char addrstr[INET6_ADDRSTRLEN];
	DCC_SOCKU su;
	struct in6_addr mask6_loc;
	const char *mp;
	char *p;
	u_int str_len;
	int bits, wordno;

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

	if (!mp) {
		if (str_len >= ISZ(addrstr))
			return 0;	/* not an IP address */
		memcpy(addrstr, str, str_len);
		addrstr[str_len] = '\0';
	} else if (mp == str
		   || mp >= str+sizeof(addrstr)) {
		dcc_pemsg(EX_NOHOST, emsg,
			  "invalid IP address range \"%s\"%s",
			  str, fnm_lineno(fnm, lineno));
		return -1;
	} else {
		memcpy(addrstr, str, mp-str);
		addrstr[mp-str] = '\0';
	}
	if (!dcc_str2ip(&su, addrstr))
		return 0;		/* not an IP address */

	if (su.sa.sa_family == AF_INET6) {
		*addr6 = su.ipv6.sin6_addr;
		bits = mp ? strtoul(++mp, &p, 10) : 128;
	} else {
		dcc_ipv4toipv6(addr6, su.ipv4.sin_addr);
		bits = mp ? (strtoul(++mp, &p, 10) + 128-32) : 128;
	}
	if ((mp && *p != '\0' && p < str+str_len)
	    || bits > 128 || bits <= 0) {
		dcc_pemsg(EX_NOHOST, emsg,
			  "invalid netmask length \"%s\"%s",
			  str, fnm_lineno(fnm, lineno));
		return -1;
	}

	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 %s-bit CIDR boundary%s",
				  str, mp, fnm_lineno(fnm, lineno));
			return -1;
		}
	}

	return bits;
}



/* Create and bind a UDP socket.
 *	The client library uses this function to determine whether
 *	IPv6 works. */
u_char					/* 0=fatal error, 1=done */
dcc_udp_bind(DCC_EMSG emsg,
	     SOCKET *fdp,		/* INVALID_SOCKET or socket */
	     const DCC_SOCKU *sup,
	     int *retry_secs)		/* -1=try anonymous port */
{
	int tenths, i;
	DCC_SOCKU su;
#ifdef DCC_WIN32
	u_long on;
#endif


#ifdef NO_IPV6
	if (sup->sa.sa_family == AF_INET6) {
		*fdp = INVALID_SOCKET;
		return 1;
	}
#endif
	*fdp = socket(sup->sa.sa_family, SOCK_DGRAM, 0);
	if (*fdp == INVALID_SOCKET) {
		dcc_pemsg(EX_OSERR, emsg, "socket(UDP): %s", ERROR_STR());
#ifndef NO_IPV6
		/* let the caller try again if this system does not do IPv6 */
		if (sup->sa.sa_family == AF_INET6
		    && (errno == EPFNOSUPPORT
			|| errno == EPROTONOSUPPORT))
			return 1;
#endif
		return 0;
	}

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

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

		if (errno == EADDRINUSE
		    && *retry_secs < 0
		    && sup != &su) {
			su = *sup;
			*DCC_SU_PORT(&su) = 0;
			sup = &su;
			continue;
		}

		if (errno != EADDRINUSE
		    || !retry_secs || *retry_secs <= 0) {
#ifndef NO_IPV6
			if (sup->sa.sa_family == AF_INET6
			    && (errno == EPFNOSUPPORT
				|| errno == EPROTONOSUPPORT))
				i = 1;
			else
#endif
				i = 0;
			dcc_pemsg(EX_OSERR, emsg, "bind(UDP %s): %s",
				  dcc_su2str(sup), ERROR_STR());
			closesocket(*fdp);
			*fdp = INVALID_SOCKET;
			return i;
		}

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