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