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