1 /* MIT License
2 *
3 * Copyright (c) 2023 Brad House
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the next
13 * paragraph) shall be included in all copies or substantial portions of the
14 * Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 *
24 * SPDX-License-Identifier: MIT
25 */
26 #include "ares_private.h"
27 #ifdef HAVE_SYS_TYPES_H
28 # include <sys/types.h>
29 #endif
30 #ifdef HAVE_SYS_STAT_H
31 # include <sys/stat.h>
32 #endif
33 #ifdef HAVE_NETINET_IN_H
34 # include <netinet/in.h>
35 #endif
36 #ifdef HAVE_NETDB_H
37 # include <netdb.h>
38 #endif
39 #ifdef HAVE_ARPA_INET_H
40 # include <arpa/inet.h>
41 #endif
42 #include <time.h>
43
44 /* HOSTS FILE PROCESSING OVERVIEW
45 * ==============================
46 * The hosts file on the system contains static entries to be processed locally
47 * rather than querying the nameserver. Each row is an IP address followed by
48 * a list of space delimited hostnames that match the ip address. This is used
49 * for both forward and reverse lookups.
50 *
51 * We are caching the entire parsed hosts file for performance reasons. Some
52 * files may be quite sizable and as per Issue #458 can approach 1/2MB in size,
53 * and the parse overhead on a rapid succession of queries can be quite large.
54 * The entries are stored in forwards and backwards hashtables so we can get
55 * O(1) performance on lookup. The file is cached until the file modification
56 * timestamp changes.
57 *
58 * The hosts file processing is quite unique. It has to merge all related hosts
59 * and ips into a single entry due to file formatting requirements. For
60 * instance take the below:
61 *
62 * 127.0.0.1 localhost.localdomain localhost
63 * ::1 localhost.localdomain localhost
64 * 192.168.1.1 host.example.com host
65 * 192.168.1.5 host.example.com host
66 * 2620:1234::1 host.example.com host6.example.com host6 host
67 *
68 * This will yield 2 entries.
69 * 1) ips: 127.0.0.1,::1
70 * hosts: localhost.localdomain,localhost
71 * 2) ips: 192.168.1.1,192.168.1.5,2620:1234::1
72 * hosts: host.example.com,host,host6.example.com,host6
73 *
74 * It could be argued that if searching for 192.168.1.1 that the 'host6'
75 * hostnames should not be returned, but this implementation will return them
76 * since they are related. It is unlikely this will matter in the real world.
77 */
78
79 struct ares_hosts_file {
80 time_t ts;
81 /*! cache the filename so we know if the filename changes it automatically
82 * invalidates the cache */
83 char *filename;
84 /*! iphash is the owner of the 'entry' object as there is only ever a single
85 * match to the object. */
86 ares_htable_strvp_t *iphash;
87 /*! hosthash does not own the entry so won't free on destruction */
88 ares_htable_strvp_t *hosthash;
89 };
90
91 struct ares_hosts_entry {
92 size_t refcnt; /*! If the entry is stored multiple times in the
93 * ip address hash, we have to reference count it */
94 ares_llist_t *ips;
95 ares_llist_t *hosts;
96 };
97
ares_dns_pton(const char * ipaddr,struct ares_addr * addr,size_t * out_len)98 const void *ares_dns_pton(const char *ipaddr, struct ares_addr *addr,
99 size_t *out_len)
100 {
101 const void *ptr = NULL;
102 size_t ptr_len = 0;
103
104 if (ipaddr == NULL || addr == NULL || out_len == NULL) {
105 return NULL; /* LCOV_EXCL_LINE: DefensiveCoding */
106 }
107
108 *out_len = 0;
109
110 if (addr->family == AF_INET &&
111 ares_inet_pton(AF_INET, ipaddr, &addr->addr.addr4) > 0) {
112 ptr = &addr->addr.addr4;
113 ptr_len = sizeof(addr->addr.addr4);
114 } else if (addr->family == AF_INET6 &&
115 ares_inet_pton(AF_INET6, ipaddr, &addr->addr.addr6) > 0) {
116 ptr = &addr->addr.addr6;
117 ptr_len = sizeof(addr->addr.addr6);
118 } else if (addr->family == AF_UNSPEC) {
119 if (ares_inet_pton(AF_INET, ipaddr, &addr->addr.addr4) > 0) {
120 addr->family = AF_INET;
121 ptr = &addr->addr.addr4;
122 ptr_len = sizeof(addr->addr.addr4);
123 } else if (ares_inet_pton(AF_INET6, ipaddr, &addr->addr.addr6) > 0) {
124 addr->family = AF_INET6;
125 ptr = &addr->addr.addr6;
126 ptr_len = sizeof(addr->addr.addr6);
127 }
128 }
129
130 *out_len = ptr_len;
131 return ptr;
132 }
133
ares_normalize_ipaddr(const char * ipaddr,char * out,size_t out_len)134 static ares_bool_t ares_normalize_ipaddr(const char *ipaddr, char *out,
135 size_t out_len)
136 {
137 struct ares_addr data;
138 const void *addr;
139 size_t addr_len = 0;
140
141 memset(&data, 0, sizeof(data));
142 data.family = AF_UNSPEC;
143
144 addr = ares_dns_pton(ipaddr, &data, &addr_len);
145 if (addr == NULL) {
146 return ARES_FALSE;
147 }
148
149 if (!ares_inet_ntop(data.family, addr, out, (ares_socklen_t)out_len)) {
150 return ARES_FALSE; /* LCOV_EXCL_LINE: DefensiveCoding */
151 }
152
153 return ARES_TRUE;
154 }
155
ares_hosts_entry_destroy(ares_hosts_entry_t * entry)156 static void ares_hosts_entry_destroy(ares_hosts_entry_t *entry)
157 {
158 if (entry == NULL) {
159 return;
160 }
161
162 /* Honor reference counting */
163 if (entry->refcnt != 0) {
164 entry->refcnt--;
165 }
166
167 if (entry->refcnt > 0) {
168 return;
169 }
170
171 ares_llist_destroy(entry->hosts);
172 ares_llist_destroy(entry->ips);
173 ares_free(entry);
174 }
175
ares_hosts_entry_destroy_cb(void * entry)176 static void ares_hosts_entry_destroy_cb(void *entry)
177 {
178 ares_hosts_entry_destroy(entry);
179 }
180
ares_hosts_file_destroy(ares_hosts_file_t * hf)181 void ares_hosts_file_destroy(ares_hosts_file_t *hf)
182 {
183 if (hf == NULL) {
184 return;
185 }
186
187 ares_free(hf->filename);
188 ares_htable_strvp_destroy(hf->hosthash);
189 ares_htable_strvp_destroy(hf->iphash);
190 ares_free(hf);
191 }
192
ares_hosts_file_create(const char * filename)193 static ares_hosts_file_t *ares_hosts_file_create(const char *filename)
194 {
195 ares_hosts_file_t *hf = ares_malloc_zero(sizeof(*hf));
196 if (hf == NULL) {
197 goto fail;
198 }
199
200 hf->ts = time(NULL);
201
202 hf->filename = ares_strdup(filename);
203 if (hf->filename == NULL) {
204 goto fail;
205 }
206
207 hf->iphash = ares_htable_strvp_create(ares_hosts_entry_destroy_cb);
208 if (hf->iphash == NULL) {
209 goto fail;
210 }
211
212 hf->hosthash = ares_htable_strvp_create(NULL);
213 if (hf->hosthash == NULL) {
214 goto fail;
215 }
216
217 return hf;
218
219 fail:
220 ares_hosts_file_destroy(hf);
221 return NULL;
222 }
223
224 typedef enum {
225 ARES_MATCH_NONE = 0,
226 ARES_MATCH_IPADDR = 1,
227 ARES_MATCH_HOST = 2
228 } ares_hosts_file_match_t;
229
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)230 static ares_status_t ares_hosts_file_merge_entry(
231 const ares_hosts_file_t *hf, ares_hosts_entry_t *existing,
232 ares_hosts_entry_t *entry, ares_hosts_file_match_t matchtype)
233 {
234 ares_llist_node_t *node;
235
236 /* If we matched on IP address, we know there can only be 1, so there's no
237 * reason to do anything */
238 if (matchtype != ARES_MATCH_IPADDR) {
239 while ((node = ares_llist_node_first(entry->ips)) != NULL) {
240 const char *ipaddr = ares_llist_node_val(node);
241
242 if (ares_htable_strvp_get_direct(hf->iphash, ipaddr) != NULL) {
243 ares_llist_node_destroy(node);
244 continue;
245 }
246
247 ares_llist_node_mvparent_last(node, existing->ips);
248 }
249 }
250
251
252 while ((node = ares_llist_node_first(entry->hosts)) != NULL) {
253 const char *hostname = ares_llist_node_val(node);
254
255 if (ares_htable_strvp_get_direct(hf->hosthash, hostname) != NULL) {
256 ares_llist_node_destroy(node);
257 continue;
258 }
259
260 ares_llist_node_mvparent_last(node, existing->hosts);
261 }
262
263 ares_hosts_entry_destroy(entry);
264 return ARES_SUCCESS;
265 }
266
267 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)268 ares_hosts_file_match(const ares_hosts_file_t *hf, ares_hosts_entry_t *entry,
269 ares_hosts_entry_t **match)
270 {
271 ares_llist_node_t *node;
272 *match = NULL;
273
274 for (node = ares_llist_node_first(entry->ips); node != NULL;
275 node = ares_llist_node_next(node)) {
276 const char *ipaddr = ares_llist_node_val(node);
277 *match = ares_htable_strvp_get_direct(hf->iphash, ipaddr);
278 if (*match != NULL) {
279 return ARES_MATCH_IPADDR;
280 }
281 }
282
283 for (node = ares_llist_node_first(entry->hosts); node != NULL;
284 node = ares_llist_node_next(node)) {
285 const char *host = ares_llist_node_val(node);
286 *match = ares_htable_strvp_get_direct(hf->hosthash, host);
287 if (*match != NULL) {
288 return ARES_MATCH_HOST;
289 }
290 }
291
292 return ARES_MATCH_NONE;
293 }
294
295 /*! entry is invalidated upon calling this function, always, even on error */
ares_hosts_file_add(ares_hosts_file_t * hosts,ares_hosts_entry_t * entry)296 static ares_status_t ares_hosts_file_add(ares_hosts_file_t *hosts,
297 ares_hosts_entry_t *entry)
298 {
299 ares_hosts_entry_t *match = NULL;
300 ares_status_t status = ARES_SUCCESS;
301 ares_llist_node_t *node;
302 ares_hosts_file_match_t matchtype;
303 size_t num_hostnames;
304
305 /* Record the number of hostnames in this entry file. If we merge into an
306 * existing record, these will be *appended* to the entry, so we'll count
307 * backwards when adding to the hosts hashtable */
308 num_hostnames = ares_llist_len(entry->hosts);
309
310 matchtype = ares_hosts_file_match(hosts, entry, &match);
311
312 if (matchtype != ARES_MATCH_NONE) {
313 status = ares_hosts_file_merge_entry(hosts, match, entry, matchtype);
314 if (status != ARES_SUCCESS) {
315 ares_hosts_entry_destroy(entry); /* LCOV_EXCL_LINE: DefensiveCoding */
316 return status; /* LCOV_EXCL_LINE: DefensiveCoding */
317 }
318 /* entry was invalidated above by merging */
319 entry = match;
320 }
321
322 if (matchtype != ARES_MATCH_IPADDR) {
323 const char *ipaddr = ares_llist_last_val(entry->ips);
324
325 if (!ares_htable_strvp_get(hosts->iphash, ipaddr, NULL)) {
326 if (!ares_htable_strvp_insert(hosts->iphash, ipaddr, entry)) {
327 ares_hosts_entry_destroy(entry);
328 return ARES_ENOMEM;
329 }
330 entry->refcnt++;
331 }
332 }
333
334 /* Go backwards, on a merge, hostnames are appended. Breakout once we've
335 * consumed all the hosts that we appended */
336 for (node = ares_llist_node_last(entry->hosts); node != NULL;
337 node = ares_llist_node_prev(node)) {
338 const char *val = ares_llist_node_val(node);
339
340 if (num_hostnames == 0) {
341 break;
342 }
343
344 num_hostnames--;
345
346 /* first hostname match wins. If we detect a duplicate hostname for another
347 * ip it will automatically be added to the same entry */
348 if (ares_htable_strvp_get(hosts->hosthash, val, NULL)) {
349 continue;
350 }
351
352 if (!ares_htable_strvp_insert(hosts->hosthash, val, entry)) {
353 return ARES_ENOMEM;
354 }
355 }
356
357 return ARES_SUCCESS;
358 }
359
ares_hosts_entry_isdup(ares_hosts_entry_t * entry,const char * host)360 static ares_bool_t ares_hosts_entry_isdup(ares_hosts_entry_t *entry,
361 const char *host)
362 {
363 ares_llist_node_t *node;
364
365 for (node = ares_llist_node_first(entry->ips); node != NULL;
366 node = ares_llist_node_next(node)) {
367 const char *myhost = ares_llist_node_val(node);
368 if (ares_strcaseeq(myhost, host)) {
369 return ARES_TRUE;
370 }
371 }
372
373 return ARES_FALSE;
374 }
375
ares_parse_hosts_hostnames(ares_buf_t * buf,ares_hosts_entry_t * entry)376 static ares_status_t ares_parse_hosts_hostnames(ares_buf_t *buf,
377 ares_hosts_entry_t *entry)
378 {
379 entry->hosts = ares_llist_create(ares_free);
380 if (entry->hosts == NULL) {
381 return ARES_ENOMEM;
382 }
383
384 /* Parse hostnames and aliases */
385 while (ares_buf_len(buf)) {
386 char hostname[256];
387 char *temp;
388 ares_status_t status;
389 unsigned char comment = '#';
390
391 ares_buf_consume_whitespace(buf, ARES_FALSE);
392
393 if (ares_buf_len(buf) == 0) {
394 break;
395 }
396
397 /* See if it is a comment, if so stop processing */
398 if (ares_buf_begins_with(buf, &comment, 1)) {
399 break;
400 }
401
402 ares_buf_tag(buf);
403
404 /* Must be at end of line */
405 if (ares_buf_consume_nonwhitespace(buf) == 0) {
406 break;
407 }
408
409 status = ares_buf_tag_fetch_string(buf, hostname, sizeof(hostname));
410 if (status != ARES_SUCCESS) {
411 /* Bad entry, just ignore as long as its not the first. If its the first,
412 * it must be valid */
413 if (ares_llist_len(entry->hosts) == 0) {
414 return ARES_EBADSTR;
415 }
416
417 continue;
418 }
419
420 /* Validate it is a valid hostname characterset */
421 if (!ares_is_hostname(hostname)) {
422 continue;
423 }
424
425 /* Don't add a duplicate to the same entry */
426 if (ares_hosts_entry_isdup(entry, hostname)) {
427 continue;
428 }
429
430 /* Add to list */
431 temp = ares_strdup(hostname);
432 if (temp == NULL) {
433 return ARES_ENOMEM;
434 }
435
436 if (ares_llist_insert_last(entry->hosts, temp) == NULL) {
437 ares_free(temp);
438 return ARES_ENOMEM;
439 }
440 }
441
442 /* Must have at least 1 entry */
443 if (ares_llist_len(entry->hosts) == 0) {
444 return ARES_EBADSTR;
445 }
446
447 return ARES_SUCCESS;
448 }
449
ares_parse_hosts_ipaddr(ares_buf_t * buf,ares_hosts_entry_t ** entry_out)450 static ares_status_t ares_parse_hosts_ipaddr(ares_buf_t *buf,
451 ares_hosts_entry_t **entry_out)
452 {
453 char addr[INET6_ADDRSTRLEN];
454 char *temp;
455 ares_hosts_entry_t *entry = NULL;
456 ares_status_t status;
457
458 *entry_out = NULL;
459
460 ares_buf_tag(buf);
461 ares_buf_consume_nonwhitespace(buf);
462 status = ares_buf_tag_fetch_string(buf, addr, sizeof(addr));
463 if (status != ARES_SUCCESS) {
464 return status;
465 }
466
467 /* Validate and normalize the ip address format */
468 if (!ares_normalize_ipaddr(addr, addr, sizeof(addr))) {
469 return ARES_EBADSTR;
470 }
471
472 entry = ares_malloc_zero(sizeof(*entry));
473 if (entry == NULL) {
474 return ARES_ENOMEM;
475 }
476
477 entry->ips = ares_llist_create(ares_free);
478 if (entry->ips == NULL) {
479 ares_hosts_entry_destroy(entry);
480 return ARES_ENOMEM;
481 }
482
483 temp = ares_strdup(addr);
484 if (temp == NULL) {
485 ares_hosts_entry_destroy(entry);
486 return ARES_ENOMEM;
487 }
488
489 if (ares_llist_insert_first(entry->ips, temp) == NULL) {
490 ares_free(temp);
491 ares_hosts_entry_destroy(entry);
492 return ARES_ENOMEM;
493 }
494
495 *entry_out = entry;
496
497 return ARES_SUCCESS;
498 }
499
ares_parse_hosts(const char * filename,ares_hosts_file_t ** out)500 static ares_status_t ares_parse_hosts(const char *filename,
501 ares_hosts_file_t **out)
502 {
503 ares_buf_t *buf = NULL;
504 ares_status_t status = ARES_EBADRESP;
505 ares_hosts_file_t *hf = NULL;
506 ares_hosts_entry_t *entry = NULL;
507
508 *out = NULL;
509
510 buf = ares_buf_create();
511 if (buf == NULL) {
512 status = ARES_ENOMEM;
513 goto done;
514 }
515
516 status = ares_buf_load_file(filename, buf);
517 if (status != ARES_SUCCESS) {
518 goto done;
519 }
520
521 hf = ares_hosts_file_create(filename);
522 if (hf == NULL) {
523 status = ARES_ENOMEM;
524 goto done;
525 }
526
527 while (ares_buf_len(buf)) {
528 unsigned char comment = '#';
529
530 /* -- Start of new line here -- */
531
532 /* Consume any leading whitespace */
533 ares_buf_consume_whitespace(buf, ARES_FALSE);
534
535 if (ares_buf_len(buf) == 0) {
536 break;
537 }
538
539 /* See if it is a comment, if so, consume remaining line */
540 if (ares_buf_begins_with(buf, &comment, 1)) {
541 ares_buf_consume_line(buf, ARES_TRUE);
542 continue;
543 }
544
545 /* Pull off ip address */
546 status = ares_parse_hosts_ipaddr(buf, &entry);
547 if (status == ARES_ENOMEM) {
548 goto done;
549 }
550 if (status != ARES_SUCCESS) {
551 /* Bad line, consume and go onto next */
552 ares_buf_consume_line(buf, ARES_TRUE);
553 continue;
554 }
555
556 /* Parse of the hostnames */
557 status = ares_parse_hosts_hostnames(buf, entry);
558 if (status == ARES_ENOMEM) {
559 goto done;
560 } else if (status != ARES_SUCCESS) {
561 /* Bad line, consume and go onto next */
562 ares_hosts_entry_destroy(entry);
563 entry = NULL;
564 ares_buf_consume_line(buf, ARES_TRUE);
565 continue;
566 }
567
568 /* Append the successful entry to the hosts file */
569 status = ares_hosts_file_add(hf, entry);
570 entry = NULL; /* is always invalidated by this function, even on error */
571 if (status != ARES_SUCCESS) {
572 goto done;
573 }
574
575 /* Go to next line */
576 ares_buf_consume_line(buf, ARES_TRUE);
577 }
578
579 status = ARES_SUCCESS;
580
581 done:
582 ares_hosts_entry_destroy(entry);
583 ares_buf_destroy(buf);
584 if (status != ARES_SUCCESS) {
585 ares_hosts_file_destroy(hf);
586 } else {
587 *out = hf;
588 }
589 return status;
590 }
591
ares_hosts_expired(const char * filename,const ares_hosts_file_t * hf)592 static ares_bool_t ares_hosts_expired(const char *filename,
593 const ares_hosts_file_t *hf)
594 {
595 time_t mod_ts = 0;
596
597 #ifdef HAVE_STAT
598 struct stat st;
599 if (stat(filename, &st) == 0) {
600 mod_ts = st.st_mtime;
601 }
602 #elif defined(_WIN32)
603 struct _stat st;
604 if (_stat(filename, &st) == 0) {
605 mod_ts = st.st_mtime;
606 }
607 #else
608 (void)filename;
609 #endif
610
611 if (hf == NULL) {
612 return ARES_TRUE;
613 }
614
615 /* Expire every 60s if we can't get a time */
616 if (mod_ts == 0) {
617 mod_ts =
618 time(NULL) - 60; /* LCOV_EXCL_LINE: only on systems without stat() */
619 }
620
621 /* If filenames are different, its expired */
622 if (!ares_strcaseeq(hf->filename, filename)) {
623 return ARES_TRUE;
624 }
625
626 if (hf->ts <= mod_ts) {
627 return ARES_TRUE;
628 }
629
630 return ARES_FALSE;
631 }
632
ares_hosts_path(const ares_channel_t * channel,ares_bool_t use_env,char ** path)633 static ares_status_t ares_hosts_path(const ares_channel_t *channel,
634 ares_bool_t use_env, char **path)
635 {
636 char *path_hosts = NULL;
637
638 *path = NULL;
639
640 if (channel->hosts_path) {
641 path_hosts = ares_strdup(channel->hosts_path);
642 if (!path_hosts) {
643 return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
644 }
645 }
646
647 if (use_env) {
648 if (path_hosts) {
649 ares_free(path_hosts);
650 }
651
652 path_hosts = ares_strdup(getenv("CARES_HOSTS"));
653 if (!path_hosts) {
654 return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
655 }
656 }
657
658 if (!path_hosts) {
659 #if defined(USE_WINSOCK)
660 char PATH_HOSTS[MAX_PATH] = "";
661 char tmp[MAX_PATH];
662 HKEY hkeyHosts;
663 DWORD dwLength = sizeof(tmp);
664 if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY, 0, KEY_READ,
665 &hkeyHosts) != ERROR_SUCCESS) {
666 return ARES_ENOTFOUND;
667 }
668 RegQueryValueExA(hkeyHosts, DATABASEPATH, NULL, NULL, (LPBYTE)tmp,
669 &dwLength);
670 ExpandEnvironmentStringsA(tmp, PATH_HOSTS, MAX_PATH);
671 RegCloseKey(hkeyHosts);
672 strcat(PATH_HOSTS, WIN_PATH_HOSTS);
673 #elif defined(WATT32)
674 const char *PATH_HOSTS = _w32_GetHostsFile();
675
676 if (!PATH_HOSTS) {
677 return ARES_ENOTFOUND;
678 }
679 #endif
680 path_hosts = ares_strdup(PATH_HOSTS);
681 if (!path_hosts) {
682 return ARES_ENOMEM;
683 }
684 }
685
686 *path = path_hosts;
687 return ARES_SUCCESS;
688 }
689
ares_hosts_update(ares_channel_t * channel,ares_bool_t use_env)690 static ares_status_t ares_hosts_update(ares_channel_t *channel,
691 ares_bool_t use_env)
692 {
693 ares_status_t status;
694 char *filename = NULL;
695
696 status = ares_hosts_path(channel, use_env, &filename);
697 if (status != ARES_SUCCESS) {
698 return status;
699 }
700
701 if (!ares_hosts_expired(filename, channel->hf)) {
702 ares_free(filename);
703 return ARES_SUCCESS;
704 }
705
706 ares_hosts_file_destroy(channel->hf);
707 channel->hf = NULL;
708
709 status = ares_parse_hosts(filename, &channel->hf);
710 ares_free(filename);
711 return status;
712 }
713
ares_hosts_search_ipaddr(ares_channel_t * channel,ares_bool_t use_env,const char * ipaddr,const ares_hosts_entry_t ** entry)714 ares_status_t ares_hosts_search_ipaddr(ares_channel_t *channel,
715 ares_bool_t use_env, const char *ipaddr,
716 const ares_hosts_entry_t **entry)
717 {
718 ares_status_t status;
719 char addr[INET6_ADDRSTRLEN];
720
721 *entry = NULL;
722
723 status = ares_hosts_update(channel, use_env);
724 if (status != ARES_SUCCESS) {
725 return status;
726 }
727
728 if (channel->hf == NULL) {
729 return ARES_ENOTFOUND; /* LCOV_EXCL_LINE: DefensiveCoding */
730 }
731
732 if (!ares_normalize_ipaddr(ipaddr, addr, sizeof(addr))) {
733 return ARES_EBADNAME;
734 }
735
736 *entry = ares_htable_strvp_get_direct(channel->hf->iphash, addr);
737 if (*entry == NULL) {
738 return ARES_ENOTFOUND;
739 }
740
741 return ARES_SUCCESS;
742 }
743
ares_hosts_search_host(ares_channel_t * channel,ares_bool_t use_env,const char * host,const ares_hosts_entry_t ** entry)744 ares_status_t ares_hosts_search_host(ares_channel_t *channel,
745 ares_bool_t use_env, const char *host,
746 const ares_hosts_entry_t **entry)
747 {
748 ares_status_t status;
749
750 *entry = NULL;
751
752 status = ares_hosts_update(channel, use_env);
753 if (status != ARES_SUCCESS) {
754 return status;
755 }
756
757 if (channel->hf == NULL) {
758 return ARES_ENOTFOUND; /* LCOV_EXCL_LINE: DefensiveCoding */
759 }
760
761 *entry = ares_htable_strvp_get_direct(channel->hf->hosthash, host);
762 if (*entry == NULL) {
763 return ARES_ENOTFOUND;
764 }
765
766 return ARES_SUCCESS;
767 }
768
769 static ares_status_t
ares_hosts_ai_append_cnames(const ares_hosts_entry_t * entry,struct ares_addrinfo_cname ** cnames_out)770 ares_hosts_ai_append_cnames(const ares_hosts_entry_t *entry,
771 struct ares_addrinfo_cname **cnames_out)
772 {
773 struct ares_addrinfo_cname *cname = NULL;
774 struct ares_addrinfo_cname *cnames = NULL;
775 const char *primaryhost;
776 ares_llist_node_t *node;
777 ares_status_t status;
778 size_t cnt = 0;
779
780 node = ares_llist_node_first(entry->hosts);
781 primaryhost = ares_llist_node_val(node);
782 /* Skip to next node to start with aliases */
783 node = ares_llist_node_next(node);
784
785 while (node != NULL) {
786 const char *host = ares_llist_node_val(node);
787
788 /* Cap at 100 entries. , some people use
789 * https://github.com/StevenBlack/hosts and we don't need 200k+ aliases */
790 cnt++;
791 if (cnt > 100) {
792 break; /* LCOV_EXCL_LINE: DefensiveCoding */
793 }
794
795 cname = ares_append_addrinfo_cname(&cnames);
796 if (cname == NULL) {
797 status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
798 goto done; /* LCOV_EXCL_LINE: OutOfMemory */
799 }
800
801 cname->alias = ares_strdup(host);
802 if (cname->alias == NULL) {
803 status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
804 goto done; /* LCOV_EXCL_LINE: OutOfMemory */
805 }
806
807 cname->name = ares_strdup(primaryhost);
808 if (cname->name == NULL) {
809 status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
810 goto done; /* LCOV_EXCL_LINE: OutOfMemory */
811 }
812
813 node = ares_llist_node_next(node);
814 }
815
816 /* No entries, add only primary */
817 if (cnames == NULL) {
818 cname = ares_append_addrinfo_cname(&cnames);
819 if (cname == NULL) {
820 status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
821 goto done; /* LCOV_EXCL_LINE: OutOfMemory */
822 }
823
824 cname->name = ares_strdup(primaryhost);
825 if (cname->name == NULL) {
826 status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
827 goto done; /* LCOV_EXCL_LINE: OutOfMemory */
828 }
829 }
830 status = ARES_SUCCESS;
831
832 done:
833 if (status != ARES_SUCCESS) {
834 ares_freeaddrinfo_cnames(cnames); /* LCOV_EXCL_LINE: DefensiveCoding */
835 return status; /* LCOV_EXCL_LINE: DefensiveCoding */
836 }
837
838 *cnames_out = cnames;
839 return ARES_SUCCESS;
840 }
841
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)842 ares_status_t ares_hosts_entry_to_addrinfo(const ares_hosts_entry_t *entry,
843 const char *name, int family,
844 unsigned short port,
845 ares_bool_t want_cnames,
846 struct ares_addrinfo *ai)
847 {
848 ares_status_t status = ARES_ENOTFOUND;
849 struct ares_addrinfo_cname *cnames = NULL;
850 struct ares_addrinfo_node *ainodes = NULL;
851 ares_llist_node_t *node;
852
853 switch (family) {
854 case AF_INET:
855 case AF_INET6:
856 case AF_UNSPEC:
857 break;
858 default: /* LCOV_EXCL_LINE: DefensiveCoding */
859 return ARES_EBADFAMILY; /* LCOV_EXCL_LINE: DefensiveCoding */
860 }
861
862 if (name != NULL) {
863 ares_free(ai->name);
864 ai->name = ares_strdup(name);
865 if (ai->name == NULL) {
866 status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
867 goto done; /* LCOV_EXCL_LINE: OutOfMemory */
868 }
869 }
870
871 for (node = ares_llist_node_first(entry->ips); node != NULL;
872 node = ares_llist_node_next(node)) {
873 struct ares_addr addr;
874 const void *ptr = NULL;
875 size_t ptr_len = 0;
876 const char *ipaddr = ares_llist_node_val(node);
877
878 memset(&addr, 0, sizeof(addr));
879 addr.family = family;
880 ptr = ares_dns_pton(ipaddr, &addr, &ptr_len);
881
882 if (ptr == NULL) {
883 continue;
884 }
885
886 status = ares_append_ai_node(addr.family, port, 0, ptr, &ainodes);
887 if (status != ARES_SUCCESS) {
888 goto done; /* LCOV_EXCL_LINE: DefensiveCoding */
889 }
890 }
891
892 /* Might be ARES_ENOTFOUND here if no ips matched requested address family */
893 if (status != ARES_SUCCESS) {
894 goto done;
895 }
896
897 if (want_cnames) {
898 status = ares_hosts_ai_append_cnames(entry, &cnames);
899 if (status != ARES_SUCCESS) {
900 goto done; /* LCOV_EXCL_LINE: DefensiveCoding */
901 }
902 }
903
904 status = ARES_SUCCESS;
905
906 done:
907 if (status != ARES_SUCCESS) {
908 /* LCOV_EXCL_START: defensive coding */
909 ares_freeaddrinfo_cnames(cnames);
910 ares_freeaddrinfo_nodes(ainodes);
911 ares_free(ai->name);
912 ai->name = NULL;
913 return status;
914 /* LCOV_EXCL_STOP */
915 }
916 ares_addrinfo_cat_cnames(&ai->cnames, cnames);
917 ares_addrinfo_cat_nodes(&ai->nodes, ainodes);
918
919 return status;
920 }
921
ares_hosts_entry_to_hostent(const ares_hosts_entry_t * entry,int family,struct hostent ** hostent)922 ares_status_t ares_hosts_entry_to_hostent(const ares_hosts_entry_t *entry,
923 int family, struct hostent **hostent)
924 {
925 ares_status_t status;
926 struct ares_addrinfo *ai = ares_malloc_zero(sizeof(*ai));
927
928 *hostent = NULL;
929
930 if (ai == NULL) {
931 return ARES_ENOMEM;
932 }
933
934 status = ares_hosts_entry_to_addrinfo(entry, NULL, family, 0, ARES_TRUE, ai);
935 if (status != ARES_SUCCESS) {
936 goto done;
937 }
938
939 status = ares_addrinfo2hostent(ai, family, hostent);
940 if (status != ARES_SUCCESS) {
941 goto done;
942 }
943
944 done:
945 ares_freeaddrinfo(ai);
946 if (status != ARES_SUCCESS) {
947 ares_free_hostent(*hostent);
948 *hostent = NULL;
949 }
950
951 return status;
952 }
953