1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2016 Lennart Poettering
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
23 #include "hostname-util.h"
24 #include "resolved-etc-hosts.h"
25 #include "resolved-dns-synthesize.h"
26 #include "string-util.h"
28 #include "time-util.h"
30 /* Recheck /etc/hosts at most once every 2s */
31 #define ETC_HOSTS_RECHECK_USEC (2*USEC_PER_SEC)
33 typedef struct EtcHostsItem
{
35 union in_addr_union address
;
40 typedef struct EtcHostsItemByName
{
44 size_t n_items
, n_allocated
;
47 void manager_etc_hosts_flush(Manager
*m
) {
49 EtcHostsItemByName
*bn
;
51 while ((item
= set_steal_first(m
->etc_hosts_by_address
))) {
52 strv_free(item
->names
);
56 while ((bn
= hashmap_steal_first(m
->etc_hosts_by_name
))) {
62 m
->etc_hosts_by_address
= set_free(m
->etc_hosts_by_address
);
63 m
->etc_hosts_by_name
= hashmap_free(m
->etc_hosts_by_name
);
65 m
->etc_hosts_mtime
= USEC_INFINITY
;
68 static void etc_hosts_item_hash_func(const void *p
, struct siphash
*state
) {
69 const EtcHostsItem
*item
= p
;
71 siphash24_compress(&item
->family
, sizeof(item
->family
), state
);
73 if (item
->family
== AF_INET
)
74 siphash24_compress(&item
->address
.in
, sizeof(item
->address
.in
), state
);
75 else if (item
->family
== AF_INET6
)
76 siphash24_compress(&item
->address
.in6
, sizeof(item
->address
.in6
), state
);
79 static int etc_hosts_item_compare_func(const void *a
, const void *b
) {
80 const EtcHostsItem
*x
= a
, *y
= b
;
82 if (x
->family
!= y
->family
)
83 return x
->family
- y
->family
;
85 if (x
->family
== AF_INET
)
86 return memcmp(&x
->address
.in
.s_addr
, &y
->address
.in
.s_addr
, sizeof(struct in_addr
));
88 if (x
->family
== AF_INET6
)
89 return memcmp(&x
->address
.in6
.s6_addr
, &y
->address
.in6
.s6_addr
, sizeof(struct in6_addr
));
91 return trivial_compare_func(a
, b
);
94 static const struct hash_ops etc_hosts_item_ops
= {
95 .hash
= etc_hosts_item_hash_func
,
96 .compare
= etc_hosts_item_compare_func
,
99 static int add_item(Manager
*m
, int family
, const union in_addr_union
*address
, char **names
) {
112 r
= in_addr_is_null(family
, address
);
116 /* This is an 0.0.0.0 or :: item, which we assume means that we shall map the specified hostname to
120 /* If this is a normal address, then, simply add entry mapping it to the specified names */
122 item
= set_get(m
->etc_hosts_by_address
, &key
);
124 r
= strv_extend_strv(&item
->names
, names
, true);
129 r
= set_ensure_allocated(&m
->etc_hosts_by_address
, &etc_hosts_item_ops
);
133 item
= new0(EtcHostsItem
, 1);
137 item
->family
= family
;
138 item
->address
= *address
;
141 r
= set_put(m
->etc_hosts_by_address
, item
);
149 STRV_FOREACH(n
, names
) {
150 EtcHostsItemByName
*bn
;
152 bn
= hashmap_get(m
->etc_hosts_by_name
, *n
);
154 r
= hashmap_ensure_allocated(&m
->etc_hosts_by_name
, &dns_name_hash_ops
);
158 bn
= new0(EtcHostsItemByName
, 1);
162 bn
->name
= strdup(*n
);
168 r
= hashmap_put(m
->etc_hosts_by_name
, bn
->name
, bn
);
177 if (!GREEDY_REALLOC(bn
->items
, bn
->n_allocated
, bn
->n_items
+1))
180 bn
->items
[bn
->n_items
++] = item
;
187 static int parse_line(Manager
*m
, unsigned nr
, const char *line
) {
188 _cleanup_free_
char *address
= NULL
;
189 _cleanup_strv_free_
char **names
= NULL
;
190 union in_addr_union in
;
191 bool suppressed
= false;
197 r
= extract_first_word(&line
, &address
, NULL
, EXTRACT_RELAX
);
199 return log_error_errno(r
, "Couldn't extract address, in line /etc/hosts:%u.", nr
);
201 log_error("Premature end of line, in line /etc/hosts:%u.", nr
);
205 r
= in_addr_from_string_auto(address
, &family
, &in
);
207 return log_error_errno(r
, "Address '%s' is invalid, in line /etc/hosts:%u.", address
, nr
);
210 _cleanup_free_
char *name
= NULL
;
212 r
= extract_first_word(&line
, &name
, NULL
, EXTRACT_RELAX
);
214 return log_error_errno(r
, "Couldn't extract host name, in line /etc/hosts:%u.", nr
);
218 r
= dns_name_is_valid(name
);
220 return log_error_errno(r
, "Hostname %s is not valid, ignoring, in line /etc/hosts:%u.", name
, nr
);
222 if (is_localhost(name
)) {
223 /* Suppress the "localhost" line that is often seen */
228 r
= strv_push(&names
, name
);
235 if (strv_isempty(names
)) {
240 log_error("Line is missing any host names, in line /etc/hosts:%u.", nr
);
244 /* Takes possession of the names strv */
245 r
= add_item(m
, family
, &in
, names
);
253 int manager_etc_hosts_read(Manager
*m
) {
254 _cleanup_fclose_
FILE *f
= NULL
;
261 assert_se(sd_event_now(m
->event
, clock_boottime_or_monotonic(), &ts
) >= 0);
263 /* See if we checked /etc/hosts recently already */
264 if (m
->etc_hosts_last
!= USEC_INFINITY
&& m
->etc_hosts_last
+ ETC_HOSTS_RECHECK_USEC
> ts
)
267 m
->etc_hosts_last
= ts
;
269 if (m
->etc_hosts_mtime
!= USEC_INFINITY
) {
270 if (stat("/etc/hosts", &st
) < 0) {
271 if (errno
== ENOENT
) {
276 return log_error_errno(errno
, "Failed to stat /etc/hosts: %m");
279 /* Did the mtime change? If not, there's no point in re-reading the file. */
280 if (timespec_load(&st
.st_mtim
) == m
->etc_hosts_mtime
)
284 f
= fopen("/etc/hosts", "re");
286 if (errno
== ENOENT
) {
291 return log_error_errno(errno
, "Failed to open /etc/hosts: %m");
294 /* Take the timestamp at the beginning of processing, so that any changes made later are read on the next
296 r
= fstat(fileno(f
), &st
);
298 return log_error_errno(errno
, "Failed to fstat() /etc/hosts: %m");
300 manager_etc_hosts_flush(m
);
302 FOREACH_LINE(line
, f
, return log_error_errno(errno
, "Failed to read /etc/hosts: %m")) {
313 r
= parse_line(m
, nr
, l
);
314 if (r
== -ENOMEM
) /* On OOM we abandon the half-built-up structure. All other errors we ignore and proceed */
318 m
->etc_hosts_mtime
= timespec_load(&st
.st_mtim
);
319 m
->etc_hosts_last
= ts
;
324 manager_etc_hosts_flush(m
);
328 int manager_etc_hosts_lookup(Manager
*m
, DnsQuestion
* q
, DnsAnswer
**answer
) {
329 bool found_a
= false, found_aaaa
= false;
330 EtcHostsItemByName
*bn
;
341 r
= manager_etc_hosts_read(m
);
345 name
= dns_question_first_name(q
);
349 r
= dns_name_address(name
, &k
.family
, &k
.address
);
352 DnsResourceKey
*found_ptr
= NULL
;
354 item
= set_get(m
->etc_hosts_by_address
, &k
);
358 /* We have an address in /etc/hosts that matches the queried name. Let's return successful. Actual data
359 * we'll only return if the request was for PTR. */
361 DNS_QUESTION_FOREACH(t
, q
) {
362 if (!IN_SET(t
->type
, DNS_TYPE_PTR
, DNS_TYPE_ANY
))
364 if (!IN_SET(t
->class, DNS_CLASS_IN
, DNS_CLASS_ANY
))
367 r
= dns_name_equal(dns_resource_key_name(t
), name
);
379 r
= dns_answer_reserve(answer
, strv_length(item
->names
));
383 STRV_FOREACH(n
, item
->names
) {
384 _cleanup_(dns_resource_record_unrefp
) DnsResourceRecord
*rr
= NULL
;
386 rr
= dns_resource_record_new(found_ptr
);
390 rr
->ptr
.name
= strdup(*n
);
394 r
= dns_answer_add(*answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
);
403 bn
= hashmap_get(m
->etc_hosts_by_name
, name
);
407 r
= dns_answer_reserve(answer
, bn
->n_items
);
411 DNS_QUESTION_FOREACH(t
, q
) {
412 if (!IN_SET(t
->type
, DNS_TYPE_A
, DNS_TYPE_AAAA
, DNS_TYPE_ANY
))
414 if (!IN_SET(t
->class, DNS_CLASS_IN
, DNS_CLASS_ANY
))
417 r
= dns_name_equal(dns_resource_key_name(t
), name
);
423 if (IN_SET(t
->type
, DNS_TYPE_A
, DNS_TYPE_ANY
))
425 if (IN_SET(t
->type
, DNS_TYPE_AAAA
, DNS_TYPE_ANY
))
428 if (found_a
&& found_aaaa
)
432 for (i
= 0; i
< bn
->n_items
; i
++) {
433 _cleanup_(dns_resource_record_unrefp
) DnsResourceRecord
*rr
= NULL
;
435 if ((!found_a
&& bn
->items
[i
]->family
== AF_INET
) ||
436 (!found_aaaa
&& bn
->items
[i
]->family
== AF_INET6
))
439 r
= dns_resource_record_new_address(&rr
, bn
->items
[i
]->family
, &bn
->items
[i
]->address
, bn
->name
);
443 r
= dns_answer_add(*answer
, rr
, 0, DNS_ANSWER_AUTHENTICATED
);
448 return found_a
|| found_aaaa
;