1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
9 #include "hostname-util.h"
10 #include "resolved-dns-synthesize.h"
11 #include "resolved-etc-hosts.h"
12 #include "socket-netlink.h"
13 #include "stat-util.h"
14 #include "string-util.h"
16 #include "time-util.h"
18 /* Recheck /etc/hosts at most once every 2s */
19 #define ETC_HOSTS_RECHECK_USEC (2*USEC_PER_SEC)
21 static void etc_hosts_item_free(EtcHostsItem
*item
) {
22 strv_free(item
->names
);
26 static void etc_hosts_item_by_name_free(EtcHostsItemByName
*item
) {
28 free(item
->addresses
);
32 void etc_hosts_free(EtcHosts
*hosts
) {
33 hosts
->by_address
= hashmap_free_with_destructor(hosts
->by_address
, etc_hosts_item_free
);
34 hosts
->by_name
= hashmap_free_with_destructor(hosts
->by_name
, etc_hosts_item_by_name_free
);
35 hosts
->no_address
= set_free_free(hosts
->no_address
);
38 void manager_etc_hosts_flush(Manager
*m
) {
39 etc_hosts_free(&m
->etc_hosts
);
40 m
->etc_hosts_stat
= (struct stat
) {};
43 static int parse_line(EtcHosts
*hosts
, unsigned nr
, const char *line
) {
44 _cleanup_free_
char *address_str
= NULL
;
45 struct in_addr_data address
= {};
53 r
= extract_first_word(&line
, &address_str
, NULL
, EXTRACT_RELAX
);
55 return log_error_errno(r
, "/etc/hosts:%u: failed to extract address: %m", nr
);
56 assert(r
> 0); /* We already checked that the line is not empty, so it should contain *something* */
58 r
= in_addr_ifindex_from_string_auto(address_str
, &address
.family
, &address
.address
, NULL
);
60 log_warning_errno(r
, "/etc/hosts:%u: address '%s' is invalid, ignoring: %m", nr
, address_str
);
64 r
= in_addr_data_is_null(&address
);
66 log_warning_errno(r
, "/etc/hosts:%u: address '%s' is invalid, ignoring: %m", nr
, address_str
);
70 /* This is an 0.0.0.0 or :: item, which we assume means that we shall map the specified hostname to
74 /* If this is a normal address, then simply add entry mapping it to the specified names */
76 item
= hashmap_get(hosts
->by_address
, &address
);
78 r
= hashmap_ensure_allocated(&hosts
->by_address
, &in_addr_data_hash_ops
);
82 item
= new(EtcHostsItem
, 1);
86 *item
= (EtcHostsItem
) {
90 r
= hashmap_put(hosts
->by_address
, &item
->address
, item
);
99 _cleanup_free_
char *name
= NULL
;
100 EtcHostsItemByName
*bn
;
102 r
= extract_first_word(&line
, &name
, NULL
, EXTRACT_RELAX
);
104 return log_error_errno(r
, "/etc/hosts:%u: couldn't extract hostname: %m", nr
);
110 r
= dns_name_is_valid_ldh(name
);
113 log_warning_errno(r
, "/etc/hosts:%u: Failed to check the validity of hostname \"%s\", ignoring: %m", nr
, name
);
115 log_warning("/etc/hosts:%u: hostname \"%s\" is not valid, ignoring.", nr
, name
);
120 /* Optimize the case where we don't need to store any addresses, by storing
121 * only the name in a dedicated Set instead of the hashmap */
123 r
= set_ensure_consume(&hosts
->no_address
, &dns_name_hash_ops
, TAKE_PTR(name
));
130 r
= strv_extend(&item
->names
, name
);
134 bn
= hashmap_get(hosts
->by_name
, name
);
136 r
= hashmap_ensure_allocated(&hosts
->by_name
, &dns_name_hash_ops
);
140 bn
= new0(EtcHostsItemByName
, 1);
144 r
= hashmap_put(hosts
->by_name
, name
, bn
);
150 bn
->name
= TAKE_PTR(name
);
153 if (!GREEDY_REALLOC(bn
->addresses
, bn
->n_addresses
+ 1))
156 bn
->addresses
[bn
->n_addresses
++] = &item
->address
;
160 log_warning("/etc/hosts:%u: line is missing any hostnames", nr
);
165 static void strip_localhost(EtcHosts
*hosts
) {
166 static const struct in_addr_data local_in_addrs
[] = {
169 #if __BYTE_ORDER == __LITTLE_ENDIAN
170 /* We want constant expressions here, that's why we don't use htole32() here */
171 .address
.in
.s_addr
= UINT32_C(0x0100007F),
173 .address
.in
.s_addr
= UINT32_C(0x7F000001),
178 .address
.in6
= IN6ADDR_LOOPBACK_INIT
,
186 /* Removes the 'localhost' entry from what we loaded. But only if the mapping is exclusively between
187 * 127.0.0.1 and localhost (or aliases to that we recognize). If there's any other name assigned to
188 * it, we leave the entry in.
190 * This way our regular synthesizing can take over, but only if it would result in the exact same
193 for (size_t j
= 0; j
< ELEMENTSOF(local_in_addrs
); j
++) {
194 bool all_localhost
, in_order
;
196 item
= hashmap_get(hosts
->by_address
, local_in_addrs
+ j
);
200 /* Check whether all hostnames the loopback address points to are localhost ones */
201 all_localhost
= true;
202 STRV_FOREACH(i
, item
->names
)
203 if (!is_localhost(*i
)) {
204 all_localhost
= false;
208 if (!all_localhost
) /* Not all names are localhost, hence keep the entries for this address. */
211 /* Now check if the names listed for this address actually all point back just to this
212 * address (or the other loopback address). If not, let's stay away from this too. */
214 STRV_FOREACH(i
, item
->names
) {
215 EtcHostsItemByName
*n
;
216 bool all_local_address
;
218 n
= hashmap_get(hosts
->by_name
, *i
);
219 if (!n
) /* No reverse entry? Then almost certainly the entry already got deleted from
220 * the previous iteration of this loop, i.e. via the other protocol */
223 /* Now check if the addresses of this item are all localhost addresses */
224 all_local_address
= true;
225 for (size_t m
= 0; m
< n
->n_addresses
; m
++)
226 if (!in_addr_is_localhost(n
->addresses
[m
]->family
, &n
->addresses
[m
]->address
)) {
227 all_local_address
= false;
231 if (!all_local_address
) {
240 STRV_FOREACH(i
, item
->names
) {
241 EtcHostsItemByName
*n
;
243 n
= hashmap_remove(hosts
->by_name
, *i
);
245 etc_hosts_item_by_name_free(n
);
248 assert_se(hashmap_remove(hosts
->by_address
, local_in_addrs
+ j
) == item
);
249 etc_hosts_item_free(item
);
253 int etc_hosts_parse(EtcHosts
*hosts
, FILE *f
) {
254 _cleanup_(etc_hosts_free
) EtcHosts t
= {};
259 _cleanup_free_
char *line
= NULL
;
262 r
= read_line(f
, LONG_LINE_MAX
, &line
);
264 return log_error_errno(r
, "Failed to read /etc/hosts: %m");
270 l
= strchr(line
, '#');
278 r
= parse_line(&t
, nr
, l
);
285 etc_hosts_free(hosts
);
287 t
= (EtcHosts
) {}; /* prevent cleanup */
291 static int manager_etc_hosts_read(Manager
*m
) {
292 _cleanup_fclose_
FILE *f
= NULL
;
297 assert_se(sd_event_now(m
->event
, CLOCK_BOOTTIME
, &ts
) >= 0);
299 /* See if we checked /etc/hosts recently already */
300 if (m
->etc_hosts_last
!= USEC_INFINITY
&& m
->etc_hosts_last
+ ETC_HOSTS_RECHECK_USEC
> ts
)
303 m
->etc_hosts_last
= ts
;
305 if (m
->etc_hosts_stat
.st_mode
!= 0) {
306 if (stat("/etc/hosts", &st
) < 0) {
308 return log_error_errno(errno
, "Failed to stat /etc/hosts: %m");
310 manager_etc_hosts_flush(m
);
314 /* Did the mtime or ino/dev change? If not, there's no point in re-reading the file. */
315 if (stat_inode_unmodified(&m
->etc_hosts_stat
, &st
))
319 f
= fopen("/etc/hosts", "re");
322 return log_error_errno(errno
, "Failed to open /etc/hosts: %m");
324 manager_etc_hosts_flush(m
);
328 /* Take the timestamp at the beginning of processing, so that any changes made later are read on the next
330 r
= fstat(fileno(f
), &st
);
332 return log_error_errno(errno
, "Failed to fstat() /etc/hosts: %m");
334 r
= etc_hosts_parse(&m
->etc_hosts
, f
);
338 m
->etc_hosts_stat
= st
;
339 m
->etc_hosts_last
= ts
;
344 int manager_etc_hosts_lookup(Manager
*m
, DnsQuestion
* q
, DnsAnswer
**answer
) {
345 bool found_a
= false, found_aaaa
= false;
346 struct in_addr_data k
= {};
347 EtcHostsItemByName
*bn
;
357 if (!m
->read_etc_hosts
)
360 (void) manager_etc_hosts_read(m
);
362 name
= dns_question_first_name(q
);
366 r
= dns_name_address(name
, &k
.family
, &k
.address
);
369 DnsResourceKey
*found_ptr
= NULL
;
371 item
= hashmap_get(m
->etc_hosts
.by_address
, &k
);
375 /* We have an address in /etc/hosts that matches the queried name. Let's return successful. Actual data
376 * we'll only return if the request was for PTR. */
378 DNS_QUESTION_FOREACH(t
, q
) {
379 if (!IN_SET(t
->type
, DNS_TYPE_PTR
, DNS_TYPE_ANY
))
381 if (!IN_SET(t
->class, DNS_CLASS_IN
, DNS_CLASS_ANY
))
384 r
= dns_name_equal(dns_resource_key_name(t
), name
);
394 r
= dns_answer_reserve(answer
, strv_length(item
->names
));
398 STRV_FOREACH(n
, item
->names
) {
399 _cleanup_(dns_resource_record_unrefp
) DnsResourceRecord
*rr
= NULL
;
401 rr
= dns_resource_record_new(found_ptr
);
405 rr
->ptr
.name
= strdup(*n
);
409 r
= dns_answer_add(*answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
, NULL
);
418 bn
= hashmap_get(m
->etc_hosts
.by_name
, name
);
420 r
= dns_answer_reserve(answer
, bn
->n_addresses
);
424 /* Check if name was listed with no address. If yes, continue to return an answer. */
425 if (!set_contains(m
->etc_hosts
.no_address
, name
))
429 DNS_QUESTION_FOREACH(t
, q
) {
430 if (!IN_SET(t
->type
, DNS_TYPE_A
, DNS_TYPE_AAAA
, DNS_TYPE_ANY
))
432 if (!IN_SET(t
->class, DNS_CLASS_IN
, DNS_CLASS_ANY
))
435 r
= dns_name_equal(dns_resource_key_name(t
), name
);
441 if (IN_SET(t
->type
, DNS_TYPE_A
, DNS_TYPE_ANY
))
443 if (IN_SET(t
->type
, DNS_TYPE_AAAA
, DNS_TYPE_ANY
))
446 if (found_a
&& found_aaaa
)
450 for (i
= 0; bn
&& i
< bn
->n_addresses
; i
++) {
451 _cleanup_(dns_resource_record_unrefp
) DnsResourceRecord
*rr
= NULL
;
453 if ((!found_a
&& bn
->addresses
[i
]->family
== AF_INET
) ||
454 (!found_aaaa
&& bn
->addresses
[i
]->family
== AF_INET6
))
457 r
= dns_resource_record_new_address(&rr
, bn
->addresses
[i
]->family
, &bn
->addresses
[i
]->address
, bn
->name
);
461 r
= dns_answer_add(*answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
, NULL
);
466 return found_a
|| found_aaaa
;