]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-conf.c
Merge pull request #16635 from keszybz/do-not-for-each-word
[thirdparty/systemd.git] / src / resolve / resolved-conf.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include "alloc-util.h"
4 #include "conf-parser.h"
5 #include "def.h"
6 #include "extract-word.h"
7 #include "hexdecoct.h"
8 #include "parse-util.h"
9 #include "resolved-conf.h"
10 #include "resolved-dnssd.h"
11 #include "resolved-manager.h"
12 #include "resolved-dns-search-domain.h"
13 #include "resolved-dns-stub.h"
14 #include "dns-domain.h"
15 #include "socket-netlink.h"
16 #include "specifier.h"
17 #include "string-table.h"
18 #include "string-util.h"
19 #include "strv.h"
20 #include "utf8.h"
21
22 DEFINE_CONFIG_PARSE_ENUM(config_parse_dns_stub_listener_mode, dns_stub_listener_mode, DnsStubListenerMode, "Failed to parse DNS stub listener mode setting");
23
24 static const char* const dns_stub_listener_mode_table[_DNS_STUB_LISTENER_MODE_MAX] = {
25 [DNS_STUB_LISTENER_NO] = "no",
26 [DNS_STUB_LISTENER_UDP] = "udp",
27 [DNS_STUB_LISTENER_TCP] = "tcp",
28 [DNS_STUB_LISTENER_YES] = "yes",
29 };
30 DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dns_stub_listener_mode, DnsStubListenerMode, DNS_STUB_LISTENER_YES);
31
32 static void dns_stub_listener_extra_hash_func(const DNSStubListenerExtra *a, struct siphash *state) {
33 assert(a);
34
35 siphash24_compress(&a->mode, sizeof(a->mode), state);
36 siphash24_compress(&a->family, sizeof(a->family), state);
37 siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state);
38 siphash24_compress(&a->port, sizeof(a->port), state);
39 }
40
41 static int dns_stub_listener_extra_compare_func(const DNSStubListenerExtra *a, const DNSStubListenerExtra *b) {
42 int r;
43
44 assert(a);
45 assert(b);
46
47 r = CMP(a->mode, b->mode);
48 if (r != 0)
49 return r;
50
51 r = CMP(a->family, b->family);
52 if (r != 0)
53 return r;
54
55 r = memcmp(&a->address, &b->address, FAMILY_ADDRESS_SIZE(a->family));
56 if (r != 0)
57 return r;
58
59 return CMP(a->port, b->port);
60 }
61
62 DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
63 dns_stub_listener_extra_hash_ops,
64 DNSStubListenerExtra,
65 dns_stub_listener_extra_hash_func,
66 dns_stub_listener_extra_compare_func,
67 dns_stub_listener_extra_free);
68
69 static int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word) {
70 _cleanup_free_ char *server_name = NULL;
71 union in_addr_union address;
72 int family, r, ifindex = 0;
73 uint16_t port;
74 DnsServer *s;
75
76 assert(m);
77 assert(word);
78
79 r = in_addr_port_ifindex_name_from_string_auto(word, &family, &address, &port, &ifindex, &server_name);
80 if (r < 0)
81 return r;
82
83 if (IN_SET(port, 53, 853))
84 port = 0;
85
86 /* Silently filter out 0.0.0.0 and 127.0.0.53 (our own stub DNS listener) */
87 if (!dns_server_address_valid(family, &address))
88 return 0;
89
90 /* By default, the port number is determined with the transaction feature level.
91 * See dns_transaction_port() and dns_server_port(). */
92 if (IN_SET(port, 53, 853))
93 port = 0;
94
95 /* Filter out duplicates */
96 s = dns_server_find(manager_get_first_dns_server(m, type), family, &address, port, ifindex, server_name);
97 if (s) {
98 /*
99 * Drop the marker. This is used to find the servers
100 * that ceased to exist, see
101 * manager_mark_dns_servers() and
102 * manager_flush_marked_dns_servers().
103 */
104 dns_server_move_back_and_unmark(s);
105 return 0;
106 }
107
108 return dns_server_new(m, NULL, type, NULL, family, &address, port, ifindex, server_name);
109 }
110
111 int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string) {
112 int r;
113
114 assert(m);
115 assert(string);
116
117 for (;;) {
118 _cleanup_free_ char *word = NULL;
119
120 r = extract_first_word(&string, &word, NULL, 0);
121 if (r < 0)
122 return r;
123 if (r == 0)
124 break;
125
126 r = manager_add_dns_server_by_string(m, type, word);
127 if (r < 0)
128 log_warning_errno(r, "Failed to add DNS server address '%s', ignoring: %m", word);
129 }
130
131 return 0;
132 }
133
134 static int manager_add_search_domain_by_string(Manager *m, const char *domain) {
135 DnsSearchDomain *d;
136 bool route_only;
137 int r;
138
139 assert(m);
140 assert(domain);
141
142 route_only = *domain == '~';
143 if (route_only)
144 domain++;
145
146 if (dns_name_is_root(domain) || streq(domain, "*")) {
147 route_only = true;
148 domain = ".";
149 }
150
151 r = dns_search_domain_find(m->search_domains, domain, &d);
152 if (r < 0)
153 return r;
154 if (r > 0)
155 dns_search_domain_move_back_and_unmark(d);
156 else {
157 r = dns_search_domain_new(m, &d, DNS_SEARCH_DOMAIN_SYSTEM, NULL, domain);
158 if (r < 0)
159 return r;
160 }
161
162 d->route_only = route_only;
163 return 0;
164 }
165
166 int manager_parse_search_domains_and_warn(Manager *m, const char *string) {
167 int r;
168
169 assert(m);
170 assert(string);
171
172 for (;;) {
173 _cleanup_free_ char *word = NULL;
174
175 r = extract_first_word(&string, &word, NULL, EXTRACT_UNQUOTE);
176 if (r < 0)
177 return r;
178 if (r == 0)
179 break;
180
181 r = manager_add_search_domain_by_string(m, word);
182 if (r < 0)
183 log_warning_errno(r, "Failed to add search domain '%s', ignoring: %m", word);
184 }
185
186 return 0;
187 }
188
189 int config_parse_dns_servers(
190 const char *unit,
191 const char *filename,
192 unsigned line,
193 const char *section,
194 unsigned section_line,
195 const char *lvalue,
196 int ltype,
197 const char *rvalue,
198 void *data,
199 void *userdata) {
200
201 Manager *m = userdata;
202 int r;
203
204 assert(filename);
205 assert(lvalue);
206 assert(rvalue);
207 assert(m);
208
209 if (isempty(rvalue))
210 /* Empty assignment means clear the list */
211 dns_server_unlink_all(manager_get_first_dns_server(m, ltype));
212 else {
213 /* Otherwise, add to the list */
214 r = manager_parse_dns_server_string_and_warn(m, ltype, rvalue);
215 if (r < 0) {
216 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse DNS server string '%s'. Ignoring.", rvalue);
217 return 0;
218 }
219 }
220
221 /* If we have a manual setting, then we stop reading
222 * /etc/resolv.conf */
223 if (ltype == DNS_SERVER_SYSTEM)
224 m->read_resolv_conf = false;
225 if (ltype == DNS_SERVER_FALLBACK)
226 m->need_builtin_fallbacks = false;
227
228 return 0;
229 }
230
231 int config_parse_search_domains(
232 const char *unit,
233 const char *filename,
234 unsigned line,
235 const char *section,
236 unsigned section_line,
237 const char *lvalue,
238 int ltype,
239 const char *rvalue,
240 void *data,
241 void *userdata) {
242
243 Manager *m = userdata;
244 int r;
245
246 assert(filename);
247 assert(lvalue);
248 assert(rvalue);
249 assert(m);
250
251 if (isempty(rvalue))
252 /* Empty assignment means clear the list */
253 dns_search_domain_unlink_all(m->search_domains);
254 else {
255 /* Otherwise, add to the list */
256 r = manager_parse_search_domains_and_warn(m, rvalue);
257 if (r < 0) {
258 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse search domains string '%s'. Ignoring.", rvalue);
259 return 0;
260 }
261 }
262
263 /* If we have a manual setting, then we stop reading
264 * /etc/resolv.conf */
265 m->read_resolv_conf = false;
266
267 return 0;
268 }
269
270 int config_parse_dnssd_service_name(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
271 static const Specifier specifier_table[] = {
272 { 'm', specifier_machine_id, NULL },
273 { 'b', specifier_boot_id, NULL },
274 { 'H', specifier_host_name, NULL },
275 { 'v', specifier_kernel_release, NULL },
276 { 'a', specifier_architecture, NULL },
277 { 'o', specifier_os_id, NULL },
278 { 'w', specifier_os_version_id, NULL },
279 { 'B', specifier_os_build_id, NULL },
280 { 'W', specifier_os_variant_id, NULL },
281 {}
282 };
283 DnssdService *s = userdata;
284 _cleanup_free_ char *name = NULL;
285 int r;
286
287 assert(filename);
288 assert(lvalue);
289 assert(rvalue);
290 assert(s);
291
292 if (isempty(rvalue)) {
293 log_syntax(unit, LOG_ERR, filename, line, 0, "Service instance name can't be empty. Ignoring.");
294 return -EINVAL;
295 }
296
297 r = free_and_strdup(&s->name_template, rvalue);
298 if (r < 0)
299 return log_oom();
300
301 r = specifier_printf(s->name_template, specifier_table, NULL, &name);
302 if (r < 0)
303 return log_debug_errno(r, "Failed to replace specifiers: %m");
304
305 if (!dns_service_name_is_valid(name)) {
306 log_syntax(unit, LOG_ERR, filename, line, 0, "Service instance name template renders to invalid name '%s'. Ignoring.", name);
307 return -EINVAL;
308 }
309
310 return 0;
311 }
312
313 int config_parse_dnssd_service_type(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
314 DnssdService *s = userdata;
315 int r;
316
317 assert(filename);
318 assert(lvalue);
319 assert(rvalue);
320 assert(s);
321
322 if (isempty(rvalue)) {
323 log_syntax(unit, LOG_ERR, filename, line, 0, "Service type can't be empty. Ignoring.");
324 return -EINVAL;
325 }
326
327 if (!dnssd_srv_type_is_valid(rvalue)) {
328 log_syntax(unit, LOG_ERR, filename, line, 0, "Service type is invalid. Ignoring.");
329 return -EINVAL;
330 }
331
332 r = free_and_strdup(&s->type, rvalue);
333 if (r < 0)
334 return log_oom();
335
336 return 0;
337 }
338
339 int config_parse_dnssd_txt(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata) {
340 _cleanup_(dnssd_txtdata_freep) DnssdTxtData *txt_data = NULL;
341 DnssdService *s = userdata;
342 DnsTxtItem *last = NULL;
343
344 assert(filename);
345 assert(lvalue);
346 assert(rvalue);
347 assert(s);
348
349 if (isempty(rvalue)) {
350 /* Flush out collected items */
351 s->txt_data_items = dnssd_txtdata_free_all(s->txt_data_items);
352 return 0;
353 }
354
355 txt_data = new0(DnssdTxtData, 1);
356 if (!txt_data)
357 return log_oom();
358
359 for (;;) {
360 _cleanup_free_ char *word = NULL;
361 _cleanup_free_ char *key = NULL;
362 _cleanup_free_ char *value = NULL;
363 _cleanup_free_ void *decoded = NULL;
364 size_t length = 0;
365 DnsTxtItem *i;
366 int r;
367
368 r = extract_first_word(&rvalue, &word, NULL,
369 EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE|EXTRACT_CUNESCAPE_RELAX);
370 if (r == 0)
371 break;
372 if (r == -ENOMEM)
373 return log_oom();
374 if (r < 0)
375 return log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
376
377 r = split_pair(word, "=", &key, &value);
378 if (r == -ENOMEM)
379 return log_oom();
380 if (r == -EINVAL)
381 key = TAKE_PTR(word);
382
383 if (!ascii_is_valid(key)) {
384 log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid syntax, ignoring: %s", key);
385 return -EINVAL;
386 }
387
388 switch (ltype) {
389
390 case DNS_TXT_ITEM_DATA:
391 if (value) {
392 r = unbase64mem(value, strlen(value), &decoded, &length);
393 if (r == -ENOMEM)
394 return log_oom();
395 if (r < 0)
396 return log_syntax(unit, LOG_ERR, filename, line, r,
397 "Invalid base64 encoding, ignoring: %s", value);
398 }
399
400 r = dnssd_txt_item_new_from_data(key, decoded, length, &i);
401 if (r < 0)
402 return log_oom();
403 break;
404
405 case DNS_TXT_ITEM_TEXT:
406 r = dnssd_txt_item_new_from_string(key, value, &i);
407 if (r < 0)
408 return log_oom();
409 break;
410
411 default:
412 assert_not_reached("Unknown type of Txt config");
413 }
414
415 LIST_INSERT_AFTER(items, txt_data->txt, last, i);
416 last = i;
417 }
418
419 if (!LIST_IS_EMPTY(txt_data->txt)) {
420 LIST_PREPEND(items, s->txt_data_items, txt_data);
421 txt_data = NULL;
422 }
423
424 return 0;
425 }
426
427 int config_parse_dns_stub_listener_extra(
428 const char *unit,
429 const char *filename,
430 unsigned line,
431 const char *section,
432 unsigned section_line,
433 const char *lvalue,
434 int ltype,
435 const char *rvalue,
436 void *data,
437 void *userdata) {
438
439 _cleanup_free_ DNSStubListenerExtra *stub = NULL;
440 Manager *m = userdata;
441 const char *p;
442 int r;
443
444 assert(filename);
445 assert(lvalue);
446 assert(rvalue);
447 assert(data);
448
449 if (isempty(rvalue)) {
450 m->dns_extra_stub_listeners = ordered_set_free(m->dns_extra_stub_listeners);
451 return 0;
452 }
453
454 r = dns_stub_listener_extra_new(&stub);
455 if (r < 0)
456 return log_oom();
457
458 p = startswith(rvalue, "udp:");
459 if (p)
460 stub->mode = DNS_STUB_LISTENER_UDP;
461 else {
462 p = startswith(rvalue, "tcp:");
463 if (p)
464 stub->mode = DNS_STUB_LISTENER_TCP;
465 else {
466 stub->mode = DNS_STUB_LISTENER_YES;
467 p = rvalue;
468 }
469 }
470
471 r = in_addr_port_from_string_auto(p, &stub->family, &stub->address, &stub->port);
472 if (r < 0) {
473 log_syntax(unit, LOG_WARNING, filename, line, r,
474 "Failed to parse address in %s=%s, ignoring assignment: %m",
475 lvalue, rvalue);
476 return 0;
477 }
478
479 r = ordered_set_ensure_put(&m->dns_extra_stub_listeners, &dns_stub_listener_extra_hash_ops, stub);
480 if (r == -ENOMEM)
481 return log_oom();
482 if (r < 0) {
483 log_syntax(unit, LOG_WARNING, filename, line, r,
484 "Failed to store %s=%s, ignoring assignment: %m", lvalue, rvalue);
485 return 0;
486 }
487
488 TAKE_PTR(stub);
489
490 return 0;
491 }
492
493 int manager_parse_config_file(Manager *m) {
494 int r;
495
496 assert(m);
497
498 r = config_parse_many_nulstr(
499 PKGSYSCONFDIR "/resolved.conf",
500 CONF_PATHS_NULSTR("systemd/resolved.conf.d"),
501 "Resolve\0",
502 config_item_perf_lookup, resolved_gperf_lookup,
503 CONFIG_PARSE_WARN,
504 m,
505 NULL);
506 if (r < 0)
507 return r;
508
509 if (m->need_builtin_fallbacks) {
510 r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_FALLBACK, DNS_SERVERS);
511 if (r < 0)
512 return r;
513 }
514
515 #if ! HAVE_GCRYPT
516 if (m->dnssec_mode != DNSSEC_NO) {
517 log_warning("DNSSEC option cannot be enabled or set to allow-downgrade when systemd-resolved is built without gcrypt support. Turning off DNSSEC support.");
518 m->dnssec_mode = DNSSEC_NO;
519 }
520 #endif
521
522 #if ! ENABLE_DNS_OVER_TLS
523 if (m->dns_over_tls_mode != DNS_OVER_TLS_NO) {
524 log_warning("DNS-over-TLS option cannot be enabled or set to opportunistic when systemd-resolved is built without DNS-over-TLS support. Turning off DNS-over-TLS support.");
525 m->dns_over_tls_mode = DNS_OVER_TLS_NO;
526 }
527 #endif
528 return 0;
529
530 }