1 /* SPDX-License-Identifier: LGPL-2.1+ */
5 #include "hostname-util.h"
6 #include "resolved-etc-hosts.h"
7 #include "resolved-dns-synthesize.h"
8 #include "string-util.h"
10 #include "time-util.h"
12 /* Recheck /etc/hosts at most once every 2s */
13 #define ETC_HOSTS_RECHECK_USEC (2*USEC_PER_SEC)
15 typedef struct EtcHostsItem
{
16 struct in_addr_data address
;
21 typedef struct EtcHostsItemByName
{
24 struct in_addr_data
**addresses
;
25 size_t n_addresses
, n_allocated
;
28 void manager_etc_hosts_flush(Manager
*m
) {
30 EtcHostsItemByName
*bn
;
32 while ((item
= hashmap_steal_first(m
->etc_hosts_by_address
))) {
33 strv_free(item
->names
);
37 while ((bn
= hashmap_steal_first(m
->etc_hosts_by_name
))) {
43 m
->etc_hosts_by_address
= hashmap_free(m
->etc_hosts_by_address
);
44 m
->etc_hosts_by_name
= hashmap_free(m
->etc_hosts_by_name
);
46 m
->etc_hosts_mtime
= USEC_INFINITY
;
49 static int parse_line(Manager
*m
, unsigned nr
, const char *line
) {
50 _cleanup_free_
char *address_str
= NULL
;
51 struct in_addr_data address
= {};
52 bool suppressed
= false;
59 r
= extract_first_word(&line
, &address_str
, NULL
, EXTRACT_RELAX
);
61 return log_error_errno(r
, "Couldn't extract address, in line /etc/hosts:%u.", nr
);
63 log_error("Premature end of line, in line /etc/hosts:%u.", nr
);
67 r
= in_addr_from_string_auto(address_str
, &address
.family
, &address
.address
);
69 return log_error_errno(r
, "Address '%s' is invalid, in line /etc/hosts:%u.", address_str
, nr
);
71 r
= in_addr_is_null(address
.family
, &address
.address
);
75 /* This is an 0.0.0.0 or :: item, which we assume means that we shall map the specified hostname to
79 /* If this is a normal address, then, simply add entry mapping it to the specified names */
81 item
= hashmap_get(m
->etc_hosts_by_address
, &address
);
83 r
= hashmap_ensure_allocated(&m
->etc_hosts_by_address
, &in_addr_data_hash_ops
);
87 item
= new0(EtcHostsItem
, 1);
91 item
->address
= address
;
93 r
= hashmap_put(m
->etc_hosts_by_address
, &item
->address
, item
);
102 _cleanup_free_
char *name
= NULL
;
103 EtcHostsItemByName
*bn
;
105 r
= extract_first_word(&line
, &name
, NULL
, EXTRACT_RELAX
);
107 return log_error_errno(r
, "Couldn't extract host name, in line /etc/hosts:%u.", nr
);
111 r
= dns_name_is_valid(name
);
113 return log_error_errno(r
, "Hostname %s is not valid, ignoring, in line /etc/hosts:%u.", name
, nr
);
115 if (is_localhost(name
)) {
116 /* Suppress the "localhost" line that is often seen */
122 r
= strv_extend(&item
->names
, name
);
127 bn
= hashmap_get(m
->etc_hosts_by_name
, name
);
129 r
= hashmap_ensure_allocated(&m
->etc_hosts_by_name
, &dns_name_hash_ops
);
133 bn
= new0(EtcHostsItemByName
, 1);
137 r
= hashmap_put(m
->etc_hosts_by_name
, name
, bn
);
143 bn
->name
= TAKE_PTR(name
);
147 if (!GREEDY_REALLOC(bn
->addresses
, bn
->n_allocated
, bn
->n_addresses
+ 1))
150 bn
->addresses
[bn
->n_addresses
++] = &item
->address
;
157 log_error("Line is missing any host names, in line /etc/hosts:%u.", nr
);
164 static int manager_etc_hosts_read(Manager
*m
) {
165 _cleanup_fclose_
FILE *f
= NULL
;
172 assert_se(sd_event_now(m
->event
, clock_boottime_or_monotonic(), &ts
) >= 0);
174 /* See if we checked /etc/hosts recently already */
175 if (m
->etc_hosts_last
!= USEC_INFINITY
&& m
->etc_hosts_last
+ ETC_HOSTS_RECHECK_USEC
> ts
)
178 m
->etc_hosts_last
= ts
;
180 if (m
->etc_hosts_mtime
!= USEC_INFINITY
) {
181 if (stat("/etc/hosts", &st
) < 0) {
182 if (errno
== ENOENT
) {
187 return log_error_errno(errno
, "Failed to stat /etc/hosts: %m");
190 /* Did the mtime change? If not, there's no point in re-reading the file. */
191 if (timespec_load(&st
.st_mtim
) == m
->etc_hosts_mtime
)
195 f
= fopen("/etc/hosts", "re");
197 if (errno
== ENOENT
) {
202 return log_error_errno(errno
, "Failed to open /etc/hosts: %m");
205 /* Take the timestamp at the beginning of processing, so that any changes made later are read on the next
207 r
= fstat(fileno(f
), &st
);
209 return log_error_errno(errno
, "Failed to fstat() /etc/hosts: %m");
211 manager_etc_hosts_flush(m
);
213 FOREACH_LINE(line
, f
, return log_error_errno(errno
, "Failed to read /etc/hosts: %m")) {
224 r
= parse_line(m
, nr
, l
);
225 if (r
== -ENOMEM
) /* On OOM we abandon the half-built-up structure. All other errors we ignore and proceed */
229 m
->etc_hosts_mtime
= timespec_load(&st
.st_mtim
);
230 m
->etc_hosts_last
= ts
;
235 manager_etc_hosts_flush(m
);
239 int manager_etc_hosts_lookup(Manager
*m
, DnsQuestion
* q
, DnsAnswer
**answer
) {
240 bool found_a
= false, found_aaaa
= false;
241 struct in_addr_data k
= {};
242 EtcHostsItemByName
*bn
;
252 if (!m
->read_etc_hosts
)
255 r
= manager_etc_hosts_read(m
);
259 name
= dns_question_first_name(q
);
263 r
= dns_name_address(name
, &k
.family
, &k
.address
);
266 DnsResourceKey
*found_ptr
= NULL
;
268 item
= hashmap_get(m
->etc_hosts_by_address
, &k
);
272 /* We have an address in /etc/hosts that matches the queried name. Let's return successful. Actual data
273 * we'll only return if the request was for PTR. */
275 DNS_QUESTION_FOREACH(t
, q
) {
276 if (!IN_SET(t
->type
, DNS_TYPE_PTR
, DNS_TYPE_ANY
))
278 if (!IN_SET(t
->class, DNS_CLASS_IN
, DNS_CLASS_ANY
))
281 r
= dns_name_equal(dns_resource_key_name(t
), name
);
293 r
= dns_answer_reserve(answer
, strv_length(item
->names
));
297 STRV_FOREACH(n
, item
->names
) {
298 _cleanup_(dns_resource_record_unrefp
) DnsResourceRecord
*rr
= NULL
;
300 rr
= dns_resource_record_new(found_ptr
);
304 rr
->ptr
.name
= strdup(*n
);
308 r
= dns_answer_add(*answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
);
317 bn
= hashmap_get(m
->etc_hosts_by_name
, name
);
321 r
= dns_answer_reserve(answer
, bn
->n_addresses
);
325 DNS_QUESTION_FOREACH(t
, q
) {
326 if (!IN_SET(t
->type
, DNS_TYPE_A
, DNS_TYPE_AAAA
, DNS_TYPE_ANY
))
328 if (!IN_SET(t
->class, DNS_CLASS_IN
, DNS_CLASS_ANY
))
331 r
= dns_name_equal(dns_resource_key_name(t
), name
);
337 if (IN_SET(t
->type
, DNS_TYPE_A
, DNS_TYPE_ANY
))
339 if (IN_SET(t
->type
, DNS_TYPE_AAAA
, DNS_TYPE_ANY
))
342 if (found_a
&& found_aaaa
)
346 for (i
= 0; i
< bn
->n_addresses
; i
++) {
347 _cleanup_(dns_resource_record_unrefp
) DnsResourceRecord
*rr
= NULL
;
349 if ((!found_a
&& bn
->addresses
[i
]->family
== AF_INET
) ||
350 (!found_aaaa
&& bn
->addresses
[i
]->family
== AF_INET6
))
353 r
= dns_resource_record_new_address(&rr
, bn
->addresses
[i
]->family
, &bn
->addresses
[i
]->address
, bn
->name
);
357 r
= dns_answer_add(*answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
);
362 return found_a
|| found_aaaa
;