Mercurial > notdcc
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; + } + } +}