]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-etc-hosts.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / resolve / resolved-etc-hosts.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
dd0bc0f1
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2016 Lennart Poettering
6
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.
11
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.
16
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/>.
19***/
20
21#include "fd-util.h"
22#include "fileio.h"
23#include "hostname-util.h"
24#include "resolved-etc-hosts.h"
25#include "resolved-dns-synthesize.h"
26#include "string-util.h"
27#include "strv.h"
28#include "time-util.h"
29
30/* Recheck /etc/hosts at most once every 2s */
31#define ETC_HOSTS_RECHECK_USEC (2*USEC_PER_SEC)
32
33typedef struct EtcHostsItem {
34 int family;
35 union in_addr_union address;
36
37 char **names;
38} EtcHostsItem;
39
40typedef struct EtcHostsItemByName {
41 char *name;
42
43 EtcHostsItem **items;
44 size_t n_items, n_allocated;
45} EtcHostsItemByName;
46
47void manager_etc_hosts_flush(Manager *m) {
48 EtcHostsItem *item;
49 EtcHostsItemByName *bn;
50
51 while ((item = set_steal_first(m->etc_hosts_by_address))) {
52 strv_free(item->names);
53 free(item);
54 }
55
56 while ((bn = hashmap_steal_first(m->etc_hosts_by_name))) {
57 free(bn->name);
58 free(bn->items);
59 free(bn);
60 }
61
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);
64
65 m->etc_hosts_mtime = USEC_INFINITY;
66}
67
68static void etc_hosts_item_hash_func(const void *p, struct siphash *state) {
69 const EtcHostsItem *item = p;
70
71 siphash24_compress(&item->family, sizeof(item->family), state);
72
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);
77}
78
79static int etc_hosts_item_compare_func(const void *a, const void *b) {
80 const EtcHostsItem *x = a, *y = b;
81
ec76e139 82 if (x->family != y->family)
dd0bc0f1
LP
83 return x->family - y->family;
84
85 if (x->family == AF_INET)
86 return memcmp(&x->address.in.s_addr, &y->address.in.s_addr, sizeof(struct in_addr));
87
88 if (x->family == AF_INET6)
89 return memcmp(&x->address.in6.s6_addr, &y->address.in6.s6_addr, sizeof(struct in6_addr));
90
91 return trivial_compare_func(a, b);
92}
93
94static const struct hash_ops etc_hosts_item_ops = {
95 .hash = etc_hosts_item_hash_func,
96 .compare = etc_hosts_item_compare_func,
97};
98
99static int add_item(Manager *m, int family, const union in_addr_union *address, char **names) {
100
101 EtcHostsItem key = {
102 .family = family,
103 .address = *address,
104 };
105 EtcHostsItem *item;
106 char **n;
107 int r;
108
109 assert(m);
110 assert(address);
111
112 r = in_addr_is_null(family, address);
113 if (r < 0)
114 return r;
115 if (r > 0)
116 /* This is an 0.0.0.0 or :: item, which we assume means that we shall map the specified hostname to
117 * nothing. */
118 item = NULL;
119 else {
120 /* If this is a normal address, then, simply add entry mapping it to the specified names */
121
122 item = set_get(m->etc_hosts_by_address, &key);
123 if (item) {
124 r = strv_extend_strv(&item->names, names, true);
125 if (r < 0)
126 return log_oom();
127 } else {
128
129 r = set_ensure_allocated(&m->etc_hosts_by_address, &etc_hosts_item_ops);
130 if (r < 0)
131 return log_oom();
132
133 item = new0(EtcHostsItem, 1);
134 if (!item)
135 return log_oom();
136
137 item->family = family;
138 item->address = *address;
139 item->names = names;
140
141 r = set_put(m->etc_hosts_by_address, item);
142 if (r < 0) {
143 free(item);
144 return log_oom();
145 }
146 }
147 }
148
149 STRV_FOREACH(n, names) {
150 EtcHostsItemByName *bn;
151
152 bn = hashmap_get(m->etc_hosts_by_name, *n);
153 if (!bn) {
154 r = hashmap_ensure_allocated(&m->etc_hosts_by_name, &dns_name_hash_ops);
155 if (r < 0)
156 return log_oom();
157
158 bn = new0(EtcHostsItemByName, 1);
159 if (!bn)
160 return log_oom();
161
162 bn->name = strdup(*n);
163 if (!bn->name) {
164 free(bn);
165 return log_oom();
166 }
167
168 r = hashmap_put(m->etc_hosts_by_name, bn->name, bn);
169 if (r < 0) {
170 free(bn->name);
171 free(bn);
172 return log_oom();
173 }
174 }
175
176 if (item) {
177 if (!GREEDY_REALLOC(bn->items, bn->n_allocated, bn->n_items+1))
178 return log_oom();
179
180 bn->items[bn->n_items++] = item;
181 }
182 }
183
184 return 0;
185}
186
187static 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;
192 int family, r;
193
194 assert(m);
195 assert(line);
196
197 r = extract_first_word(&line, &address, NULL, EXTRACT_RELAX);
198 if (r < 0)
199 return log_error_errno(r, "Couldn't extract address, in line /etc/hosts:%u.", nr);
200 if (r == 0) {
201 log_error("Premature end of line, in line /etc/hosts:%u.", nr);
202 return -EINVAL;
203 }
204
205 r = in_addr_from_string_auto(address, &family, &in);
206 if (r < 0)
207 return log_error_errno(r, "Address '%s' is invalid, in line /etc/hosts:%u.", address, nr);
208
209 for (;;) {
210 _cleanup_free_ char *name = NULL;
211
212 r = extract_first_word(&line, &name, NULL, EXTRACT_RELAX);
213 if (r < 0)
214 return log_error_errno(r, "Couldn't extract host name, in line /etc/hosts:%u.", nr);
215 if (r == 0)
216 break;
217
218 r = dns_name_is_valid(name);
219 if (r <= 0)
220 return log_error_errno(r, "Hostname %s is not valid, ignoring, in line /etc/hosts:%u.", name, nr);
221
222 if (is_localhost(name)) {
223 /* Suppress the "localhost" line that is often seen */
224 suppressed = true;
225 continue;
226 }
227
228 r = strv_push(&names, name);
229 if (r < 0)
230 return log_oom();
231
232 name = NULL;
233 }
234
235 if (strv_isempty(names)) {
236
237 if (suppressed)
238 return 0;
239
240 log_error("Line is missing any host names, in line /etc/hosts:%u.", nr);
241 return -EINVAL;
242 }
243
244 /* Takes possession of the names strv */
245 r = add_item(m, family, &in, names);
246 if (r < 0)
247 return r;
248
249 names = NULL;
250 return r;
251}
252
253int manager_etc_hosts_read(Manager *m) {
254 _cleanup_fclose_ FILE *f = NULL;
255 char line[LINE_MAX];
256 struct stat st;
257 usec_t ts;
258 unsigned nr = 0;
259 int r;
260
261 assert_se(sd_event_now(m->event, clock_boottime_or_monotonic(), &ts) >= 0);
262
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)
265 return 0;
266
267 m->etc_hosts_last = ts;
268
269 if (m->etc_hosts_mtime != USEC_INFINITY) {
270 if (stat("/etc/hosts", &st) < 0) {
271 if (errno == ENOENT) {
272 r = 0;
273 goto clear;
274 }
275
276 return log_error_errno(errno, "Failed to stat /etc/hosts: %m");
277 }
278
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)
281 return 0;
282 }
283
284 f = fopen("/etc/hosts", "re");
285 if (!f) {
286 if (errno == ENOENT) {
287 r = 0;
288 goto clear;
289 }
290
291 return log_error_errno(errno, "Failed to open /etc/hosts: %m");
292 }
293
294 /* Take the timestamp at the beginning of processing, so that any changes made later are read on the next
295 * invocation */
296 r = fstat(fileno(f), &st);
297 if (r < 0)
298 return log_error_errno(errno, "Failed to fstat() /etc/hosts: %m");
299
300 manager_etc_hosts_flush(m);
301
302 FOREACH_LINE(line, f, return log_error_errno(errno, "Failed to read /etc/hosts: %m")) {
303 char *l;
304
313cefa1 305 nr++;
dd0bc0f1
LP
306
307 l = strstrip(line);
308 if (isempty(l))
309 continue;
310 if (l[0] == '#')
311 continue;
312
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 */
315 goto clear;
316 }
317
318 m->etc_hosts_mtime = timespec_load(&st.st_mtim);
319 m->etc_hosts_last = ts;
320
321 return 1;
322
323clear:
324 manager_etc_hosts_flush(m);
325 return r;
326}
327
328int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer) {
329 bool found_a = false, found_aaaa = false;
330 EtcHostsItemByName *bn;
331 EtcHostsItem k = {};
332 DnsResourceKey *t;
333 const char *name;
334 unsigned i;
335 int r;
336
337 assert(m);
338 assert(q);
339 assert(answer);
340
341 r = manager_etc_hosts_read(m);
342 if (r < 0)
343 return r;
344
345 name = dns_question_first_name(q);
346 if (!name)
347 return 0;
348
349 r = dns_name_address(name, &k.family, &k.address);
350 if (r > 0) {
351 EtcHostsItem *item;
352 DnsResourceKey *found_ptr = NULL;
353
354 item = set_get(m->etc_hosts_by_address, &k);
355 if (!item)
356 return 0;
357
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. */
360
361 DNS_QUESTION_FOREACH(t, q) {
362 if (!IN_SET(t->type, DNS_TYPE_PTR, DNS_TYPE_ANY))
363 continue;
364 if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY))
365 continue;
366
1c02e7ba 367 r = dns_name_equal(dns_resource_key_name(t), name);
dd0bc0f1
LP
368 if (r < 0)
369 return r;
370 if (r > 0) {
371 found_ptr = t;
372 break;
373 }
374 }
375
376 if (found_ptr) {
377 char **n;
378
379 r = dns_answer_reserve(answer, strv_length(item->names));
380 if (r < 0)
381 return r;
382
383 STRV_FOREACH(n, item->names) {
384 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
385
386 rr = dns_resource_record_new(found_ptr);
387 if (!rr)
388 return -ENOMEM;
389
390 rr->ptr.name = strdup(*n);
391 if (!rr->ptr.name)
392 return -ENOMEM;
393
394 r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED);
395 if (r < 0)
396 return r;
397 }
398 }
399
400 return 1;
401 }
402
403 bn = hashmap_get(m->etc_hosts_by_name, name);
404 if (!bn)
405 return 0;
406
407 r = dns_answer_reserve(answer, bn->n_items);
408 if (r < 0)
409 return r;
410
411 DNS_QUESTION_FOREACH(t, q) {
412 if (!IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_TYPE_ANY))
413 continue;
414 if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY))
415 continue;
416
1c02e7ba 417 r = dns_name_equal(dns_resource_key_name(t), name);
dd0bc0f1
LP
418 if (r < 0)
419 return r;
420 if (r == 0)
421 continue;
422
423 if (IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_ANY))
424 found_a = true;
425 if (IN_SET(t->type, DNS_TYPE_AAAA, DNS_TYPE_ANY))
426 found_aaaa = true;
427
428 if (found_a && found_aaaa)
429 break;
430 }
431
432 for (i = 0; i < bn->n_items; i++) {
433 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
434
4050e04b
MP
435 if ((!found_a && bn->items[i]->family == AF_INET) ||
436 (!found_aaaa && bn->items[i]->family == AF_INET6))
dd0bc0f1
LP
437 continue;
438
439 r = dns_resource_record_new_address(&rr, bn->items[i]->family, &bn->items[i]->address, bn->name);
440 if (r < 0)
441 return r;
442
443 r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED);
444 if (r < 0)
445 return r;
446 }
447
4050e04b 448 return found_a || found_aaaa;
dd0bc0f1 449}