| /* MIT License |
| * |
| * Copyright (c) 2023 Brad House |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the next |
| * paragraph) shall be included in all copies or substantial portions of the |
| * Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| * |
| * SPDX-License-Identifier: MIT |
| */ |
| #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> |
| |
| /* HOSTS FILE PROCESSING OVERVIEW |
| * ============================== |
| * The hosts file on the system contains static entries to be processed locally |
| * rather than querying the nameserver. Each row is an IP address followed by |
| * a list of space delimited hostnames that match the ip address. This is used |
| * for both forward and reverse lookups. |
| * |
| * We are caching the entire parsed hosts file for performance reasons. Some |
| * files may be quite sizable and as per Issue #458 can approach 1/2MB in size, |
| * and the parse overhead on a rapid succession of queries can be quite large. |
| * The entries are stored in forwards and backwards hashtables so we can get |
| * O(1) performance on lookup. The file is cached until the file modification |
| * timestamp changes. |
| * |
| * The hosts file processing is quite unique. It has to merge all related hosts |
| * and ips into a single entry due to file formatting requirements. For |
| * instance take the below: |
| * |
| * 127.0.0.1 localhost.localdomain localhost |
| * ::1 localhost.localdomain localhost |
| * 192.168.1.1 host.example.com host |
| * 192.168.1.5 host.example.com host |
| * 2620:1234::1 host.example.com host6.example.com host6 host |
| * |
| * This will yield 2 entries. |
| * 1) ips: 127.0.0.1,::1 |
| * hosts: localhost.localdomain,localhost |
| * 2) ips: 192.168.1.1,192.168.1.5,2620:1234::1 |
| * hosts: host.example.com,host,host6.example.com,host6 |
| * |
| * It could be argued that if searching for 192.168.1.1 that the 'host6' |
| * hostnames should not be returned, but this implementation will return them |
| * since they are related. It is unlikely this will matter in the real world. |
| */ |
| |
| struct ares_hosts_file { |
| time_t ts; |
| /*! cache the filename so we know if the filename changes it automatically |
| * invalidates the cache */ |
| char *filename; |
| /*! iphash is the owner of the 'entry' object as there is only ever a single |
| * match to the object. */ |
| ares_htable_strvp_t *iphash; |
| /*! hosthash does not own the entry so won't free on destruction */ |
| ares_htable_strvp_t *hosthash; |
| }; |
| |
| struct ares_hosts_entry { |
| size_t refcnt; /*! If the entry is stored multiple times in the |
| * ip address hash, we have to reference count it */ |
| 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; /* LCOV_EXCL_LINE: DefensiveCoding */ |
| } |
| |
| *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; /* LCOV_EXCL_LINE: DefensiveCoding */ |
| } |
| |
| return ARES_TRUE; |
| } |
| |
| static void ares_hosts_entry_destroy(ares_hosts_entry_t *entry) |
| { |
| if (entry == NULL) { |
| return; |
| } |
| |
| /* Honor reference counting */ |
| 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 we matched on IP address, we know there can only be 1, so there's no |
| * reason to do anything */ |
| 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; |
| } |
| |
| /*! entry is invalidated upon calling this function, always, even on error */ |
| 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; |
| |
| /* Record the number of hostnames in this entry file. If we merge into an |
| * existing record, these will be *appended* to the entry, so we'll count |
| * backwards when adding to the hosts hashtable */ |
| 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); /* LCOV_EXCL_LINE: DefensiveCoding */ |
| return status; /* LCOV_EXCL_LINE: DefensiveCoding */ |
| } |
| /* entry was invalidated above by merging */ |
| 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++; |
| } |
| } |
| |
| /* Go backwards, on a merge, hostnames are appended. Breakout once we've |
| * consumed all the hosts that we appended */ |
| 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--; |
| |
| /* first hostname match wins. If we detect a duplicate hostname for another |
| * ip it will automatically be added to the same entry */ |
| 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; |
| } |
| |
| /* Parse hostnames and aliases */ |
| 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; |
| } |
| |
| /* See if it is a comment, if so stop processing */ |
| if (ares_buf_begins_with(buf, &comment, 1)) { |
| break; |
| } |
| |
| ares_buf_tag(buf); |
| |
| /* Must be at end of line */ |
| if (ares_buf_consume_nonwhitespace(buf) == 0) { |
| break; |
| } |
| |
| status = ares_buf_tag_fetch_string(buf, hostname, sizeof(hostname)); |
| if (status != ARES_SUCCESS) { |
| /* Bad entry, just ignore as long as its not the first. If its the first, |
| * it must be valid */ |
| if (ares_llist_len(entry->hosts) == 0) { |
| return ARES_EBADSTR; |
| } |
| |
| continue; |
| } |
| |
| /* Validate it is a valid hostname characterset */ |
| if (!ares_is_hostname(hostname)) { |
| continue; |
| } |
| |
| /* Don't add a duplicate to the same entry */ |
| if (ares_hosts_entry_isdup(entry, hostname)) { |
| continue; |
| } |
| |
| /* Add to list */ |
| 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; |
| } |
| } |
| |
| /* Must have at least 1 entry */ |
| 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; |
| } |
| |
| /* Validate and normalize the ip address format */ |
| 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 = '#'; |
| |
| /* -- Start of new line here -- */ |
| |
| /* Consume any leading whitespace */ |
| ares_buf_consume_whitespace(buf, ARES_FALSE); |
| |
| if (ares_buf_len(buf) == 0) { |
| break; |
| } |
| |
| /* See if it is a comment, if so, consume remaining line */ |
| if (ares_buf_begins_with(buf, &comment, 1)) { |
| ares_buf_consume_line(buf, ARES_TRUE); |
| continue; |
| } |
| |
| /* Pull off ip address */ |
| status = ares_parse_hosts_ipaddr(buf, &entry); |
| if (status == ARES_ENOMEM) { |
| goto done; |
| } |
| if (status != ARES_SUCCESS) { |
| /* Bad line, consume and go onto next */ |
| ares_buf_consume_line(buf, ARES_TRUE); |
| continue; |
| } |
| |
| /* Parse of the hostnames */ |
| status = ares_parse_hosts_hostnames(buf, entry); |
| if (status == ARES_ENOMEM) { |
| goto done; |
| } else if (status != ARES_SUCCESS) { |
| /* Bad line, consume and go onto next */ |
| ares_hosts_entry_destroy(entry); |
| entry = NULL; |
| ares_buf_consume_line(buf, ARES_TRUE); |
| continue; |
| } |
| |
| /* Append the successful entry to the hosts file */ |
| status = ares_hosts_file_add(hf, entry); |
| entry = NULL; /* is always invalidated by this function, even on error */ |
| if (status != ARES_SUCCESS) { |
| goto done; |
| } |
| |
| /* Go to next line */ |
| 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; |
| } |
| |
| /* Expire every 60s if we can't get a time */ |
| if (mod_ts == 0) { |
| mod_ts = |
| time(NULL) - 60; /* LCOV_EXCL_LINE: only on systems without stat() */ |
| } |
| |
| /* If filenames are different, its expired */ |
| 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; /* LCOV_EXCL_LINE: OutOfMemory */ |
| } |
| } |
| |
| if (use_env) { |
| if (path_hosts) { |
| ares_free(path_hosts); |
| } |
| |
| path_hosts = ares_strdup(getenv("CARES_HOSTS")); |
| if (!path_hosts) { |
| return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ |
| } |
| } |
| |
| 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; /* LCOV_EXCL_LINE: DefensiveCoding */ |
| } |
| |
| 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; /* LCOV_EXCL_LINE: DefensiveCoding */ |
| } |
| |
| *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); |
| /* Skip to next node to start with aliases */ |
| node = ares_llist_node_next(node); |
| |
| while (node != NULL) { |
| const char *host = ares_llist_node_val(node); |
| |
| /* Cap at 100 entries. , some people use |
| * https://github.com/StevenBlack/hosts and we don't need 200k+ aliases */ |
| cnt++; |
| if (cnt > 100) { |
| break; /* LCOV_EXCL_LINE: DefensiveCoding */ |
| } |
| |
| cname = ares_append_addrinfo_cname(&cnames); |
| if (cname == NULL) { |
| status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ |
| goto done; /* LCOV_EXCL_LINE: OutOfMemory */ |
| } |
| |
| cname->alias = ares_strdup(host); |
| if (cname->alias == NULL) { |
| status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ |
| goto done; /* LCOV_EXCL_LINE: OutOfMemory */ |
| } |
| |
| cname->name = ares_strdup(primaryhost); |
| if (cname->name == NULL) { |
| status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ |
| goto done; /* LCOV_EXCL_LINE: OutOfMemory */ |
| } |
| |
| node = ares_llist_node_next(node); |
| } |
| |
| /* No entries, add only primary */ |
| if (cnames == NULL) { |
| cname = ares_append_addrinfo_cname(&cnames); |
| if (cname == NULL) { |
| status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ |
| goto done; /* LCOV_EXCL_LINE: OutOfMemory */ |
| } |
| |
| cname->name = ares_strdup(primaryhost); |
| if (cname->name == NULL) { |
| status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ |
| goto done; /* LCOV_EXCL_LINE: OutOfMemory */ |
| } |
| } |
| status = ARES_SUCCESS; |
| |
| done: |
| if (status != ARES_SUCCESS) { |
| ares_freeaddrinfo_cnames(cnames); /* LCOV_EXCL_LINE: DefensiveCoding */ |
| return status; /* LCOV_EXCL_LINE: DefensiveCoding */ |
| } |
| |
| *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: /* LCOV_EXCL_LINE: DefensiveCoding */ |
| return ARES_EBADFAMILY; /* LCOV_EXCL_LINE: DefensiveCoding */ |
| } |
| |
| if (name != NULL) { |
| ai->name = ares_strdup(name); |
| if (ai->name == NULL) { |
| status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */ |
| goto done; /* LCOV_EXCL_LINE: OutOfMemory */ |
| } |
| } |
| |
| 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; /* LCOV_EXCL_LINE: DefensiveCoding */ |
| } |
| } |
| |
| if (want_cnames) { |
| status = ares_hosts_ai_append_cnames(entry, &cnames); |
| if (status != ARES_SUCCESS) { |
| goto done; /* LCOV_EXCL_LINE: DefensiveCoding */ |
| } |
| } |
| |
| status = ARES_SUCCESS; |
| |
| done: |
| if (status != ARES_SUCCESS) { |
| /* LCOV_EXCL_START: defensive coding */ |
| ares_freeaddrinfo_cnames(cnames); |
| ares_freeaddrinfo_nodes(ainodes); |
| ares_free(ai->name); |
| ai->name = NULL; |
| return status; |
| /* LCOV_EXCL_STOP */ |
| } |
| 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; |
| } |