523 lines
13 KiB
C++
523 lines
13 KiB
C++
|
/*
|
||
|
* Copyright (C) 2020 The LineageOS Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
#include <fcntl.h>
|
||
|
#include <netdb.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <ctype.h>
|
||
|
#include <strings.h>
|
||
|
#include <sys/file.h>
|
||
|
#include <sys/mman.h>
|
||
|
#include <sys/socket.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <unistd.h>
|
||
|
#include <utime.h>
|
||
|
#include <pthread.h>
|
||
|
|
||
|
#include <netinet/in6.h>
|
||
|
#include <arpa/inet.h>
|
||
|
#include <arpa/nameser.h>
|
||
|
|
||
|
#include "hostent.h"
|
||
|
#include "resolv_private.h"
|
||
|
|
||
|
constexpr int MAXALIASES = 35;
|
||
|
constexpr int MAXADDRS = 35;
|
||
|
|
||
|
#define MAX_ADDRLEN (INET6_ADDRSTRLEN - (1 + 5))
|
||
|
#define MAX_HOSTLEN MAXHOSTNAMELEN
|
||
|
|
||
|
#define ESTIMATED_LINELEN 32
|
||
|
#define HCFILE_ALLOC_SIZE 256
|
||
|
|
||
|
/* From sethostent.c */
|
||
|
#define ALIGNBYTES (sizeof(uintptr_t) - 1)
|
||
|
#define ALIGN(p) (((uintptr_t)(p) + ALIGNBYTES) &~ ALIGNBYTES)
|
||
|
|
||
|
/*
|
||
|
* Host cache entry for hcfile.c_data.
|
||
|
* Offsets are into hcfile.h_data.
|
||
|
* Strings are *not* terminated by NULL, but by whitespace (isspace) or '#'.
|
||
|
* Use hstr* functions with these.
|
||
|
*/
|
||
|
struct hcent
|
||
|
{
|
||
|
uint32_t addr;
|
||
|
uint32_t name;
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Overall host cache file state.
|
||
|
*/
|
||
|
struct hcfile
|
||
|
{
|
||
|
int h_fd;
|
||
|
struct stat h_st;
|
||
|
char* h_data;
|
||
|
|
||
|
uint32_t c_alloc;
|
||
|
uint32_t c_len;
|
||
|
struct hcent* c_data;
|
||
|
};
|
||
|
static struct hcfile hcfile;
|
||
|
static pthread_mutex_t hclock = PTHREAD_MUTEX_INITIALIZER;
|
||
|
|
||
|
static size_t hstrlen(const char *s)
|
||
|
{
|
||
|
const char *p = s;
|
||
|
while (*p && *p != '#' && !isspace(*p))
|
||
|
++p;
|
||
|
return p - s;
|
||
|
}
|
||
|
|
||
|
static int hstrcmp(const char *a, const char *b)
|
||
|
{
|
||
|
size_t alen = hstrlen(a);
|
||
|
size_t blen = hstrlen(b);
|
||
|
int res = strncmp(a, b, MIN(alen, blen));
|
||
|
if (res == 0)
|
||
|
res = alen - blen;
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static char *hstrcpy(char *dest, const char *src)
|
||
|
{
|
||
|
size_t len = hstrlen(src);
|
||
|
memcpy(dest, src, len);
|
||
|
dest[len] = '\0';
|
||
|
return dest;
|
||
|
}
|
||
|
|
||
|
static char *hstrdup(const char *s)
|
||
|
{
|
||
|
size_t len = hstrlen(s);
|
||
|
char *dest = (char *)malloc(len + 1);
|
||
|
if (!dest)
|
||
|
return NULL;
|
||
|
memcpy(dest, s, len);
|
||
|
dest[len] = '\0';
|
||
|
return dest;
|
||
|
}
|
||
|
|
||
|
static int cmp_hcent_name(const void *a, const void *b)
|
||
|
{
|
||
|
struct hcent *ea = (struct hcent *)a;
|
||
|
const char *na = hcfile.h_data + ea->name;
|
||
|
struct hcent *eb = (struct hcent *)b;
|
||
|
const char *nb = hcfile.h_data + eb->name;
|
||
|
|
||
|
return hstrcmp(na, nb);
|
||
|
}
|
||
|
|
||
|
static struct hcent *_hcfindname(const char *name)
|
||
|
{
|
||
|
size_t first, last, mid;
|
||
|
struct hcent *cur = NULL;
|
||
|
int cmp;
|
||
|
|
||
|
if (hcfile.c_len == 0)
|
||
|
return NULL;
|
||
|
|
||
|
first = 0;
|
||
|
last = hcfile.c_len - 1;
|
||
|
mid = (first + last) / 2;
|
||
|
while (first <= last) {
|
||
|
cur = hcfile.c_data + mid;
|
||
|
cmp = hstrcmp(hcfile.h_data + cur->name, name);
|
||
|
if (cmp == 0)
|
||
|
goto found;
|
||
|
if (cmp < 0)
|
||
|
first = mid + 1;
|
||
|
else {
|
||
|
if (mid > 0)
|
||
|
last = mid - 1;
|
||
|
else
|
||
|
return NULL;
|
||
|
}
|
||
|
mid = (first + last) / 2;
|
||
|
}
|
||
|
return NULL;
|
||
|
|
||
|
found:
|
||
|
while (cur > hcfile.c_data) {
|
||
|
struct hcent *prev = cur - 1;
|
||
|
cmp = cmp_hcent_name(cur, prev);
|
||
|
if (cmp)
|
||
|
break;
|
||
|
cur = prev;
|
||
|
}
|
||
|
|
||
|
return cur;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Find next name on line, if any.
|
||
|
*
|
||
|
* Assumes that line is terminated by LF.
|
||
|
*/
|
||
|
static const char *_hcnextname(const char *name)
|
||
|
{
|
||
|
while (!isspace(*name)) {
|
||
|
if (*name == '#')
|
||
|
return NULL;
|
||
|
++name;
|
||
|
}
|
||
|
while (isspace(*name)) {
|
||
|
if (*name == '\n')
|
||
|
return NULL;
|
||
|
++name;
|
||
|
}
|
||
|
if (*name == '#')
|
||
|
return NULL;
|
||
|
return name;
|
||
|
}
|
||
|
|
||
|
static int _hcfilemmap(void)
|
||
|
{
|
||
|
struct stat st;
|
||
|
int h_fd;
|
||
|
char *h_addr;
|
||
|
const char *p, *pend;
|
||
|
uint32_t c_alloc;
|
||
|
|
||
|
h_fd = open(_PATH_HOSTS, O_CLOEXEC);
|
||
|
if (h_fd < 0)
|
||
|
return -1;
|
||
|
if (flock(h_fd, LOCK_EX) != 0) {
|
||
|
close(h_fd);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if (hcfile.h_data) {
|
||
|
memset(&st, 0, sizeof(st));
|
||
|
if (fstat(h_fd, &st) == 0) {
|
||
|
if (st.st_size == hcfile.h_st.st_size &&
|
||
|
st.st_mtime == hcfile.h_st.st_mtime) {
|
||
|
flock(h_fd, LOCK_UN);
|
||
|
close(h_fd);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
free(hcfile.c_data);
|
||
|
munmap(hcfile.h_data, hcfile.h_st.st_size);
|
||
|
close(hcfile.h_fd);
|
||
|
memset(&hcfile, 0, sizeof(struct hcfile));
|
||
|
}
|
||
|
|
||
|
if (fstat(h_fd, &st) != 0) {
|
||
|
flock(h_fd, LOCK_UN);
|
||
|
close(h_fd);
|
||
|
return -1;
|
||
|
}
|
||
|
h_addr = (char*)mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, h_fd, 0);
|
||
|
if (h_addr == MAP_FAILED) {
|
||
|
flock(h_fd, LOCK_UN);
|
||
|
close(h_fd);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
hcfile.h_fd = h_fd;
|
||
|
hcfile.h_st = st;
|
||
|
hcfile.h_data = h_addr;
|
||
|
|
||
|
c_alloc = 0;
|
||
|
/*
|
||
|
* Do an initial allocation if the file is "large". Estimate
|
||
|
* 32 bytes per line and define "large" as more than half of
|
||
|
* the alloc growth size (256 entries).
|
||
|
*/
|
||
|
if (st.st_size >= ESTIMATED_LINELEN * HCFILE_ALLOC_SIZE / 2) {
|
||
|
c_alloc = st.st_size / ESTIMATED_LINELEN;
|
||
|
hcfile.c_data = (struct hcent*)malloc(c_alloc * sizeof(struct hcent));
|
||
|
if (!hcfile.c_data) {
|
||
|
goto oom;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
p = (const char *)h_addr;
|
||
|
pend = p + st.st_size;
|
||
|
while (p < pend) {
|
||
|
const char *eol, *addr, *name;
|
||
|
size_t len;
|
||
|
addr = p;
|
||
|
eol = (const char*)memchr(p, '\n', pend - p);
|
||
|
if (!eol)
|
||
|
break;
|
||
|
p = eol + 1;
|
||
|
if (*addr == '#' || *addr == '\n')
|
||
|
continue;
|
||
|
len = hstrlen(addr);
|
||
|
if (len > MAX_ADDRLEN)
|
||
|
continue;
|
||
|
name = addr + len;
|
||
|
while (name < eol && isspace(*name))
|
||
|
++name;
|
||
|
while (name < eol) {
|
||
|
len = hstrlen(name);
|
||
|
if (len == 0)
|
||
|
break;
|
||
|
if (len < MAX_HOSTLEN) {
|
||
|
struct hcent *ent;
|
||
|
if (c_alloc <= hcfile.c_len) {
|
||
|
struct hcent *c_data;
|
||
|
c_alloc += HCFILE_ALLOC_SIZE;
|
||
|
c_data = (struct hcent*)realloc(hcfile.c_data, c_alloc * sizeof(struct hcent));
|
||
|
if (!c_data) {
|
||
|
goto oom;
|
||
|
}
|
||
|
hcfile.c_data = c_data;
|
||
|
}
|
||
|
ent = hcfile.c_data + hcfile.c_len;
|
||
|
ent->addr = addr - h_addr;
|
||
|
ent->name = name - h_addr;
|
||
|
++hcfile.c_len;
|
||
|
}
|
||
|
name += len;
|
||
|
while (name < eol && isspace(*name))
|
||
|
++name;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
qsort(hcfile.c_data, hcfile.c_len,
|
||
|
sizeof(struct hcent), cmp_hcent_name);
|
||
|
|
||
|
flock(h_fd, LOCK_UN);
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
oom:
|
||
|
free(hcfile.c_data);
|
||
|
munmap(hcfile.h_data, hcfile.h_st.st_size);
|
||
|
flock(hcfile.h_fd, LOCK_UN);
|
||
|
close(hcfile.h_fd);
|
||
|
memset(&hcfile, 0, sizeof(struct hcfile));
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Caching version of getaddrinfo.
|
||
|
*
|
||
|
* If we find the requested host name in the cache, use getaddrinfo to
|
||
|
* populate the result for each address we find.
|
||
|
*
|
||
|
* Note glibc and bionic differ in the handling of ai_canonname. POSIX
|
||
|
* says that ai_canonname is only populated in the first result entry.
|
||
|
* glibc does this. bionic populates ai_canonname in all result entries.
|
||
|
* We choose the POSIX/glibc way here.
|
||
|
*/
|
||
|
int hc_getaddrinfo(const char *name, const struct addrinfo* hints, struct addrinfo** result)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct hcent *ent, *cur;
|
||
|
struct addrinfo *ai;
|
||
|
struct addrinfo rhints;
|
||
|
struct addrinfo *last;
|
||
|
int canonname = 0;
|
||
|
int cmp;
|
||
|
|
||
|
if (getenv("ANDROID_HOSTS_CACHE_DISABLE") != NULL)
|
||
|
return EAI_SYSTEM;
|
||
|
|
||
|
if (!name)
|
||
|
return EAI_SYSTEM;
|
||
|
|
||
|
pthread_mutex_lock(&hclock);
|
||
|
|
||
|
if (_hcfilemmap() != 0) {
|
||
|
ret = EAI_SYSTEM;
|
||
|
goto out;
|
||
|
}
|
||
|
ent = _hcfindname(name);
|
||
|
if (!ent) {
|
||
|
ret = EAI_NONAME;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
if (hints) {
|
||
|
canonname = (hints->ai_flags & AI_CANONNAME);
|
||
|
memcpy(&rhints, hints, sizeof(rhints));
|
||
|
rhints.ai_flags &= ~AI_CANONNAME;
|
||
|
}
|
||
|
else {
|
||
|
memset(&rhints, 0, sizeof(rhints));
|
||
|
}
|
||
|
rhints.ai_flags |= AI_NUMERICHOST;
|
||
|
|
||
|
last = NULL;
|
||
|
cur = ent;
|
||
|
do {
|
||
|
char addrstr[MAX_ADDRLEN];
|
||
|
struct addrinfo *res;
|
||
|
|
||
|
hstrcpy(addrstr, hcfile.h_data + cur->addr);
|
||
|
|
||
|
if (getaddrinfo_numeric(addrstr, nullptr, rhints, &res) == 0) {
|
||
|
if (!last)
|
||
|
(*result)->ai_next = res;
|
||
|
else
|
||
|
last->ai_next = res;
|
||
|
last = res;
|
||
|
while (last->ai_next)
|
||
|
last = last->ai_next;
|
||
|
}
|
||
|
|
||
|
if(cur + 1 >= hcfile.c_data + hcfile.c_len)
|
||
|
break;
|
||
|
cmp = cmp_hcent_name(cur, cur + 1);
|
||
|
cur = cur + 1;
|
||
|
}
|
||
|
while (!cmp);
|
||
|
|
||
|
if (last == NULL) {
|
||
|
/* This check is equivalent to (*result)->ai_next == NULL */
|
||
|
ret = EAI_NODATA;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
if (canonname) {
|
||
|
ai = (*result)->ai_next;
|
||
|
free(ai->ai_canonname);
|
||
|
ai->ai_canonname = hstrdup(hcfile.h_data + ent->name);
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
pthread_mutex_unlock(&hclock);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Caching version of gethtbyname.
|
||
|
*
|
||
|
* Note glibc and bionic differ in the handling of aliases. glibc returns
|
||
|
* all aliases for all entries, regardless of whether they match h_addrtype.
|
||
|
* bionic returns only the aliases for the first hosts entry. We return all
|
||
|
* aliases for all IPv4 entries.
|
||
|
*
|
||
|
* Additionally, if an alias is IPv6 and the primary name for an alias also
|
||
|
* has an IPv4 entry, glibc will return the IPv4 address(es), but bionic
|
||
|
* will not. Neither do we.
|
||
|
*/
|
||
|
int hc_gethtbyname(const char *host, int af, struct getnamaddr *info)
|
||
|
{
|
||
|
int ret = NETDB_SUCCESS;
|
||
|
struct hcent *ent, *cur;
|
||
|
int cmp;
|
||
|
size_t addrlen;
|
||
|
unsigned int naliases = 0;
|
||
|
char *aliases[MAXALIASES];
|
||
|
unsigned int naddrs = 0;
|
||
|
char *addr_ptrs[MAXADDRS];
|
||
|
unsigned int n;
|
||
|
|
||
|
if (getenv("ANDROID_HOSTS_CACHE_DISABLE") != NULL)
|
||
|
return NETDB_INTERNAL;
|
||
|
|
||
|
switch (af) {
|
||
|
case AF_INET: addrlen = NS_INADDRSZ; break;
|
||
|
case AF_INET6: addrlen = NS_IN6ADDRSZ; break;
|
||
|
default:
|
||
|
return NETDB_INTERNAL;
|
||
|
}
|
||
|
|
||
|
pthread_mutex_lock(&hclock);
|
||
|
|
||
|
if (_hcfilemmap() != 0) {
|
||
|
ret = NETDB_INTERNAL;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
ent = _hcfindname(host);
|
||
|
if (!ent) {
|
||
|
ret = HOST_NOT_FOUND;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
cur = ent;
|
||
|
do {
|
||
|
char addr[16];
|
||
|
char addrstr[MAX_ADDRLEN];
|
||
|
char namestr[MAX_HOSTLEN];
|
||
|
const char *name;
|
||
|
|
||
|
hstrcpy(addrstr, hcfile.h_data + cur->addr);
|
||
|
if (inet_pton(af, addrstr, &addr) == 1) {
|
||
|
/* char *aligned;
|
||
|
First match is considered the official hostname */
|
||
|
if (naddrs == 0) {
|
||
|
hstrcpy(namestr, hcfile.h_data + cur->name);
|
||
|
HENT_SCOPY(info->hp->h_name, namestr, info->buf, info->buflen);
|
||
|
}
|
||
|
for (name = hcfile.h_data + cur->name; name; name = _hcnextname(name)) {
|
||
|
if (!hstrcmp(name, host))
|
||
|
continue;
|
||
|
hstrcpy(namestr, name);
|
||
|
HENT_SCOPY(aliases[naliases], namestr, info->buf, info->buflen);
|
||
|
++naliases;
|
||
|
if (naliases >= MAXALIASES)
|
||
|
goto nospc;
|
||
|
}
|
||
|
/* aligned = (char *)ALIGN(info->buf);
|
||
|
if (info->buf != aligned) {
|
||
|
if ((ptrdiff_t)info->buflen < (aligned - info->buf))
|
||
|
goto nospc;
|
||
|
info->buflen -= (aligned - info->buf);
|
||
|
info->buf = aligned;
|
||
|
} */
|
||
|
HENT_COPY(addr_ptrs[naddrs], addr, addrlen, info->buf, info->buflen);
|
||
|
++naddrs;
|
||
|
if (naddrs >= MAXADDRS)
|
||
|
goto nospc;
|
||
|
}
|
||
|
|
||
|
if(cur + 1 >= hcfile.c_data + hcfile.c_len)
|
||
|
break;
|
||
|
cmp = cmp_hcent_name(cur, cur + 1);
|
||
|
cur = cur + 1;
|
||
|
}
|
||
|
while (!cmp);
|
||
|
|
||
|
if (naddrs == 0) {
|
||
|
ret = HOST_NOT_FOUND;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
addr_ptrs[naddrs++] = NULL;
|
||
|
aliases[naliases++] = NULL;
|
||
|
|
||
|
/* hp->h_name already populated */
|
||
|
HENT_ARRAY(info->hp->h_aliases, naliases, info->buf, info->buflen);
|
||
|
for (n = 0; n < naliases; ++n) {
|
||
|
info->hp->h_aliases[n] = aliases[n];
|
||
|
}
|
||
|
info->hp->h_addrtype = af;
|
||
|
info->hp->h_length = addrlen;
|
||
|
HENT_ARRAY(info->hp->h_addr_list, naddrs, info->buf, info->buflen);
|
||
|
for (n = 0; n < naddrs; ++n) {
|
||
|
info->hp->h_addr_list[n] = addr_ptrs[n];
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
pthread_mutex_unlock(&hclock);
|
||
|
return ret;
|
||
|
|
||
|
nospc:
|
||
|
ret = NETDB_INTERNAL;
|
||
|
goto out;
|
||
|
}
|