]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-etc-hosts.c
tree-wide: remove Lennart's copyright lines
[thirdparty/systemd.git] / src / resolve / resolved-etc-hosts.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
dd0bc0f1
LP
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
15typedef struct EtcHostsItem {
16 int family;
17 union in_addr_union address;
18
19 char **names;
20} EtcHostsItem;
21
22typedef struct EtcHostsItemByName {
23 char *name;
24
25 EtcHostsItem **items;
26 size_t n_items, n_allocated;
27} EtcHostsItemByName;
28
29void 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
50static 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
61static int etc_hosts_item_compare_func(const void *a, const void *b) {
62 const EtcHostsItem *x = a, *y = b;
63
ec76e139 64 if (x->family != y->family)
dd0bc0f1
LP
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
76static const struct hash_ops etc_hosts_item_ops = {
77 .hash = etc_hosts_item_hash_func,
78 .compare = etc_hosts_item_compare_func,
79};
80
81static int add_item(Manager *m, int family, const union in_addr_union *address, char **names) {
82
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 = new0(EtcHostsItem, 1);
116 if (!item)
117 return log_oom();
118
119 item->family = family;
120 item->address = *address;
121 item->names = names;
122
123 r = set_put(m->etc_hosts_by_address, item);
124 if (r < 0) {
125 free(item);
126 return log_oom();
127 }
128 }
129 }
130
131 STRV_FOREACH(n, names) {
132 EtcHostsItemByName *bn;
133
134 bn = hashmap_get(m->etc_hosts_by_name, *n);
135 if (!bn) {
136 r = hashmap_ensure_allocated(&m->etc_hosts_by_name, &dns_name_hash_ops);
137 if (r < 0)
138 return log_oom();
139
140 bn = new0(EtcHostsItemByName, 1);
141 if (!bn)
142 return log_oom();
143
144 bn->name = strdup(*n);
145 if (!bn->name) {
146 free(bn);
147 return log_oom();
148 }
149
150 r = hashmap_put(m->etc_hosts_by_name, bn->name, bn);
151 if (r < 0) {
152 free(bn->name);
153 free(bn);
154 return log_oom();
155 }
156 }
157
158 if (item) {
159 if (!GREEDY_REALLOC(bn->items, bn->n_allocated, bn->n_items+1))
160 return log_oom();
161
162 bn->items[bn->n_items++] = item;
163 }
164 }
165
166 return 0;
167}
168
169static int parse_line(Manager *m, unsigned nr, const char *line) {
170 _cleanup_free_ char *address = NULL;
171 _cleanup_strv_free_ char **names = NULL;
172 union in_addr_union in;
173 bool suppressed = false;
174 int family, r;
175
176 assert(m);
177 assert(line);
178
179 r = extract_first_word(&line, &address, NULL, EXTRACT_RELAX);
180 if (r < 0)
181 return log_error_errno(r, "Couldn't extract address, in line /etc/hosts:%u.", nr);
182 if (r == 0) {
183 log_error("Premature end of line, in line /etc/hosts:%u.", nr);
184 return -EINVAL;
185 }
186
187 r = in_addr_from_string_auto(address, &family, &in);
188 if (r < 0)
189 return log_error_errno(r, "Address '%s' is invalid, in line /etc/hosts:%u.", address, nr);
190
191 for (;;) {
192 _cleanup_free_ char *name = NULL;
193
194 r = extract_first_word(&line, &name, NULL, EXTRACT_RELAX);
195 if (r < 0)
196 return log_error_errno(r, "Couldn't extract host name, in line /etc/hosts:%u.", nr);
197 if (r == 0)
198 break;
199
200 r = dns_name_is_valid(name);
201 if (r <= 0)
202 return log_error_errno(r, "Hostname %s is not valid, ignoring, in line /etc/hosts:%u.", name, nr);
203
204 if (is_localhost(name)) {
205 /* Suppress the "localhost" line that is often seen */
206 suppressed = true;
207 continue;
208 }
209
210 r = strv_push(&names, name);
211 if (r < 0)
212 return log_oom();
213
214 name = NULL;
215 }
216
217 if (strv_isempty(names)) {
218
219 if (suppressed)
220 return 0;
221
222 log_error("Line is missing any host names, in line /etc/hosts:%u.", nr);
223 return -EINVAL;
224 }
225
226 /* Takes possession of the names strv */
227 r = add_item(m, family, &in, names);
228 if (r < 0)
229 return r;
230
231 names = NULL;
232 return r;
233}
234
235int manager_etc_hosts_read(Manager *m) {
236 _cleanup_fclose_ FILE *f = NULL;
237 char line[LINE_MAX];
238 struct stat st;
239 usec_t ts;
240 unsigned nr = 0;
241 int r;
242
243 assert_se(sd_event_now(m->event, clock_boottime_or_monotonic(), &ts) >= 0);
244
245 /* See if we checked /etc/hosts recently already */
246 if (m->etc_hosts_last != USEC_INFINITY && m->etc_hosts_last + ETC_HOSTS_RECHECK_USEC > ts)
247 return 0;
248
249 m->etc_hosts_last = ts;
250
251 if (m->etc_hosts_mtime != USEC_INFINITY) {
252 if (stat("/etc/hosts", &st) < 0) {
253 if (errno == ENOENT) {
254 r = 0;
255 goto clear;
256 }
257
258 return log_error_errno(errno, "Failed to stat /etc/hosts: %m");
259 }
260
261 /* Did the mtime change? If not, there's no point in re-reading the file. */
262 if (timespec_load(&st.st_mtim) == m->etc_hosts_mtime)
263 return 0;
264 }
265
266 f = fopen("/etc/hosts", "re");
267 if (!f) {
268 if (errno == ENOENT) {
269 r = 0;
270 goto clear;
271 }
272
273 return log_error_errno(errno, "Failed to open /etc/hosts: %m");
274 }
275
276 /* Take the timestamp at the beginning of processing, so that any changes made later are read on the next
277 * invocation */
278 r = fstat(fileno(f), &st);
279 if (r < 0)
280 return log_error_errno(errno, "Failed to fstat() /etc/hosts: %m");
281
282 manager_etc_hosts_flush(m);
283
284 FOREACH_LINE(line, f, return log_error_errno(errno, "Failed to read /etc/hosts: %m")) {
285 char *l;
286
313cefa1 287 nr++;
dd0bc0f1
LP
288
289 l = strstrip(line);
290 if (isempty(l))
291 continue;
292 if (l[0] == '#')
293 continue;
294
295 r = parse_line(m, nr, l);
296 if (r == -ENOMEM) /* On OOM we abandon the half-built-up structure. All other errors we ignore and proceed */
297 goto clear;
298 }
299
300 m->etc_hosts_mtime = timespec_load(&st.st_mtim);
301 m->etc_hosts_last = ts;
302
303 return 1;
304
305clear:
306 manager_etc_hosts_flush(m);
307 return r;
308}
309
310int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer) {
311 bool found_a = false, found_aaaa = false;
312 EtcHostsItemByName *bn;
313 EtcHostsItem k = {};
314 DnsResourceKey *t;
315 const char *name;
316 unsigned i;
317 int r;
318
319 assert(m);
320 assert(q);
321 assert(answer);
322
323 r = manager_etc_hosts_read(m);
324 if (r < 0)
325 return r;
326
327 name = dns_question_first_name(q);
328 if (!name)
329 return 0;
330
331 r = dns_name_address(name, &k.family, &k.address);
332 if (r > 0) {
333 EtcHostsItem *item;
334 DnsResourceKey *found_ptr = NULL;
335
336 item = set_get(m->etc_hosts_by_address, &k);
337 if (!item)
338 return 0;
339
340 /* We have an address in /etc/hosts that matches the queried name. Let's return successful. Actual data
341 * we'll only return if the request was for PTR. */
342
343 DNS_QUESTION_FOREACH(t, q) {
344 if (!IN_SET(t->type, DNS_TYPE_PTR, DNS_TYPE_ANY))
345 continue;
346 if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY))
347 continue;
348
1c02e7ba 349 r = dns_name_equal(dns_resource_key_name(t), name);
dd0bc0f1
LP
350 if (r < 0)
351 return r;
352 if (r > 0) {
353 found_ptr = t;
354 break;
355 }
356 }
357
358 if (found_ptr) {
359 char **n;
360
361 r = dns_answer_reserve(answer, strv_length(item->names));
362 if (r < 0)
363 return r;
364
365 STRV_FOREACH(n, item->names) {
366 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
367
368 rr = dns_resource_record_new(found_ptr);
369 if (!rr)
370 return -ENOMEM;
371
372 rr->ptr.name = strdup(*n);
373 if (!rr->ptr.name)
374 return -ENOMEM;
375
376 r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED);
377 if (r < 0)
378 return r;
379 }
380 }
381
382 return 1;
383 }
384
385 bn = hashmap_get(m->etc_hosts_by_name, name);
386 if (!bn)
387 return 0;
388
389 r = dns_answer_reserve(answer, bn->n_items);
390 if (r < 0)
391 return r;
392
393 DNS_QUESTION_FOREACH(t, q) {
394 if (!IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_TYPE_ANY))
395 continue;
396 if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY))
397 continue;
398
1c02e7ba 399 r = dns_name_equal(dns_resource_key_name(t), name);
dd0bc0f1
LP
400 if (r < 0)
401 return r;
402 if (r == 0)
403 continue;
404
405 if (IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_ANY))
406 found_a = true;
407 if (IN_SET(t->type, DNS_TYPE_AAAA, DNS_TYPE_ANY))
408 found_aaaa = true;
409
410 if (found_a && found_aaaa)
411 break;
412 }
413
414 for (i = 0; i < bn->n_items; i++) {
415 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
416
4050e04b
MP
417 if ((!found_a && bn->items[i]->family == AF_INET) ||
418 (!found_aaaa && bn->items[i]->family == AF_INET6))
dd0bc0f1
LP
419 continue;
420
421 r = dns_resource_record_new_address(&rr, bn->items[i]->family, &bn->items[i]->address, bn->name);
422 if (r < 0)
423 return r;
424
425 r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED);
426 if (r < 0)
427 return r;
428 }
429
4050e04b 430 return found_a || found_aaaa;
dd0bc0f1 431}