]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-etc-hosts.c
resolve: use in_addr_data type for storing address
[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 struct in_addr_data address;
17
18 char **names;
19 } EtcHostsItem;
20
21 typedef struct EtcHostsItemByName {
22 char *name;
23
24 struct in_addr_data **addresses;
25 size_t n_addresses, n_allocated;
26 } EtcHostsItemByName;
27
28 void manager_etc_hosts_flush(Manager *m) {
29 EtcHostsItem *item;
30 EtcHostsItemByName *bn;
31
32 while ((item = hashmap_steal_first(m->etc_hosts_by_address))) {
33 strv_free(item->names);
34 free(item);
35 }
36
37 while ((bn = hashmap_steal_first(m->etc_hosts_by_name))) {
38 free(bn->name);
39 free(bn->addresses);
40 free(bn);
41 }
42
43 m->etc_hosts_by_address = hashmap_free(m->etc_hosts_by_address);
44 m->etc_hosts_by_name = hashmap_free(m->etc_hosts_by_name);
45
46 m->etc_hosts_mtime = USEC_INFINITY;
47 }
48
49 static int parse_line(Manager *m, unsigned nr, const char *line) {
50 _cleanup_free_ char *address_str = NULL;
51 struct in_addr_data address = {};
52 bool suppressed = false;
53 EtcHostsItem *item;
54 int r;
55
56 assert(m);
57 assert(line);
58
59 r = extract_first_word(&line, &address_str, NULL, EXTRACT_RELAX);
60 if (r < 0)
61 return log_error_errno(r, "Couldn't extract address, in line /etc/hosts:%u.", nr);
62 if (r == 0) {
63 log_error("Premature end of line, in line /etc/hosts:%u.", nr);
64 return -EINVAL;
65 }
66
67 r = in_addr_from_string_auto(address_str, &address.family, &address.address);
68 if (r < 0)
69 return log_error_errno(r, "Address '%s' is invalid, in line /etc/hosts:%u.", address_str, nr);
70
71 r = in_addr_is_null(address.family, &address.address);
72 if (r < 0)
73 return r;
74 if (r > 0)
75 /* This is an 0.0.0.0 or :: item, which we assume means that we shall map the specified hostname to
76 * nothing. */
77 item = NULL;
78 else {
79 /* If this is a normal address, then, simply add entry mapping it to the specified names */
80
81 item = hashmap_get(m->etc_hosts_by_address, &address);
82 if (!item) {
83 r = hashmap_ensure_allocated(&m->etc_hosts_by_address, &in_addr_data_hash_ops);
84 if (r < 0)
85 return log_oom();
86
87 item = new0(EtcHostsItem, 1);
88 if (!item)
89 return log_oom();
90
91 item->address = address;
92
93 r = hashmap_put(m->etc_hosts_by_address, &item->address, item);
94 if (r < 0) {
95 free(item);
96 return log_oom();
97 }
98 }
99 }
100
101 for (;;) {
102 _cleanup_free_ char *name = NULL;
103 EtcHostsItemByName *bn;
104
105 r = extract_first_word(&line, &name, NULL, EXTRACT_RELAX);
106 if (r < 0)
107 return log_error_errno(r, "Couldn't extract host name, in line /etc/hosts:%u.", nr);
108 if (r == 0)
109 break;
110
111 r = dns_name_is_valid(name);
112 if (r <= 0)
113 return log_error_errno(r, "Hostname %s is not valid, ignoring, in line /etc/hosts:%u.", name, nr);
114
115 if (is_localhost(name)) {
116 /* Suppress the "localhost" line that is often seen */
117 suppressed = true;
118 continue;
119 }
120
121 if (item) {
122 r = strv_extend(&item->names, name);
123 if (r < 0)
124 return log_oom();
125 }
126
127 bn = hashmap_get(m->etc_hosts_by_name, name);
128 if (!bn) {
129 r = hashmap_ensure_allocated(&m->etc_hosts_by_name, &dns_name_hash_ops);
130 if (r < 0)
131 return log_oom();
132
133 bn = new0(EtcHostsItemByName, 1);
134 if (!bn)
135 return log_oom();
136
137 r = hashmap_put(m->etc_hosts_by_name, name, bn);
138 if (r < 0) {
139 free(bn);
140 return log_oom();
141 }
142
143 bn->name = TAKE_PTR(name);
144 }
145
146 if (item) {
147 if (!GREEDY_REALLOC(bn->addresses, bn->n_allocated, bn->n_addresses + 1))
148 return log_oom();
149
150 bn->addresses[bn->n_addresses++] = &item->address;
151 }
152
153 suppressed = true;
154 }
155
156 if (!suppressed) {
157 log_error("Line is missing any host names, in line /etc/hosts:%u.", nr);
158 return -EINVAL;
159 }
160
161 return 0;
162 }
163
164 static int manager_etc_hosts_read(Manager *m) {
165 _cleanup_fclose_ FILE *f = NULL;
166 char line[LINE_MAX];
167 struct stat st;
168 usec_t ts;
169 unsigned nr = 0;
170 int r;
171
172 assert_se(sd_event_now(m->event, clock_boottime_or_monotonic(), &ts) >= 0);
173
174 /* See if we checked /etc/hosts recently already */
175 if (m->etc_hosts_last != USEC_INFINITY && m->etc_hosts_last + ETC_HOSTS_RECHECK_USEC > ts)
176 return 0;
177
178 m->etc_hosts_last = ts;
179
180 if (m->etc_hosts_mtime != USEC_INFINITY) {
181 if (stat("/etc/hosts", &st) < 0) {
182 if (errno == ENOENT) {
183 r = 0;
184 goto clear;
185 }
186
187 return log_error_errno(errno, "Failed to stat /etc/hosts: %m");
188 }
189
190 /* Did the mtime change? If not, there's no point in re-reading the file. */
191 if (timespec_load(&st.st_mtim) == m->etc_hosts_mtime)
192 return 0;
193 }
194
195 f = fopen("/etc/hosts", "re");
196 if (!f) {
197 if (errno == ENOENT) {
198 r = 0;
199 goto clear;
200 }
201
202 return log_error_errno(errno, "Failed to open /etc/hosts: %m");
203 }
204
205 /* Take the timestamp at the beginning of processing, so that any changes made later are read on the next
206 * invocation */
207 r = fstat(fileno(f), &st);
208 if (r < 0)
209 return log_error_errno(errno, "Failed to fstat() /etc/hosts: %m");
210
211 manager_etc_hosts_flush(m);
212
213 FOREACH_LINE(line, f, return log_error_errno(errno, "Failed to read /etc/hosts: %m")) {
214 char *l;
215
216 nr++;
217
218 l = strstrip(line);
219 if (isempty(l))
220 continue;
221 if (l[0] == '#')
222 continue;
223
224 r = parse_line(m, nr, l);
225 if (r == -ENOMEM) /* On OOM we abandon the half-built-up structure. All other errors we ignore and proceed */
226 goto clear;
227 }
228
229 m->etc_hosts_mtime = timespec_load(&st.st_mtim);
230 m->etc_hosts_last = ts;
231
232 return 1;
233
234 clear:
235 manager_etc_hosts_flush(m);
236 return r;
237 }
238
239 int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer) {
240 bool found_a = false, found_aaaa = false;
241 struct in_addr_data k = {};
242 EtcHostsItemByName *bn;
243 DnsResourceKey *t;
244 const char *name;
245 unsigned i;
246 int r;
247
248 assert(m);
249 assert(q);
250 assert(answer);
251
252 if (!m->read_etc_hosts)
253 return 0;
254
255 r = manager_etc_hosts_read(m);
256 if (r < 0)
257 return r;
258
259 name = dns_question_first_name(q);
260 if (!name)
261 return 0;
262
263 r = dns_name_address(name, &k.family, &k.address);
264 if (r > 0) {
265 EtcHostsItem *item;
266 DnsResourceKey *found_ptr = NULL;
267
268 item = hashmap_get(m->etc_hosts_by_address, &k);
269 if (!item)
270 return 0;
271
272 /* We have an address in /etc/hosts that matches the queried name. Let's return successful. Actual data
273 * we'll only return if the request was for PTR. */
274
275 DNS_QUESTION_FOREACH(t, q) {
276 if (!IN_SET(t->type, DNS_TYPE_PTR, DNS_TYPE_ANY))
277 continue;
278 if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY))
279 continue;
280
281 r = dns_name_equal(dns_resource_key_name(t), name);
282 if (r < 0)
283 return r;
284 if (r > 0) {
285 found_ptr = t;
286 break;
287 }
288 }
289
290 if (found_ptr) {
291 char **n;
292
293 r = dns_answer_reserve(answer, strv_length(item->names));
294 if (r < 0)
295 return r;
296
297 STRV_FOREACH(n, item->names) {
298 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
299
300 rr = dns_resource_record_new(found_ptr);
301 if (!rr)
302 return -ENOMEM;
303
304 rr->ptr.name = strdup(*n);
305 if (!rr->ptr.name)
306 return -ENOMEM;
307
308 r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED);
309 if (r < 0)
310 return r;
311 }
312 }
313
314 return 1;
315 }
316
317 bn = hashmap_get(m->etc_hosts_by_name, name);
318 if (!bn)
319 return 0;
320
321 r = dns_answer_reserve(answer, bn->n_addresses);
322 if (r < 0)
323 return r;
324
325 DNS_QUESTION_FOREACH(t, q) {
326 if (!IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_TYPE_ANY))
327 continue;
328 if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY))
329 continue;
330
331 r = dns_name_equal(dns_resource_key_name(t), name);
332 if (r < 0)
333 return r;
334 if (r == 0)
335 continue;
336
337 if (IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_ANY))
338 found_a = true;
339 if (IN_SET(t->type, DNS_TYPE_AAAA, DNS_TYPE_ANY))
340 found_aaaa = true;
341
342 if (found_a && found_aaaa)
343 break;
344 }
345
346 for (i = 0; i < bn->n_addresses; i++) {
347 _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
348
349 if ((!found_a && bn->addresses[i]->family == AF_INET) ||
350 (!found_aaaa && bn->addresses[i]->family == AF_INET6))
351 continue;
352
353 r = dns_resource_record_new_address(&rr, bn->addresses[i]->family, &bn->addresses[i]->address, bn->name);
354 if (r < 0)
355 return r;
356
357 r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED);
358 if (r < 0)
359 return r;
360 }
361
362 return found_a || found_aaaa;
363 }