/* Distributed Checksum Clearinghouse * * convert a service name to a port number * * Copyright (c) 2006 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 for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * Parties not eligible to receive a license under this agreement can * obtain a commercial license to use DCC and permission to use * U.S. Patent 6,330,590 by contacting Commtouch at http://www.commtouch.com/ * or by email to nospam@commtouch.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.50-1.73 $Revision$ */ #include "dcc_clnt.h" #ifndef DCC_WIN32 #include /* for AIX */ #endif DCC_SOCKU dcc_hostaddrs[MAX_DCC_HOSTADDRS]; char dcc_host_canonname[MAXHOSTNAMELEN]; 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. */ 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, 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 *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_lno(fnm_buf, fnm, lno)); 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_lno(fnm_buf, fnm, lno)); 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_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 -11; } #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; } } }