1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include "alloc-util.h"
7 #include "dns-domain.h"
8 #include "extract-word.h"
11 #include "hostname-util.h"
13 #include "resolved-dns-answer.h"
14 #include "resolved-dns-question.h"
15 #include "resolved-dns-rr.h"
16 #include "resolved-etc-hosts.h"
17 #include "resolved-manager.h"
19 #include "socket-netlink.h"
20 #include "stat-util.h"
21 #include "string-util.h"
22 #include "time-util.h"
24 /* Recheck /etc/hosts at most once every 2s */
25 #define ETC_HOSTS_RECHECK_USEC (2*USEC_PER_SEC)
27 static EtcHostsItemByAddress
*etc_hosts_item_by_address_free(EtcHostsItemByAddress
*item
) {
31 set_free(item
->names
);
35 DEFINE_TRIVIAL_CLEANUP_FUNC(EtcHostsItemByAddress
*, etc_hosts_item_by_address_free
);
37 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
40 in_addr_data_hash_func
,
41 in_addr_data_compare_func
,
42 EtcHostsItemByAddress
,
43 etc_hosts_item_by_address_free
);
45 static EtcHostsItemByName
*etc_hosts_item_by_name_free(EtcHostsItemByName
*item
) {
50 set_free(item
->addresses
);
54 DEFINE_TRIVIAL_CLEANUP_FUNC(EtcHostsItemByName
*, etc_hosts_item_by_name_free
);
56 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
60 dns_name_compare_func
,
62 etc_hosts_item_by_name_free
);
64 void etc_hosts_clear(EtcHosts
*hosts
) {
67 hosts
->by_address
= hashmap_free(hosts
->by_address
);
68 hosts
->by_name
= hashmap_free(hosts
->by_name
);
69 hosts
->no_address
= set_free(hosts
->no_address
);
72 void manager_etc_hosts_flush(Manager
*m
) {
73 etc_hosts_clear(&m
->etc_hosts
);
74 m
->etc_hosts_stat
= (struct stat
) {};
77 static int parse_line(EtcHosts
*hosts
, unsigned nr
, const char *line
) {
78 _cleanup_free_
char *address_str
= NULL
;
79 struct in_addr_data address
= {};
81 EtcHostsItemByAddress
*item
;
87 r
= extract_first_word(&line
, &address_str
, NULL
, EXTRACT_RELAX
);
89 return log_error_errno(r
, "/etc/hosts:%u: failed to extract address: %m", nr
);
90 assert(r
> 0); /* We already checked that the line is not empty, so it should contain *something* */
92 r
= in_addr_ifindex_from_string_auto(address_str
, &address
.family
, &address
.address
, NULL
);
94 log_warning_errno(r
, "/etc/hosts:%u: address '%s' is invalid, ignoring: %m", nr
, address_str
);
98 r
= in_addr_data_is_null(&address
);
100 log_warning_errno(r
, "/etc/hosts:%u: address '%s' is invalid, ignoring: %m", nr
, address_str
);
104 /* This is an 0.0.0.0 or :: item, which we assume means that we shall map the specified hostname to
108 /* If this is a normal address, then simply add entry mapping it to the specified names */
110 item
= hashmap_get(hosts
->by_address
, &address
);
112 _cleanup_(etc_hosts_item_by_address_freep
) EtcHostsItemByAddress
*new_item
= NULL
;
114 new_item
= new(EtcHostsItemByAddress
, 1);
118 *new_item
= (EtcHostsItemByAddress
) {
122 r
= hashmap_ensure_put(&hosts
->by_address
, &by_address_hash_ops
, &new_item
->address
, new_item
);
126 item
= TAKE_PTR(new_item
);
131 _cleanup_free_
char *name
= NULL
;
132 EtcHostsItemByName
*bn
;
134 r
= extract_first_word(&line
, &name
, NULL
, EXTRACT_RELAX
);
136 return log_error_errno(r
, "/etc/hosts:%u: couldn't extract hostname: %m", nr
);
140 r
= dns_name_is_valid_ldh(name
);
143 log_warning_errno(r
, "/etc/hosts:%u: Failed to check the validity of hostname \"%s\", ignoring: %m", nr
, name
);
145 log_warning("/etc/hosts:%u: hostname \"%s\" is not valid, ignoring.", nr
, name
);
152 /* Optimize the case where we don't need to store any addresses, by storing
153 * only the name in a dedicated Set instead of the hashmap */
155 r
= set_ensure_consume(&hosts
->no_address
, &dns_name_hash_ops_free
, TAKE_PTR(name
));
162 bn
= hashmap_get(hosts
->by_name
, name
);
164 _cleanup_(etc_hosts_item_by_name_freep
) EtcHostsItemByName
*new_item
= NULL
;
165 _cleanup_free_
char *name_copy
= NULL
;
167 name_copy
= strdup(name
);
171 new_item
= new(EtcHostsItemByName
, 1);
175 *new_item
= (EtcHostsItemByName
) {
176 .name
= TAKE_PTR(name_copy
),
179 r
= hashmap_ensure_put(&hosts
->by_name
, &by_name_hash_ops
, new_item
->name
, new_item
);
183 bn
= TAKE_PTR(new_item
);
186 if (!set_contains(bn
->addresses
, &address
)) {
187 _cleanup_free_
struct in_addr_data
*address_copy
= NULL
;
189 address_copy
= newdup(struct in_addr_data
, &address
, 1);
193 r
= set_ensure_consume(&bn
->addresses
, &in_addr_data_hash_ops_free
, TAKE_PTR(address_copy
));
198 r
= set_ensure_put(&item
->names
, &dns_name_hash_ops_free
, name
);
201 if (r
== 0) /* the name is already listed */
204 * Keep track of the first name listed for this address.
205 * This name will be used in responses as the canonical name.
207 if (!item
->canonical_name
)
208 item
->canonical_name
= name
;
213 log_warning("/etc/hosts:%u: line is missing any valid hostnames", nr
);
218 static void strip_localhost(EtcHosts
*hosts
) {
219 static const struct in_addr_data local_in_addrs
[] = {
222 #if __BYTE_ORDER == __LITTLE_ENDIAN
223 /* We want constant expressions here, that's why we don't use htole32() here */
224 .address
.in
.s_addr
= UINT32_C(0x0100007F),
226 .address
.in
.s_addr
= UINT32_C(0x7F000001),
231 .address
.in6
= IN6ADDR_LOOPBACK_INIT
,
237 /* Removes the 'localhost' entry from what we loaded. But only if the mapping is exclusively between
238 * 127.0.0.1 and localhost (or aliases to that we recognize). If there's any other name assigned to
239 * it, we leave the entry in.
241 * This way our regular synthesizing can take over, but only if it would result in the exact same
244 FOREACH_ELEMENT(local_in_addr
, local_in_addrs
) {
245 bool all_localhost
, all_local_address
;
246 EtcHostsItemByAddress
*item
;
249 item
= hashmap_get(hosts
->by_address
, local_in_addr
);
253 /* Check whether all hostnames the loopback address points to are localhost ones */
254 all_localhost
= true;
255 SET_FOREACH(name
, item
->names
)
256 if (!is_localhost(name
)) {
257 all_localhost
= false;
261 if (!all_localhost
) /* Not all names are localhost, hence keep the entries for this address. */
264 /* Now check if the names listed for this address actually all point back just to this
265 * address (or the other loopback address). If not, let's stay away from this too. */
266 all_local_address
= true;
267 SET_FOREACH(name
, item
->names
) {
268 EtcHostsItemByName
*n
;
269 struct in_addr_data
*a
;
271 n
= hashmap_get(hosts
->by_name
, name
);
272 if (!n
) /* No reverse entry? Then almost certainly the entry already got deleted from
273 * the previous iteration of this loop, i.e. via the other protocol */
276 /* Now check if the addresses of this item are all localhost addresses */
277 SET_FOREACH(a
, n
->addresses
)
278 if (!in_addr_is_localhost(a
->family
, &a
->address
)) {
279 all_local_address
= false;
283 if (!all_local_address
)
287 if (!all_local_address
)
290 SET_FOREACH(name
, item
->names
)
291 etc_hosts_item_by_name_free(hashmap_remove(hosts
->by_name
, name
));
293 assert_se(hashmap_remove(hosts
->by_address
, local_in_addr
) == item
);
294 etc_hosts_item_by_address_free(item
);
298 int etc_hosts_parse(EtcHosts
*hosts
, FILE *f
) {
299 _cleanup_(etc_hosts_clear
) EtcHosts t
= {};
306 _cleanup_free_
char *line
= NULL
;
309 r
= read_line(f
, LONG_LINE_MAX
, &line
);
311 return log_error_errno(r
, "Failed to read /etc/hosts: %m");
317 l
= strchr(line
, '#');
325 r
= parse_line(&t
, nr
, l
);
332 etc_hosts_clear(hosts
);
333 *hosts
= TAKE_STRUCT(t
);
337 static int manager_etc_hosts_read(Manager
*m
) {
338 _cleanup_fclose_
FILE *f
= NULL
;
343 assert_se(sd_event_now(m
->event
, CLOCK_BOOTTIME
, &ts
) >= 0);
345 /* See if we checked /etc/hosts recently already */
346 if (m
->etc_hosts_last
!= USEC_INFINITY
&& m
->etc_hosts_last
+ ETC_HOSTS_RECHECK_USEC
> ts
)
349 m
->etc_hosts_last
= ts
;
351 if (stat_is_set(&m
->etc_hosts_stat
)) {
352 if (stat("/etc/hosts", &st
) < 0) {
354 return log_error_errno(errno
, "Failed to stat /etc/hosts: %m");
356 manager_etc_hosts_flush(m
);
360 /* Did the mtime or ino/dev change? If not, there's no point in re-reading the file. */
361 if (stat_inode_unmodified(&m
->etc_hosts_stat
, &st
))
365 f
= fopen("/etc/hosts", "re");
368 return log_error_errno(errno
, "Failed to open %s: %m", "/etc/hosts");
370 manager_etc_hosts_flush(m
);
374 /* Take the timestamp at the beginning of processing, so that any changes made later are read on the next
376 r
= fstat(fileno(f
), &st
);
378 return log_error_errno(errno
, "Failed to fstat() /etc/hosts: %m");
380 r
= etc_hosts_parse(&m
->etc_hosts
, f
);
384 m
->etc_hosts_stat
= st
;
385 m
->etc_hosts_last
= ts
;
390 static int answer_add_ptr(DnsAnswer
*answer
, DnsResourceKey
*key
, const char *name
) {
391 _cleanup_(dns_resource_record_unrefp
) DnsResourceRecord
*rr
= NULL
;
393 rr
= dns_resource_record_new(key
);
397 rr
->ptr
.name
= strdup(name
);
401 return dns_answer_add(answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
, NULL
);
404 static int answer_add_cname(DnsAnswer
*answer
, const char *name
, const char *cname
) {
405 _cleanup_(dns_resource_record_unrefp
) DnsResourceRecord
*rr
= NULL
;
407 rr
= dns_resource_record_new_full(DNS_CLASS_IN
, DNS_TYPE_CNAME
, name
);
411 rr
->cname
.name
= strdup(cname
);
415 return dns_answer_add(answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
, NULL
);
418 static int answer_add_addr(DnsAnswer
*answer
, const char *name
, const struct in_addr_data
*a
) {
419 _cleanup_(dns_resource_record_unrefp
) DnsResourceRecord
*rr
= NULL
;
422 r
= dns_resource_record_new_address(&rr
, a
->family
, &a
->address
, name
);
426 return dns_answer_add(answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
, NULL
);
429 static int etc_hosts_lookup_by_address(
433 const struct in_addr_data
*address
,
434 DnsAnswer
**answer
) {
436 DnsResourceKey
*t
, *found_ptr
= NULL
;
437 EtcHostsItemByAddress
*item
;
446 item
= hashmap_get(hosts
->by_address
, address
);
450 /* We have an address in /etc/hosts that matches the queried name. Let's return successful. Actual data
451 * we'll only return if the request was for PTR. */
453 DNS_QUESTION_FOREACH(t
, q
) {
454 if (!IN_SET(t
->type
, DNS_TYPE_PTR
, DNS_TYPE_ANY
))
456 if (!IN_SET(t
->class, DNS_CLASS_IN
, DNS_CLASS_ANY
))
459 r
= dns_name_equal(dns_resource_key_name(t
), name
);
471 r
= dns_answer_reserve(answer
, set_size(item
->names
));
475 if (item
->canonical_name
) {
476 r
= answer_add_ptr(*answer
, found_ptr
, item
->canonical_name
);
481 SET_FOREACH(n
, item
->names
) {
482 if (n
== item
->canonical_name
)
485 r
= answer_add_ptr(*answer
, found_ptr
, n
);
494 static int etc_hosts_lookup_by_name(
498 DnsAnswer
**answer
) {
500 bool question_for_a
= false, question_for_aaaa
= false;
501 const struct in_addr_data
*a
;
502 EtcHostsItemByName
*item
;
511 item
= hashmap_get(hosts
->by_name
, name
);
513 r
= dns_answer_reserve(answer
, set_size(item
->addresses
));
517 /* Check if name was listed with no address. If yes, continue to return an answer. */
518 if (!set_contains(hosts
->no_address
, name
))
522 /* Determine whether we are looking for A and/or AAAA RRs */
523 DNS_QUESTION_FOREACH(t
, q
) {
524 if (!IN_SET(t
->type
, DNS_TYPE_A
, DNS_TYPE_AAAA
, DNS_TYPE_ANY
))
526 if (!IN_SET(t
->class, DNS_CLASS_IN
, DNS_CLASS_ANY
))
529 r
= dns_name_equal(dns_resource_key_name(t
), name
);
535 if (IN_SET(t
->type
, DNS_TYPE_A
, DNS_TYPE_ANY
))
536 question_for_a
= true;
537 if (IN_SET(t
->type
, DNS_TYPE_AAAA
, DNS_TYPE_ANY
))
538 question_for_aaaa
= true;
540 if (question_for_a
&& question_for_aaaa
)
541 break; /* We are looking for both, no need to continue loop */
544 SET_FOREACH(a
, item
? item
->addresses
: NULL
) {
545 EtcHostsItemByAddress
*item_by_addr
;
546 const char *canonical_name
;
548 if ((!question_for_a
&& a
->family
== AF_INET
) ||
549 (!question_for_aaaa
&& a
->family
== AF_INET6
))
552 item_by_addr
= hashmap_get(hosts
->by_address
, a
);
553 if (item_by_addr
&& item_by_addr
->canonical_name
)
554 canonical_name
= item_by_addr
->canonical_name
;
556 canonical_name
= item
->name
;
558 if (!streq(item
->name
, canonical_name
)) {
559 r
= answer_add_cname(*answer
, item
->name
, canonical_name
);
564 r
= answer_add_addr(*answer
, canonical_name
, a
);
569 return true; /* We consider ourselves authoritative for the whole name, all RR types, not just A/AAAA */
572 int manager_etc_hosts_lookup(Manager
*m
, DnsQuestion
*q
, DnsAnswer
**answer
) {
573 struct in_addr_data k
;
580 if (!m
->read_etc_hosts
)
583 (void) manager_etc_hosts_read(m
);
585 name
= dns_question_first_name(q
);
589 if (dns_name_address(name
, &k
.family
, &k
.address
) > 0)
590 return etc_hosts_lookup_by_address(&m
->etc_hosts
, q
, name
, &k
, answer
);
592 return etc_hosts_lookup_by_name(&m
->etc_hosts
, q
, name
, answer
);