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