/* * Copyright (c) 1997, 1998, 1999, 2000, 2001, 2008, 2009, 2010, 2011, 2012, * 2013, 2016, 2017, 2019, 2020 * Inferno Nettverk A/S, Norway. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. The above copyright notice, this list of conditions and the following * disclaimer must appear in all copies of the software, derivative works * or modified versions, and any portions thereof, aswell as in all * supporting documentation. * 2. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by * Inferno Nettverk A/S, Norway. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Inferno Nettverk A/S requests users of this software to return to * * Software Distribution Coordinator or sdc@inet.no * Inferno Nettverk A/S * Oslo Research Park * Gaustadalléen 21 * NO-0349 Oslo * Norway * * any improvements or extensions that they make and grant Inferno Nettverk A/S * the rights to redistribute these changes. * */ #define DISABLE_GETHOSTBYNAME_CACHE (1) #include "common.h" static const char rcsid[] = "$Id: hostcache.c,v 1.172.4.9.2.4.4.3 2020/11/11 16:11:54 karls Exp $"; #if 0 #warning "XXX change back to LOG_DEBUG" #define LOG_DEBUG LOG_INFO #endif #if !SOCKS_CLIENT #define MIN_HASH_SIZE (10240) /* * Assume this is so small a cache that it's better to just scan it * sequentially rather than expire DNS-entries that we could otherwise * have continued to keep in the cache. */ static size_t hosthash(const char *name, const size_t size); /* * Calculates a hash value for "name" and returns it's value. * Size of hash table is given by "size". */ static hostentry_t * hostentcopy(hostentry_t *to, const struct hostent *from); /* * Copies all the values in "from" into "to", or as much as there is room * for. * * The only reason this function may fail is if "from" is too big, i.e. * has names that are too long (according to the dns spec) or similar. * * Note that the caller must set to->h_name or to->ipv4 when creating the * original entry based on the result of a gethostby*() call. This is because * the result returned by the gethostby*() call may not be the address * used when gethostby*() was called, but the latter is what we want to use * in our cache. * * Returns "to" on success, NULL on failure. */ static size_t addrhash(const struct sockaddr_storage *addr, const size_t size); /* * Calculates a hash value for the IP-address "addr" and returns it's value. * Size of hash table is given by "size". */ static int addrisinlist(const struct sockaddr *addr, struct addrinfo *ailist, struct addrinfo *ailist_last); /* * "ailist_last" is the last member in the "ailist" that is valid. * * Returns true if the address "addr" is in the list "ailist". * Returns false otherwise. */ dnsinfo_t * getoldest( const size_t starti); /* * Returns the oldest entry at index "starti" or later. */ static int name_matches_cache(const dnsinfo_t *cacheentry, const char *name, const char *service, const struct addrinfo *hints); static int addr_matches_cache(const dnsinfo_t *cacheentry, const struct sockaddr *addr, const int flags); /* * If the passed arguments match the cache entry "cacheentry", * the functions returns true. * Otherwise they returns false. */ #if STANDALONE_UNIT_TEST size_t cbyname_hit, cbyname_miss; size_t cbyaddr_hit, cbyaddr_miss; #endif /* !STANDALONE_UNIT_TEST */ /* * hostname/ipaddress cache. Shared among all processes. */ UNIT_TEST_STATIC_SCOPE dnsinfo_t *hostcache; static int gai2h_errno(const int gai_rc); /* * Returns the h_errno version of the getaddrinfo/getnameinfo error "gai_rc". */ #undef cgetaddrinfo #endif /* !SOCKS_CLIENT */ UNIT_TEST_STATIC_SCOPE int addrinfocopy(dnsinfo_t *to, const struct addrinfo *from, const struct addrinfo *hints); /* * Copies all the values in "from" into "to", or as much as there is room * for. "hints" is taken into consideration concerning what to copy. * * The only reason this function may fail is if "from" is too big, i.e. * has names that are too long (according to the dns spec) or similar, * *OR* if "hints" precludes some entries from being copied. * * Returns an errocode of the same type as getaddrinfo(3). */ #if SOCKSLIBRARY_DYNAMIC #define gethostbyaddr(addr, len, type) sys_gethostbyaddr(addr, len, type) #if SOCKS_CLIENT #undef gethostbyname #define gethostbyname(name) sys_gethostbyname(name) #endif /* SOCKS_CLIENT */ #endif /* SOCKSLIBRARY_DYNAMIC */ #define GAI_RC_IS_SYSTEMPROBLEM(gai_rc) \ ((gai_rc) == EAI_MEMORY \ || (gai_rc) == EAI_SYSTEM) #define GAI_RC_IS_EAGAIN(gai_rc) \ ((gai_rc) == EAI_AGAIN) #if HAVE_ERR_EAI_OVERFLOW #define GAI_RC_IS_INTERNALERROR(gai_rc) \ ( ((gai_rc) == EAI_BADFLAGS) \ || ((gai_rc) == EAI_FAMILY) \ || ((gai_rc) == EAI_SOCKTYPE) \ || ((gai_rc) == EAI_OVERFLOW)) #else #define GAI_RC_IS_INTERNALERROR(gai_rc) \ ( ((gai_rc) == EAI_BADFLAGS) \ || ((gai_rc) == EAI_FAMILY) \ || ((gai_rc) == EAI_SOCKTYPE)) #endif /* HAVE_ERR_EAI_OVERFLOW */ int cgetaddrinfo(name, service, hints, res, resmem) const char *name; const char *service; const struct addrinfo *hints; struct addrinfo **res; dnsinfo_t *resmem; { const char *function = "cgetaddrinfo()"; char namebuf[MAXHOSTNAMELEN * 4], servicebuf[MAXSERVICELEN * 4]; int gai_rc; #if SOCKS_CLIENT /* * No cache for client. Just a slightly different interface, mainly * to limit differences in code called by both client and server, as * the server caches the result of getaddrinfo(3). */ SASSERTX(res != NULL); SASSERTX(resmem != NULL); if ((gai_rc = getaddrinfo(name, service, hints, res)) != 0) return gai_rc; SASSERTX(*res != NULL); /* * have resmem. Copy into that and don't use the one returned from * getaddrinfo(3). */ gai_rc = addrinfocopy(resmem, *res, hints); freeaddrinfo(*res); if (gai_rc != 0) { if (GAI_RC_IS_SYSTEMPROBLEM(gai_rc)) swarnx("%s: addrinfocopy() failed for hostname \"%s\", service \"%s\"", function, str2vis(name, strlen(name), namebuf, sizeof(namebuf)), service == NULL ? "" : str2vis(service, strlen(service), servicebuf, sizeof(servicebuf))); return gai_rc; } *res = &resmem->data.getaddr.addrinfo; return gai_rc; #else /* !SOCKS_CLIENT */ static size_t i; static int count; const time_t timenow = time_monotonic(NULL); #if !STANDALONE_UNIT_TEST static size_t cbyname_hit, cbyname_miss; #endif /* !STANDALONE_UNIT_TEST */ dnsinfo_t *freehost; size_t hashi; int have_oldres = 0; SASSERTX(res != NULL); SASSERTX(resmem != NULL); if (count++ % SOCKD_CACHESTAT == 0) slog(LOG_DEBUG, "%s, hit: %lu, miss: %lu", function, (unsigned long)cbyname_hit, (unsigned long)cbyname_miss); if (strlen(name) >= sizeof(freehost->id.name)) { swarnx("%s: hostname \"%s\" is too long. Max length is %lu", function, str2vis(name, strlen(name), namebuf, sizeof(namebuf)), (unsigned long)sizeof(freehost->id.name) - 1); return EAI_MEMORY; } if (service != NULL && strlen(service) >= sizeof(freehost->service)) { swarn("max length of servicename was at compiletime set to %lu, but " "now being called with servicename of length %lu. Recompile " "required to support a servicename this long. " "Servicename: \"%s\"", (unsigned long)sizeof(freehost->service) - 1, (unsigned long)strlen(service) + 1, str2vis(service, strlen(service), servicebuf, sizeof(servicebuf))); return EAI_MEMORY; } socks_lock(sockscf.hostfd, 0, 0, 0, 1); /* * First check if the desired name is the same as last time. */ if (i < SOCKD_HOSTCACHE && name_matches_cache(&hostcache[i], name, service, hints)) { if (socks_difftime(timenow, hostcache[i].written) < SOCKD_CACHETIMEOUT) { ++cbyname_hit; if (hostcache[i].gai_rc == 0) { gai_rc = addrinfocopy(resmem, &hostcache[i].data.getaddr.addrinfo, hints); SASSERTX(gai_rc == 0); *res = &resmem->data.getaddr.addrinfo; } socks_unlock(sockscf.hostfd, 0, 0); return hostcache[i].gai_rc; } /* * Else; in cache, but expired already. */ freehost = &hostcache[i]; hashi = hosthash(name, SOCKD_HOSTCACHE); if (freehost->gai_rc == 0) { gai_rc = addrinfocopy(resmem, &freehost->data.getaddr.addrinfo, hints); SASSERTX(gai_rc == 0); have_oldres = 1; } } else { /* * Go through the entire in the cache, looking for a match. */ hashi = hosthash(name, SOCKD_HOSTCACHE); for (i = hashi, freehost = NULL; i < SOCKD_HOSTCACHE; ++i) { if (!hostcache[i].allocated) { if (freehost == NULL) freehost = &hostcache[i]; continue; } if (name_matches_cache(&hostcache[i], name, service, hints)) { if (socks_difftime(timenow, hostcache[i].written) >= SOCKD_CACHETIMEOUT) { freehost = &hostcache[i]; if (freehost->gai_rc == 0) { gai_rc = addrinfocopy(resmem, &freehost->data.getaddr.addrinfo, hints); SASSERTX(gai_rc == 0); have_oldres = 1; } break; } ++cbyname_hit; if (hostcache[i].gai_rc != 0) { socks_unlock(sockscf.hostfd, 0, 0); return hostcache[i].gai_rc; } gai_rc = addrinfocopy(resmem, &hostcache[i].data.getaddr.addrinfo, hints); SASSERTX(gai_rc == 0); socks_unlock(sockscf.hostfd, 0, 0); *res = &resmem->data.getaddr.addrinfo; return 0; } } } /* * Nope, this name is not in the cache. Have to resolve. */ socks_unlock(sockscf.hostfd, 0, 0); ++cbyname_miss; gai_rc = getaddrinfo(name, service, hints, res); slog(LOG_DEBUG, "%s: getaddrinfo(%s, %s, { %s }) returned %d and %p: %s", function, str2vis(name, strlen(name), namebuf, sizeof(namebuf)), service == NULL ? "" : str2vis(service, strlen(service), servicebuf, sizeof(servicebuf)), hints == NULL ? "" : aihints2string(hints, NULL, 0), gai_rc, gai_rc == 0 ? *res : NULL, gai_rc == 0 ? "no error" : gai_strerror(gai_rc)); if (gai_rc == EAI_SYSTEM && errno == EMFILE) { if (sockscf.state.reservedfdv[0] != -1) { close(sockscf.state.reservedfdv[0]); gai_rc = getaddrinfo(name, service, hints, res); sockscf.state.reservedfdv[0] = makedummyfd(0, 0); if (gai_rc != 0) slog(LOG_DEBUG, "%s: getaddrinfo(%s, %s) failed again: %s", function, str2vis(name, strlen(name), namebuf, sizeof(namebuf)), service == NULL ? "" : str2vis(service, strlen(service), servicebuf, sizeof(servicebuf)), gai_strerror(gai_rc)); } } #if HAVE_LINUX_BUGS /* * glibc calls connect(2) to something that fails from __GI_getaddrinfo(), * and then does not clear the errno, so even though the above getaddrinfo() * call succeeded, errno is now set, at least on the glibc used by * 3.7.3-101.fc17.x86_64. */ if (gai_rc == 0) errno = 0; #endif /* HAVE_LINUX_BUGS */ if (gai_rc != 0) { /* * check if the problem is ours, or the DNS's. */ if (GAI_RC_IS_SYSTEMPROBLEM(gai_rc)) { swarn("%s: getaddrinfo(%s, %s) failed again: %s", function, str2vis(name, strlen(name), namebuf, sizeof(namebuf)), service == NULL ? "" : str2vis(service, strlen(service), servicebuf, sizeof(servicebuf)), gai_strerror(gai_rc)); } if (have_oldres) { slog(LOG_DEBUG, "%s: resolve of %s failed. Returning stale cache entry", function, str2vis(name, strlen(name), namebuf, sizeof(namebuf))), *res = &resmem->data.getaddr.addrinfo; return 0; } if (GAI_RC_IS_SYSTEMPROBLEM(gai_rc)) return gai_rc; else if (GAI_RC_IS_EAGAIN(gai_rc)) return gai_rc; else if (GAI_RC_IS_INTERNALERROR(gai_rc)) SERR(gai_rc); /* else; assume problem is not ours. */ } /* * Either things resolved ok, or the reason for failure is not our own * (i.e., not lack of memory, free fds, etc.). */ if (gai_rc == 0) SASSERTX(*res != NULL); else { /* * else; assume host does not exist at the moment and cache that result. */ *res = NULL; res = NULL; } socks_lock(sockscf.hostfd, 0, 0, 1, 1); if (freehost == NULL) freehost = getoldest(hashi); /* * else: contents pointed to can have changed, in which case we may now * be overwriting one of the most recent entries, but never mind that; * that unlikely case is not important enough to warrant locking and * scanning for the oldest entry now. */ SASSERTX(freehost != NULL); if (gai_rc == 0) { gai_rc = addrinfocopy(freehost, *res, hints); /* * have resmem. Don't use the mem returned from getaddrinfo(3). */ freeaddrinfo(*res); if (gai_rc != 0) { if (GAI_RC_IS_SYSTEMPROBLEM(gai_rc)) swarnx("%s: addrinfocopy() failed for \"%s\"/service \"%s\": %s", function, str2vis(name, strlen(name), namebuf, sizeof(namebuf)), service == NULL ? "" : str2vis(service, strlen(service), servicebuf, sizeof(servicebuf)), gai_strerror(gai_rc)); socks_unlock(sockscf.hostfd, 0, 0); return gai_rc; } } if (hints == NULL) freehost->data.getaddr.hints = NULL; else { freehost->data.getaddr.hints = &freehost->data.getaddr.hints_mem; *freehost->data.getaddr.hints = *hints; } freehost->key = id_name; STRCPY_ASSERTLEN(freehost->id.name, name); if (service == NULL) *freehost->service = NUL; else STRCPY_ASSERTLEN(freehost->service, service); freehost->written = timenow; freehost->gai_rc = gai_rc; freehost->allocated = 1; SASSERTX(freehost->key == id_name); if (gai_rc == 0) { gai_rc = addrinfocopy(resmem, &freehost->data.getaddr.addrinfo, hints); SASSERTX(gai_rc == 0); } socks_unlock(sockscf.hostfd, 0, 0); resmem->gai_rc = gai_rc; if (resmem->gai_rc == 0) *res = &resmem->data.getaddr.addrinfo; return resmem->gai_rc; #endif /* !SOCKS_CLIENT */ } #if !SOCKS_CLIENT int cgetnameinfo(addr, addrlen, _host, _hostlen, _service, _servicelen, flags) const struct sockaddr *addr; const socklen_t addrlen; char *_host; const socklen_t _hostlen; char *_service; const socklen_t _servicelen; const int flags; { const char *function = "cgetnameinfo()"; const time_t timenow = time_monotonic(NULL); #if !STANDALONE_UNIT_TEST static size_t cbyaddr_hit, cbyaddr_miss; #endif /* !STANDALONE_UNIT_TEST */ static size_t i; static int count; dnsinfo_t *freehost; size_t hashi; char host[sizeof(freehost->data.getname.name)], service[sizeof(freehost->service)]; int gai_rc, have_oldres = 0; #define DOCOPY(_host, _hostlen, _service, _servicelen, cacheentry) \ do { \ if ((_hostlen) > 0) { \ strncpy((_host), (cacheentry)->data.getname.name, (_hostlen) - 1); \ (_host)[(_hostlen) - 1] = NUL; \ } \ \ if ((_servicelen) > 0) { \ strncpy((_service), (cacheentry)->service, (_servicelen) - 1); \ (_service)[(_servicelen) - 1] = NUL; \ } \ } while (/* CONSTCOND */ 0) if (count++ % SOCKD_CACHESTAT == 0) { slog(LOG_DEBUG, "%s: hit: %lu, miss: %lu", function, (unsigned long)cbyaddr_hit, (unsigned long)cbyaddr_miss); } socks_lock(sockscf.hostfd, 0, 0, 0, 1); /* * First check if the desired name is the same as last time. */ if (i < SOCKD_HOSTCACHE && addr_matches_cache(&hostcache[i], addr, flags)) { if (socks_difftime(timenow, hostcache[i].written) < SOCKD_CACHETIMEOUT) { ++cbyaddr_hit; if (hostcache[i].gai_rc == 0) DOCOPY(_host, _hostlen, _service, _servicelen, &hostcache[i]); socks_unlock(sockscf.hostfd, 0, 0); return hostcache[i].gai_rc; } /* * Else; have an entry, but it's expired already. */ freehost = &hostcache[i]; hashi = addrhash(TOCSS(addr), SOCKD_HOSTCACHE); if (freehost->gai_rc == 0) { DOCOPY(_host, _hostlen, _service, _servicelen, freehost); have_oldres = 1; } } else { /* * Go through the cache and see if there is match there. */ hashi = addrhash(TOCSS(addr), SOCKD_HOSTCACHE); for (i = hashi, freehost = NULL; i < SOCKD_HOSTCACHE; ++i) { if (!hostcache[i].allocated) { if (freehost == NULL) freehost = &hostcache[i]; continue; } if (addr_matches_cache(&hostcache[i], addr, flags)) { if (socks_difftime(timenow, hostcache[i].written) >= SOCKD_CACHETIMEOUT) { freehost = &hostcache[i]; if (freehost->gai_rc == 0) { DOCOPY(_host, _hostlen, _service, _servicelen, freehost); have_oldres = 1; } break; } ++cbyaddr_hit; if (hostcache[i].gai_rc != 0) { socks_unlock(sockscf.hostfd, 0, 0); return hostcache[i].gai_rc; } DOCOPY(_host, _hostlen, _service, _servicelen, &hostcache[i]); socks_unlock(sockscf.hostfd, 0, 0); return 0; } } } ++cbyaddr_miss; socks_unlock(sockscf.hostfd, 0, 0); #if SOCKSLIBRARY_DYNAMIC /* * In case getnameinfo(3) resolves to some other libresolv call * which we are also interpositioning. E.g. on FreeBSD 9.1 it's * getnameinfo(3) -> getipnodebyaddr(3) -> gethostbyaddr(3). * We don't want that; when we call getnameinfo(3), we don't want * that to resolve to something possibly interpositioned by us. */ socks_markasnative("*"); #endif /* SOCKSLIBRARY_DYNAMIC */ gai_rc = getnameinfo(addr, addrlen, host, sizeof(host), service, sizeof(service), flags); #if SOCKSLIBRARY_DYNAMIC socks_markasnormal("*"); #endif /* SOCKSLIBRARY_DYNAMIC */ slog(LOG_DEBUG, "%s: getnameinfo(%s) returned %d: %s", function, sockaddr2string(TOCSS(addr), NULL, 0), gai_rc, gai_strerror(gai_rc)); if (gai_rc != 0 && GAI_RC_IS_SYSTEMPROBLEM(gai_rc)) { if (sockscf.state.reservedfdv[0] != -1) { close(sockscf.state.reservedfdv[0]); gai_rc = getnameinfo(addr, addrlen, host, sizeof(host), service, sizeof(service), flags); sockscf.state.reservedfdv[0] = makedummyfd(0, 0); if (gai_rc != 0) { slog(LOG_DEBUG, "%s: getnameinfo(%s) failed again: %s", function, sockaddr2string(TOCSS(addr), NULL, 0), gai_strerror(gai_rc)); } } } if (gai_rc != 0) { if (have_oldres) { slog(LOG_DEBUG, "%s: resolve of %s failed. Returning stale cache entry", function, sockaddr2string(TOCSS(addr), NULL, 0)); /* should be copied into _host and _service already. */ return 0; } if (GAI_RC_IS_SYSTEMPROBLEM(gai_rc)) { swarn("%s: getnameinfo(%s) failed: %s", function, sockaddr2string(TOCSS(addr), NULL, 0), gai_strerror(gai_rc)); return gai_rc; } else if (GAI_RC_IS_EAGAIN(gai_rc)) return gai_rc; else if (GAI_RC_IS_INTERNALERROR(gai_rc)) SERR(gai_rc); } socks_lock(sockscf.hostfd, 0, 0, 1, -1); if (freehost == NULL) freehost = getoldest(hashi); SASSERTX(freehost != NULL); freehost->gai_rc = gai_rc; STRCPY_ASSERTSIZE(freehost->service, service); STRCPY_ASSERTSIZE(freehost->data.getname.name, host); freehost->data.getname.flags = flags; freehost->written = timenow; freehost->key = id_addr; memcpy(&freehost->id.addr, addr, MIN(sizeof(freehost->id.addr), addrlen)); freehost->allocated = 1; socks_unlock(sockscf.hostfd, 0, 0); if (freehost->gai_rc == 0) DOCOPY(_host, _hostlen, _service, _servicelen, freehost); return freehost->gai_rc; } #endif /* !SOCKS_CLIENT */ int addrinfo_issupported(ai) const struct addrinfo *ai; { if (!safamily_issupported(ai->ai_family)) return 0; switch (ai->ai_socktype) { case 0: /* Solaris. :-(. */ case SOCK_STREAM: case SOCK_DGRAM: break; default: return 0; } switch (ai->ai_protocol) { case 0: /* Solaris. :-(. */ case IPPROTO_TCP: case IPPROTO_UDP: break; default: return 0; } return 1; } UNIT_TEST_STATIC_SCOPE int addrinfocopy(to, from, hints) dnsinfo_t *to; const struct addrinfo *from; const struct addrinfo *hints; { const char *function = "addrinfocopy()"; const struct addrinfo *from_ai; const size_t maxentries = ELEMENTS(to->data.getaddr.ai_addr_mem); struct addrinfo *to_ai, *to_ai_previous; #if !SOCKS_CLIENT struct addrinfo *to_ai_start; #endif /* !SOCKS_CLIENT */ size_t i; char visbuf[MAXHOSTNAMELEN * 4]; bzero(to, sizeof(*to)); from_ai = from; to_ai = &to->data.getaddr.addrinfo; #if !SOCKS_CLIENT to_ai_start = to_ai; #endif /* !SOCKS_CLIENT */ to_ai_previous = to_ai; i = 0; while (i < maxentries && from_ai != NULL) { #if !SOCKS_CLIENT int doskip = 0; if (!addrinfo_issupported(from_ai)) doskip = 1; else if (i > 0 && ntohs(GET_SOCKADDRPORT(TOSS(from_ai->ai_addr))) == 0 && ntohs(GET_SOCKADDRPORT(TOSS(to_ai_previous->ai_addr))) == 0 && addrisinlist(from_ai->ai_addr, to_ai_start, to_ai_previous)) { /* * same address as before, just different protocol. We don't * care about protocol and don't want to waste memory on that. * * We compare against 0 since a portnumber of 0 should mean * the caller doesn't care either (though this will still not * make things work in the generic case, where the caller may * be looking for an entry with the appropriate protocol), * and makes it easy to set a portnumber (different from 0) * when used in a particular unit-test. Apart from that, it * would be more correct to ignore the portnumber of course. */ slog(LOG_DEBUG, "%s: skipping address %s, protocol %d", function, sockaddr2string(TOSS(from_ai->ai_addr), NULL, 0), from_ai->ai_protocol); doskip = 1; } else if (from_ai->ai_addr->sa_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&TOIN6(from_ai->ai_addr)->sin6_addr)) { if (hints != NULL && hints->ai_family != 0 && hints->ai_family != AF_INET) doskip = 1; else if (!external_has_safamily(AF_INET)) { /* * no point in converting to IPv4 if we don't have any * IPv4 address to use. Lets hope there is another * address for this hostname. */ doskip = 1; } else { /* * Have IPv4 on external interface and hints does not preclude * us from returning IPv4, so can convert the address from * IPv4-mapped IPv6 (which we don't want to use), to a regular * IPv4 address. */ *to_ai = *from_ai; /* most attributes will remain the same. */ to_ai->ai_addr = TOSA(&to->data.getaddr.ai_addr_mem[i]); bzero(to_ai->ai_addr, salen(AF_INET)); SET_SOCKADDR(TOSS(to_ai->ai_addr), AF_INET); ipv4_mapped_to_regular(&TOIN6(from_ai->ai_addr)->sin6_addr, &TOIN(to_ai->ai_addr)->sin_addr); to_ai->ai_family = AF_INET; to_ai->ai_addrlen = salen(AF_INET); } } else { *to_ai = *from_ai; to_ai->ai_addr = TOSA(&to->data.getaddr.ai_addr_mem[i]); memcpy(to_ai->ai_addr, from_ai->ai_addr, from_ai->ai_addrlen); } if (doskip) { slog(LOG_DEBUG, "%s: skipping address family %d", function, from_ai->ai_addr->sa_family); from_ai = from_ai->ai_next; continue; } #else /* SOCKS_CLIENT */ *to_ai = *from_ai; to_ai->ai_addr = TOSA(&to->data.getaddr.ai_addr_mem[i]); memcpy(to_ai->ai_addr, from_ai->ai_addr, from_ai->ai_addrlen); #endif /* !SOCKS_CLIENT */ if (from_ai->ai_canonname != NULL) { const size_t len = strlen(from_ai->ai_canonname); if (len >= sizeof(to->data.getaddr.ai_canonname_mem)) { swarnx("%s: DNS-name %s is %lu bytes long, expected max is %lu", function, str2vis(from_ai->ai_canonname, len, visbuf, sizeof(visbuf)), (unsigned long)len, (unsigned long)sizeof(to->data.getaddr.ai_canonname_mem) - 1); return EAI_MEMORY; } /* * Whether any entries but the first entry have an ai_canonname * appears to vary. * * glibc: no, at least the version on 3.7.3-101.fc17.x86_64. * libresolv: yes, at least the version with FreeBSD 9.1-RELEASE. * * In both cases, all ai_canonnames are the same however, so * we do not need to allocate separate memory for them but can * let them all use the memory of the first entry. */ if (i == 0) /* first entry; initialize memory. */ memcpy(to->data.getaddr.ai_canonname_mem, from_ai->ai_canonname, len + 1); to_ai->ai_canonname = to->data.getaddr.ai_canonname_mem; } else to_ai->ai_canonname = NULL; from_ai = from_ai->ai_next; to_ai_previous = to_ai; to_ai->ai_next = &to->data.getaddr.ai_next_mem[i]; to_ai = to_ai->ai_next; ++i; } to_ai->ai_next = NULL; if (from_ai == NULL || i >= maxentries) to_ai_previous->ai_next = NULL; if (i == 0) { slog(LOG_DEBUG, "%s: strange, no entries copied", function); bzero(&to->data.getaddr.addrinfo, sizeof(to->data.getaddr.addrinfo)); return EAI_FAMILY; } return 0; } #if !SOCKS_CLIENT void hostcacheinvalidate(void) { const char *function = "hostcacheinvalidate()"; size_t i; slog(LOG_DEBUG, "%s", function); socks_lock(sockscf.hostfd, 0, 0, 0, 1); for (i = 0; i < SOCKD_HOSTCACHE; ++i) hostcache[i].allocated = 0; socks_unlock(sockscf.hostfd, 0, 0); } static int gai2h_errno(gai_rc) const int gai_rc; { switch (gai_rc) { case EAI_AGAIN: case EAI_MEMORY: return TRY_AGAIN; case EAI_NONAME: return HOST_NOT_FOUND; } return NO_RECOVERY; } void hostcachesetup(void) { const char *function = "hostcachesetup()"; if ((sockscf.hostfd = socks_mklock(SOCKD_SHMEMFILE, NULL, 0)) == -1) serr("%s: socks_mklock() failed to create shmemfile using base %s", function, SOCKD_SHMEMFILE); if ((hostcache = sockd_mmap(NULL, sizeof(*hostcache) * SOCKD_HOSTCACHE, PROT_READ | PROT_WRITE, MAP_SHARED, sockscf.hostfd, 1)) == MAP_FAILED) serr("%s: failed to mmap(2) hostcache of size %lu", function, (unsigned long)(sizeof(*hostcache) * SOCKD_HOSTCACHE)); } struct hostent * cgethostbyname(name) const char *name; { const char *function = "cgethostbyname()"; static hostentry_t hostentry; static dnsinfo_t resmem; static char *nullist[] = { NULL }, *addrv[HOSTENT_MAX_ALIASES + 1]; struct addrinfo *ainfo, *next, hints; struct hostent he; size_t addrc; int rc; bzero(&hints, sizeof(hints)); hints.ai_flags = AI_CANONNAME; hints.ai_family = AF_INET; if ((rc = cgetaddrinfo(name, NULL, &hints, &ainfo, &resmem)) != 0) { char visbuf[MAXHOSTNAMELEN * 4]; slog(LOG_DEBUG, "%s: getaddrinfo(%s) failed: %s", function, str2vis(name, strlen(name), visbuf, sizeof(visbuf)), gai_strerror(rc)); h_errno = gai2h_errno(rc); return NULL; } SASSERTX(ainfo != NULL); addrc = 0; for (next = ainfo; next != NULL; next = next->ai_next) { SASSERTX(addrc < ELEMENTS(addrv)); SASSERTX(next->ai_addr != NULL); SASSERTX(next->ai_family == next->ai_addr->sa_family); if (next->ai_addr->sa_family == AF_INET) { he.h_addrtype = next->ai_addr->sa_family; he.h_length = sizeof(struct in_addr); addrv[addrc++] = (char *)&TOIN(next->ai_addr)->sin_addr; if (addrc + 1 >= ELEMENTS(addrv)) break; } else slog(LOG_DEBUG, "%s: ai_family = %d. Skipped", function, next->ai_family); } if (addrc == 0) { h_errno = NO_ADDRESS; return NULL; } addrv[addrc] = NULL; he.h_name = ainfo->ai_canonname; he.h_aliases = nullist; he.h_addr_list = addrv; hostentcopy(&hostentry, &he); return &hostentry.hostent; } struct hostent * cgethostbyaddr(addr, len, type) const void *addr; socklen_t len; int type; { const char *function = "cgethostbyaddr()"; static hostentry_t hostentry; static char *nullist[] = { NULL }, *onelist[2]; struct sockaddr_storage sa; struct hostent he; char host[MAXHOSTNAMELEN]; int rc; SASSERTX(type == AF_INET); SASSERTX(len == sizeof(struct in_addr)); bzero(&sa, sizeof(sa)); SET_SOCKADDR(&sa, AF_INET); TOIN(&sa)->sin_addr = *(const struct in_addr *)addr; if ((rc = cgetnameinfo(TOSA(&sa), salen(sa.ss_family), host, sizeof(host), NULL, 0, NI_NAMEREQD)) != 0) { slog(LOG_DEBUG, "%s: cgetnameinfo(%s) failed: %s", function, sockaddr2string(&sa, NULL, 0), gai_strerror(rc)); h_errno = gai2h_errno(rc); return NULL; } he.h_name = host; he.h_aliases = nullist; he.h_addrtype = sa.ss_family; he.h_length = sizeof(struct in_addr); he.h_addr_list = onelist; he.h_addr_list[0] = (char *)&TOIN(&sa)->sin_addr; he.h_addr_list[1] = NULL; hostentcopy(&hostentry, &he); return &hostentry.hostent; } static hostentry_t * hostentcopy(to, from) hostentry_t *to; const struct hostent *from; { const char *function = "hostentcopy()"; const size_t maxaliases = ELEMENTS(to->h_aliases); size_t i; if ((size_t)from->h_length > sizeof(to->h_addr_listmem[0])) { swarnx("%s: h_length of %s is %d bytes long, max expected is %lu", function, from->h_name, from->h_length, (unsigned long)sizeof(to->h_addr_listmem[0])); return NULL; } if (strlen(from->h_name) >= sizeof(to->h_name)) { swarnx("%s: name %s is %lu bytes long, max expected is %lu", function, from->h_name, (unsigned long)strlen(from->h_name), (unsigned long)sizeof(to->h_name) - 1); return NULL; } to->hostent.h_addrtype = from->h_addrtype; to->hostent.h_length = from->h_length; to->hostent.h_name = to->h_name; strcpy(to->hostent.h_name, from->h_name); for (i = 0; i < (maxaliases - 1) && from->h_aliases[i] != NULL; ++i) { if (strlen(from->h_aliases[i]) >= sizeof(to->h_aliasesmem[0])) { swarnx("%s: name %s is %lu bytes long, max expected is %lu", function, from->h_aliases[i], (unsigned long)strlen(from->h_aliases[i]), (unsigned long)sizeof(to->h_aliases[0]) - 1); return NULL; } to->h_aliases[i] = to->h_aliasesmem[i]; strcpy(to->h_aliases[i], from->h_aliases[i]); } to->hostent.h_aliases = to->h_aliases; to->hostent.h_aliases[i] = NULL; for (i = 0; i < (maxaliases - 1) && from->h_addr_list[i] != NULL; ++i) { to->h_addr_list[i] = to->h_addr_listmem[i]; memcpy(to->h_addr_list[i], from->h_addr_list[i], (size_t)from->h_length); } to->hostent.h_addr_list = to->h_addr_list; to->hostent.h_addr_list[i] = NULL; return to; } static size_t hosthash(name, size) const char *name; const size_t size; { unsigned int value; char *end; if (size < MIN_HASH_SIZE) return 0; /* don't bother scanning past second dot. */ if ((end = strchr(name, '.')) != NULL) end = strchr(end + 1, '.'); if (end == NULL) /* zero or one dot in name. */ end = strchr(name, NUL); SASSERTX(name <= end); value = 0; while (name != end) { value = (value << 5) + *name; /* MAW - DS&A: Horner's rule. */ ++name; } return value % size; } static size_t addrhash(addr, size) const struct sockaddr_storage *addr; const size_t size; { size_t sum; if (size < MIN_HASH_SIZE) /* * Assume this is so small a cache that it's better to just scan * it sequentially, rather than risk premature expiry of cached * DNS-entries. */ return 0; switch (addr->ss_family) { case AF_INET: sum = ntohl(TOCIN(addr)->sin_addr.s_addr); break; case AF_INET6: { const unsigned char *byte = TOCIN6(addr)->sin6_addr.s6_addr; const size_t len = sizeof(TOCIN6(addr)->sin6_addr.s6_addr), digits_wanted = 4; ssize_t i; size_t digits_summed; sum = 0; digits_summed = 0; i = len - 1; while (i >= 0 && digits_summed < digits_wanted) { if (byte[i] != 0) { sum += byte[i]; ++digits_summed; } --i; } break; } default: SERRX(addr->ss_family); } return sum % size; } dnsinfo_t * getoldest(starti) const size_t starti; { dnsinfo_t *oldest; size_t i; for (i = starti, oldest = NULL; i < SOCKD_HOSTCACHE; ++i) { if (oldest == NULL || hostcache[i].written < oldest->written) oldest = &hostcache[i]; } SASSERTX(oldest != NULL); return oldest; } static int name_matches_cache(ce, name, service, hints) const dnsinfo_t *ce; /* CacheEntry. */ const char *name; const char *service; const struct addrinfo *hints; { if (ce->allocated && ce->key == id_name) { if (hints == NULL) { if (ce->data.getaddr.hints != NULL) return 0; } else { if (ce->data.getaddr.hints == NULL) return 0; if (memcmp(ce->data.getaddr.hints, hints, sizeof(*hints)) != 0) return 0; } if (strcasecmp(ce->id.name, name) != 0) return 0; if (service == NULL) { if (*ce->service != NUL) return 0; } else { if (*ce->service == NUL) return 0; if (strcasecmp(ce->service, service) != 0) return 0; } /* all matches */ return 1; } return 0; } static int addr_matches_cache(ce, addr, flags) const dnsinfo_t *ce; /* cache entry. */ const struct sockaddr *addr; const int flags; { if (ce->allocated && ce->data.getname.flags == flags && ce->key == id_addr && sockaddrareeq(&ce->id.addr, TOCSS(addr), 0)) return 1; return 0; } static int addrisinlist(addr, ailist, ailist_last) const struct sockaddr *addr; struct addrinfo *ailist; struct addrinfo *ailist_last; { const char *function = "addrisinlist()"; struct addrinfo *ai_next_original = ailist_last->ai_next; int isinlist; ailist_last->ai_next = NULL; switch(addr->sa_family) { case AF_INET: { struct in_addr mask;/* not const due to Solaris's "struct in struct" */ mask.s_addr = htonl(IPV4_FULLNETMASK); isinlist = ipv4_addrisinlist(&TOCIN(addr)->sin_addr, &mask, ailist) == NULL ? 0 : 1; break; } case AF_INET6: { const unsigned int mask = { IPV6_NETMASKBITS }; isinlist = ipv6_addrisinlist(&TOCIN6(addr)->sin6_addr, mask, ailist) == NULL ? 0 : 1; break; } default: slog(PRERELEASE ? LOG_NOTICE /* does this ever happen? */ : LOG_DEBUG, "%s: unknown/unused sa_family %d in addrinfo struct", function, addr->sa_family); isinlist = 1; } ailist_last->ai_next = ai_next_original; return isinlist; } #endif /* !SOCKS_CLIENT */