#include "ares_private.h"
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif
#ifdef HAVE_NETDB_H
# include <netdb.h>
#endif
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif
#include <time.h>
struct
ares_hosts_file {
time_t
ts;
char
*filename;
ares_htable_strvp_t *iphash;
ares_htable_strvp_t *hosthash;
};
struct
ares_hosts_entry {
size_t
refcnt;
ares_llist_t *ips;
ares_llist_t *hosts;
};
const
void
*ares_dns_pton(
const
char
*ipaddr,
struct
ares_addr *addr,
size_t
*out_len)
{
const
void
*ptr = NULL;
size_t
ptr_len = 0;
if
(ipaddr == NULL || addr == NULL || out_len == NULL) {
return
NULL;
}
*out_len = 0;
if
(addr->family == AF_INET &&
ares_inet_pton(AF_INET, ipaddr, &addr->addr.addr4) > 0) {
ptr = &addr->addr.addr4;
ptr_len =
sizeof
(addr->addr.addr4);
}
else
if
(addr->family == AF_INET6 &&
ares_inet_pton(AF_INET6, ipaddr, &addr->addr.addr6) > 0) {
ptr = &addr->addr.addr6;
ptr_len =
sizeof
(addr->addr.addr6);
}
else
if
(addr->family == AF_UNSPEC) {
if
(ares_inet_pton(AF_INET, ipaddr, &addr->addr.addr4) > 0) {
addr->family = AF_INET;
ptr = &addr->addr.addr4;
ptr_len =
sizeof
(addr->addr.addr4);
}
else
if
(ares_inet_pton(AF_INET6, ipaddr, &addr->addr.addr6) > 0) {
addr->family = AF_INET6;
ptr = &addr->addr.addr6;
ptr_len =
sizeof
(addr->addr.addr6);
}
}
*out_len = ptr_len;
return
ptr;
}
static
ares_bool_t ares_normalize_ipaddr(
const
char
*ipaddr,
char
*out,
size_t
out_len)
{
struct
ares_addr data;
const
void
*addr;
size_t
addr_len = 0;
memset
(&data, 0,
sizeof
(data));
data.family = AF_UNSPEC;
addr = ares_dns_pton(ipaddr, &data, &addr_len);
if
(addr == NULL) {
return
ARES_FALSE;
}
if
(!ares_inet_ntop(data.family, addr, out, (ares_socklen_t)out_len)) {
return
ARES_FALSE;
}
return
ARES_TRUE;
}
static
void
ares_hosts_entry_destroy(ares_hosts_entry_t *entry)
{
if
(entry == NULL) {
return
;
}
if
(entry->refcnt != 0) {
entry->refcnt--;
}
if
(entry->refcnt > 0) {
return
;
}
ares_llist_destroy(entry->hosts);
ares_llist_destroy(entry->ips);
ares_free(entry);
}
static
void
ares_hosts_entry_destroy_cb(
void
*entry)
{
ares_hosts_entry_destroy(entry);
}
void
ares_hosts_file_destroy(ares_hosts_file_t *hf)
{
if
(hf == NULL) {
return
;
}
ares_free(hf->filename);
ares_htable_strvp_destroy(hf->hosthash);
ares_htable_strvp_destroy(hf->iphash);
ares_free(hf);
}
static
ares_hosts_file_t *ares_hosts_file_create(
const
char
*filename)
{
ares_hosts_file_t *hf = ares_malloc_zero(
sizeof
(*hf));
if
(hf == NULL) {
goto
fail;
}
hf->ts =
time
(NULL);
hf->filename = ares_strdup(filename);
if
(hf->filename == NULL) {
goto
fail;
}
hf->iphash = ares_htable_strvp_create(ares_hosts_entry_destroy_cb);
if
(hf->iphash == NULL) {
goto
fail;
}
hf->hosthash = ares_htable_strvp_create(NULL);
if
(hf->hosthash == NULL) {
goto
fail;
}
return
hf;
fail:
ares_hosts_file_destroy(hf);
return
NULL;
}
typedef
enum
{
ARES_MATCH_NONE = 0,
ARES_MATCH_IPADDR = 1,
ARES_MATCH_HOST = 2
} ares_hosts_file_match_t;
static
ares_status_t ares_hosts_file_merge_entry(
const
ares_hosts_file_t *hf, ares_hosts_entry_t *existing,
ares_hosts_entry_t *entry, ares_hosts_file_match_t matchtype)
{
ares_llist_node_t *node;
if
(matchtype != ARES_MATCH_IPADDR) {
while
((node = ares_llist_node_first(entry->ips)) != NULL) {
const
char
*ipaddr = ares_llist_node_val(node);
if
(ares_htable_strvp_get_direct(hf->iphash, ipaddr) != NULL) {
ares_llist_node_destroy(node);
continue
;
}
ares_llist_node_mvparent_last(node, existing->ips);
}
}
while
((node = ares_llist_node_first(entry->hosts)) != NULL) {
const
char
*hostname = ares_llist_node_val(node);
if
(ares_htable_strvp_get_direct(hf->hosthash, hostname) != NULL) {
ares_llist_node_destroy(node);
continue
;
}
ares_llist_node_mvparent_last(node, existing->hosts);
}
ares_hosts_entry_destroy(entry);
return
ARES_SUCCESS;
}
static
ares_hosts_file_match_t
ares_hosts_file_match(
const
ares_hosts_file_t *hf, ares_hosts_entry_t *entry,
ares_hosts_entry_t **match)
{
ares_llist_node_t *node;
*match = NULL;
for
(node = ares_llist_node_first(entry->ips); node != NULL;
node = ares_llist_node_next(node)) {
const
char
*ipaddr = ares_llist_node_val(node);
*match = ares_htable_strvp_get_direct(hf->iphash, ipaddr);
if
(*match != NULL) {
return
ARES_MATCH_IPADDR;
}
}
for
(node = ares_llist_node_first(entry->hosts); node != NULL;
node = ares_llist_node_next(node)) {
const
char
*host = ares_llist_node_val(node);
*match = ares_htable_strvp_get_direct(hf->hosthash, host);
if
(*match != NULL) {
return
ARES_MATCH_HOST;
}
}
return
ARES_MATCH_NONE;
}
static
ares_status_t ares_hosts_file_add(ares_hosts_file_t *hosts,
ares_hosts_entry_t *entry)
{
ares_hosts_entry_t *match = NULL;
ares_status_t status = ARES_SUCCESS;
ares_llist_node_t *node;
ares_hosts_file_match_t matchtype;
size_t
num_hostnames;
num_hostnames = ares_llist_len(entry->hosts);
matchtype = ares_hosts_file_match(hosts, entry, &match);
if
(matchtype != ARES_MATCH_NONE) {
status = ares_hosts_file_merge_entry(hosts, match, entry, matchtype);
if
(status != ARES_SUCCESS) {
ares_hosts_entry_destroy(entry);
return
status;
}
entry = match;
}
if
(matchtype != ARES_MATCH_IPADDR) {
const
char
*ipaddr = ares_llist_last_val(entry->ips);
if
(!ares_htable_strvp_get(hosts->iphash, ipaddr, NULL)) {
if
(!ares_htable_strvp_insert(hosts->iphash, ipaddr, entry)) {
ares_hosts_entry_destroy(entry);
return
ARES_ENOMEM;
}
entry->refcnt++;
}
}
for
(node = ares_llist_node_last(entry->hosts); node != NULL;
node = ares_llist_node_prev(node)) {
const
char
*val = ares_llist_node_val(node);
if
(num_hostnames == 0) {
break
;
}
num_hostnames--;
if
(ares_htable_strvp_get(hosts->hosthash, val, NULL)) {
continue
;
}
if
(!ares_htable_strvp_insert(hosts->hosthash, val, entry)) {
return
ARES_ENOMEM;
}
}
return
ARES_SUCCESS;
}
static
ares_bool_t ares_hosts_entry_isdup(ares_hosts_entry_t *entry,
const
char
*host)
{
ares_llist_node_t *node;
for
(node = ares_llist_node_first(entry->ips); node != NULL;
node = ares_llist_node_next(node)) {
const
char
*myhost = ares_llist_node_val(node);
if
(ares_strcaseeq(myhost, host)) {
return
ARES_TRUE;
}
}
return
ARES_FALSE;
}
static
ares_status_t ares_parse_hosts_hostnames(ares_buf_t *buf,
ares_hosts_entry_t *entry)
{
entry->hosts = ares_llist_create(ares_free);
if
(entry->hosts == NULL) {
return
ARES_ENOMEM;
}
while
(ares_buf_len(buf)) {
char
hostname[256];
char
*temp;
ares_status_t status;
unsigned
char
comment =
'#'
;
ares_buf_consume_whitespace(buf, ARES_FALSE);
if
(ares_buf_len(buf) == 0) {
break
;
}
if
(ares_buf_begins_with(buf, &comment, 1)) {
break
;
}
ares_buf_tag(buf);
if
(ares_buf_consume_nonwhitespace(buf) == 0) {
break
;
}
status = ares_buf_tag_fetch_string(buf, hostname,
sizeof
(hostname));
if
(status != ARES_SUCCESS) {
if
(ares_llist_len(entry->hosts) == 0) {
return
ARES_EBADSTR;
}
continue
;
}
if
(!ares_is_hostname(hostname)) {
continue
;
}
if
(ares_hosts_entry_isdup(entry, hostname)) {
continue
;
}
temp = ares_strdup(hostname);
if
(temp == NULL) {
return
ARES_ENOMEM;
}
if
(ares_llist_insert_last(entry->hosts, temp) == NULL) {
ares_free(temp);
return
ARES_ENOMEM;
}
}
if
(ares_llist_len(entry->hosts) == 0) {
return
ARES_EBADSTR;
}
return
ARES_SUCCESS;
}
static
ares_status_t ares_parse_hosts_ipaddr(ares_buf_t *buf,
ares_hosts_entry_t **entry_out)
{
char
addr[INET6_ADDRSTRLEN];
char
*temp;
ares_hosts_entry_t *entry = NULL;
ares_status_t status;
*entry_out = NULL;
ares_buf_tag(buf);
ares_buf_consume_nonwhitespace(buf);
status = ares_buf_tag_fetch_string(buf, addr,
sizeof
(addr));
if
(status != ARES_SUCCESS) {
return
status;
}
if
(!ares_normalize_ipaddr(addr, addr,
sizeof
(addr))) {
return
ARES_EBADSTR;
}
entry = ares_malloc_zero(
sizeof
(*entry));
if
(entry == NULL) {
return
ARES_ENOMEM;
}
entry->ips = ares_llist_create(ares_free);
if
(entry->ips == NULL) {
ares_hosts_entry_destroy(entry);
return
ARES_ENOMEM;
}
temp = ares_strdup(addr);
if
(temp == NULL) {
ares_hosts_entry_destroy(entry);
return
ARES_ENOMEM;
}
if
(ares_llist_insert_first(entry->ips, temp) == NULL) {
ares_free(temp);
ares_hosts_entry_destroy(entry);
return
ARES_ENOMEM;
}
*entry_out = entry;
return
ARES_SUCCESS;
}
static
ares_status_t ares_parse_hosts(
const
char
*filename,
ares_hosts_file_t **out)
{
ares_buf_t *buf = NULL;
ares_status_t status = ARES_EBADRESP;
ares_hosts_file_t *hf = NULL;
ares_hosts_entry_t *entry = NULL;
*out = NULL;
buf = ares_buf_create();
if
(buf == NULL) {
status = ARES_ENOMEM;
goto
done;
}
status = ares_buf_load_file(filename, buf);
if
(status != ARES_SUCCESS) {
goto
done;
}
hf = ares_hosts_file_create(filename);
if
(hf == NULL) {
status = ARES_ENOMEM;
goto
done;
}
while
(ares_buf_len(buf)) {
unsigned
char
comment =
'#'
;
ares_buf_consume_whitespace(buf, ARES_FALSE);
if
(ares_buf_len(buf) == 0) {
break
;
}
if
(ares_buf_begins_with(buf, &comment, 1)) {
ares_buf_consume_line(buf, ARES_TRUE);
continue
;
}
status = ares_parse_hosts_ipaddr(buf, &entry);
if
(status == ARES_ENOMEM) {
goto
done;
}
if
(status != ARES_SUCCESS) {
ares_buf_consume_line(buf, ARES_TRUE);
continue
;
}
status = ares_parse_hosts_hostnames(buf, entry);
if
(status == ARES_ENOMEM) {
goto
done;
}
else
if
(status != ARES_SUCCESS) {
ares_hosts_entry_destroy(entry);
entry = NULL;
ares_buf_consume_line(buf, ARES_TRUE);
continue
;
}
status = ares_hosts_file_add(hf, entry);
entry = NULL;
if
(status != ARES_SUCCESS) {
goto
done;
}
ares_buf_consume_line(buf, ARES_TRUE);
}
status = ARES_SUCCESS;
done:
ares_hosts_entry_destroy(entry);
ares_buf_destroy(buf);
if
(status != ARES_SUCCESS) {
ares_hosts_file_destroy(hf);
}
else
{
*out = hf;
}
return
status;
}
static
ares_bool_t ares_hosts_expired(
const
char
*filename,
const
ares_hosts_file_t *hf)
{
time_t
mod_ts = 0;
#ifdef HAVE_STAT
struct
stat st;
if
(stat(filename, &st) == 0) {
mod_ts = st.st_mtime;
}
#elif defined(_WIN32)
struct
_stat
st;
if
(
_stat
(filename, &st) == 0) {
mod_ts = st.st_mtime;
}
#else
(
void
)filename;
#endif
if
(hf == NULL) {
return
ARES_TRUE;
}
if
(mod_ts == 0) {
mod_ts =
time
(NULL) - 60;
}
if
(!ares_strcaseeq(hf->filename, filename)) {
return
ARES_TRUE;
}
if
(hf->ts <= mod_ts) {
return
ARES_TRUE;
}
return
ARES_FALSE;
}
static
ares_status_t ares_hosts_path(
const
ares_channel_t *channel,
ares_bool_t use_env,
char
**path)
{
char
*path_hosts = NULL;
*path = NULL;
if
(channel->hosts_path) {
path_hosts = ares_strdup(channel->hosts_path);
if
(!path_hosts) {
return
ARES_ENOMEM;
}
}
if
(use_env) {
if
(path_hosts) {
ares_free(path_hosts);
}
path_hosts = ares_strdup(
getenv
(
"CARES_HOSTS"
));
if
(!path_hosts) {
return
ARES_ENOMEM;
}
}
if
(!path_hosts) {
#if defined(USE_WINSOCK)
char
PATH_HOSTS[MAX_PATH] =
""
;
char
tmp[MAX_PATH];
HKEY
hkeyHosts;
DWORD
dwLength =
sizeof
(tmp);
if
(RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY, 0, KEY_READ,
&hkeyHosts) != ERROR_SUCCESS) {
return
ARES_ENOTFOUND;
}
RegQueryValueExA(hkeyHosts, DATABASEPATH, NULL, NULL, (
LPBYTE
)tmp,
&dwLength);
ExpandEnvironmentStringsA(tmp, PATH_HOSTS, MAX_PATH);
RegCloseKey(hkeyHosts);
strcat
(PATH_HOSTS, WIN_PATH_HOSTS);
#elif defined(WATT32)
const
char
*PATH_HOSTS = _w32_GetHostsFile();
if
(!PATH_HOSTS) {
return
ARES_ENOTFOUND;
}
#endif
path_hosts = ares_strdup(PATH_HOSTS);
if
(!path_hosts) {
return
ARES_ENOMEM;
}
}
*path = path_hosts;
return
ARES_SUCCESS;
}
static
ares_status_t ares_hosts_update(ares_channel_t *channel,
ares_bool_t use_env)
{
ares_status_t status;
char
*filename = NULL;
status = ares_hosts_path(channel, use_env, &filename);
if
(status != ARES_SUCCESS) {
return
status;
}
if
(!ares_hosts_expired(filename, channel->hf)) {
ares_free(filename);
return
ARES_SUCCESS;
}
ares_hosts_file_destroy(channel->hf);
channel->hf = NULL;
status = ares_parse_hosts(filename, &channel->hf);
ares_free(filename);
return
status;
}
ares_status_t ares_hosts_search_ipaddr(ares_channel_t *channel,
ares_bool_t use_env,
const
char
*ipaddr,
const
ares_hosts_entry_t **entry)
{
ares_status_t status;
char
addr[INET6_ADDRSTRLEN];
*entry = NULL;
status = ares_hosts_update(channel, use_env);
if
(status != ARES_SUCCESS) {
return
status;
}
if
(channel->hf == NULL) {
return
ARES_ENOTFOUND;
}
if
(!ares_normalize_ipaddr(ipaddr, addr,
sizeof
(addr))) {
return
ARES_EBADNAME;
}
*entry = ares_htable_strvp_get_direct(channel->hf->iphash, addr);
if
(*entry == NULL) {
return
ARES_ENOTFOUND;
}
return
ARES_SUCCESS;
}
ares_status_t ares_hosts_search_host(ares_channel_t *channel,
ares_bool_t use_env,
const
char
*host,
const
ares_hosts_entry_t **entry)
{
ares_status_t status;
*entry = NULL;
status = ares_hosts_update(channel, use_env);
if
(status != ARES_SUCCESS) {
return
status;
}
if
(channel->hf == NULL) {
return
ARES_ENOTFOUND;
}
*entry = ares_htable_strvp_get_direct(channel->hf->hosthash, host);
if
(*entry == NULL) {
return
ARES_ENOTFOUND;
}
return
ARES_SUCCESS;
}
static
ares_status_t
ares_hosts_ai_append_cnames(
const
ares_hosts_entry_t *entry,
struct
ares_addrinfo_cname **cnames_out)
{
struct
ares_addrinfo_cname *cname = NULL;
struct
ares_addrinfo_cname *cnames = NULL;
const
char
*primaryhost;
ares_llist_node_t *node;
ares_status_t status;
size_t
cnt = 0;
node = ares_llist_node_first(entry->hosts);
primaryhost = ares_llist_node_val(node);
node = ares_llist_node_next(node);
while
(node != NULL) {
const
char
*host = ares_llist_node_val(node);
cnt++;
if
(cnt > 100) {
break
;
}
cname = ares_append_addrinfo_cname(&cnames);
if
(cname == NULL) {
status = ARES_ENOMEM;
goto
done;
}
cname->alias = ares_strdup(host);
if
(cname->alias == NULL) {
status = ARES_ENOMEM;
goto
done;
}
cname->name = ares_strdup(primaryhost);
if
(cname->name == NULL) {
status = ARES_ENOMEM;
goto
done;
}
node = ares_llist_node_next(node);
}
if
(cnames == NULL) {
cname = ares_append_addrinfo_cname(&cnames);
if
(cname == NULL) {
status = ARES_ENOMEM;
goto
done;
}
cname->name = ares_strdup(primaryhost);
if
(cname->name == NULL) {
status = ARES_ENOMEM;
goto
done;
}
}
status = ARES_SUCCESS;
done:
if
(status != ARES_SUCCESS) {
ares_freeaddrinfo_cnames(cnames);
return
status;
}
*cnames_out = cnames;
return
ARES_SUCCESS;
}
ares_status_t ares_hosts_entry_to_addrinfo(
const
ares_hosts_entry_t *entry,
const
char
*name,
int
family,
unsigned
short
port,
ares_bool_t want_cnames,
struct
ares_addrinfo *ai)
{
ares_status_t status;
struct
ares_addrinfo_cname *cnames = NULL;
struct
ares_addrinfo_node *ainodes = NULL;
ares_llist_node_t *node;
switch
(family) {
case
AF_INET:
case
AF_INET6:
case
AF_UNSPEC:
break
;
default
:
return
ARES_EBADFAMILY;
}
if
(name != NULL) {
ai->name = ares_strdup(name);
if
(ai->name == NULL) {
status = ARES_ENOMEM;
goto
done;
}
}
for
(node = ares_llist_node_first(entry->ips); node != NULL;
node = ares_llist_node_next(node)) {
struct
ares_addr addr;
const
void
*ptr = NULL;
size_t
ptr_len = 0;
const
char
*ipaddr = ares_llist_node_val(node);
memset
(&addr, 0,
sizeof
(addr));
addr.family = family;
ptr = ares_dns_pton(ipaddr, &addr, &ptr_len);
if
(ptr == NULL) {
continue
;
}
status = ares_append_ai_node(addr.family, port, 0, ptr, &ainodes);
if
(status != ARES_SUCCESS) {
goto
done;
}
}
if
(want_cnames) {
status = ares_hosts_ai_append_cnames(entry, &cnames);
if
(status != ARES_SUCCESS) {
goto
done;
}
}
status = ARES_SUCCESS;
done:
if
(status != ARES_SUCCESS) {
ares_freeaddrinfo_cnames(cnames);
ares_freeaddrinfo_nodes(ainodes);
ares_free(ai->name);
ai->name = NULL;
return
status;
}
ares_addrinfo_cat_cnames(&ai->cnames, cnames);
ares_addrinfo_cat_nodes(&ai->nodes, ainodes);
return
status;
}
ares_status_t ares_hosts_entry_to_hostent(
const
ares_hosts_entry_t *entry,
int
family,
struct
hostent **hostent)
{
ares_status_t status;
struct
ares_addrinfo *ai = ares_malloc_zero(
sizeof
(*ai));
*hostent = NULL;
if
(ai == NULL) {
return
ARES_ENOMEM;
}
status = ares_hosts_entry_to_addrinfo(entry, NULL, family, 0, ARES_TRUE, ai);
if
(status != ARES_SUCCESS) {
goto
done;
}
status = ares_addrinfo2hostent(ai, family, hostent);
if
(status != ARES_SUCCESS) {
goto
done;
}
done:
ares_freeaddrinfo(ai);
if
(status != ARES_SUCCESS) {
ares_free_hostent(*hostent);
*hostent = NULL;
}
return
status;
}