]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/resolve/resolved-conf.c
journal: Serialize __MONOTONIC_TIMESTAMP metadata field as well
[thirdparty/systemd.git] / src / resolve / resolved-conf.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "alloc-util.h"
4 #include "conf-parser.h"
5 #include "constants.h"
6 #include "creds-util.h"
7 #include "dns-domain.h"
8 #include "extract-word.h"
9 #include "hexdecoct.h"
10 #include "parse-util.h"
11 #include "proc-cmdline.h"
12 #include "resolved-conf.h"
13 #include "resolved-dns-search-domain.h"
14 #include "resolved-dns-stub.h"
15 #include "resolved-dnssd.h"
16 #include "resolved-manager.h"
17 #include "socket-netlink.h"
18 #include "specifier.h"
19 #include "string-table.h"
20 #include "string-util.h"
21 #include "strv.h"
22 #include "utf8.h"
23
24 DEFINE_CONFIG_PARSE_ENUM(config_parse_dns_stub_listener_mode, dns_stub_listener_mode, DnsStubListenerMode, "Failed to parse DNS stub listener mode setting");
25
26 static int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word) {
27 _cleanup_free_ char *server_name = NULL;
28 union in_addr_union address;
29 int family, r, ifindex = 0;
30 uint16_t port;
31 DnsServer *s;
32
33 assert(m);
34 assert(word);
35
36 r = in_addr_port_ifindex_name_from_string_auto(word, &family, &address, &port, &ifindex, &server_name);
37 if (r < 0)
38 return r;
39
40 /* Silently filter out 0.0.0.0, 127.0.0.53, 127.0.0.54 (our own stub DNS listener) */
41 if (!dns_server_address_valid(family, &address))
42 return 0;
43
44 /* By default, the port number is determined with the transaction feature level.
45 * See dns_transaction_port() and dns_server_port(). */
46 if (IN_SET(port, 53, 853))
47 port = 0;
48
49 /* Filter out duplicates */
50 s = dns_server_find(manager_get_first_dns_server(m, type), family, &address, port, ifindex, server_name);
51 if (s) {
52 /* Drop the marker. This is used to find the servers that ceased to exist, see
53 * manager_mark_dns_servers() and manager_flush_marked_dns_servers(). */
54 dns_server_move_back_and_unmark(s);
55 return 0;
56 }
57
58 return dns_server_new(m, NULL, type, NULL, family, &address, port, ifindex, server_name, RESOLVE_CONFIG_SOURCE_FILE);
59 }
60
61 int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string) {
62 int r;
63
64 assert(m);
65 assert(string);
66
67 for (;;) {
68 _cleanup_free_ char *word = NULL;
69
70 r = extract_first_word(&string, &word, NULL, 0);
71 if (r <= 0)
72 return r;
73
74 r = manager_add_dns_server_by_string(m, type, word);
75 if (r < 0)
76 log_warning_errno(r, "Failed to add DNS server address '%s', ignoring: %m", word);
77 }
78 }
79
80 static int manager_add_search_domain_by_string(Manager *m, const char *domain) {
81 DnsSearchDomain *d;
82 bool route_only;
83 int r;
84
85 assert(m);
86 assert(domain);
87
88 route_only = *domain == '~';
89 if (route_only)
90 domain++;
91
92 if (dns_name_is_root(domain) || streq(domain, "*")) {
93 route_only = true;
94 domain = ".";
95 }
96
97 r = dns_search_domain_find(m->search_domains, domain, &d);
98 if (r < 0)
99 return r;
100 if (r > 0)
101 dns_search_domain_move_back_and_unmark(d);
102 else {
103 r = dns_search_domain_new(m, &d, DNS_SEARCH_DOMAIN_SYSTEM, NULL, domain);
104 if (r < 0)
105 return r;
106 }
107
108 d->route_only = route_only;
109 return 0;
110 }
111
112 int manager_parse_search_domains_and_warn(Manager *m, const char *string) {
113 int r;
114
115 assert(m);
116 assert(string);
117
118 for (;;) {
119 _cleanup_free_ char *word = NULL;
120
121 r = extract_first_word(&string, &word, NULL, EXTRACT_UNQUOTE);
122 if (r <= 0)
123 return r;
124
125 r = manager_add_search_domain_by_string(m, word);
126 if (r < 0)
127 log_warning_errno(r, "Failed to add search domain '%s', ignoring: %m", word);
128 }
129 }
130
131 int config_parse_dns_servers(
132 const char *unit,
133 const char *filename,
134 unsigned line,
135 const char *section,
136 unsigned section_line,
137 const char *lvalue,
138 int ltype,
139 const char *rvalue,
140 void *data,
141 void *userdata) {
142
143 Manager *m = ASSERT_PTR(userdata);
144 int r;
145
146 assert(filename);
147 assert(lvalue);
148 assert(rvalue);
149
150 if (isempty(rvalue))
151 /* Empty assignment means clear the list */
152 dns_server_unlink_all(manager_get_first_dns_server(m, ltype));
153 else {
154 /* Otherwise, add to the list */
155 r = manager_parse_dns_server_string_and_warn(m, ltype, rvalue);
156 if (r < 0) {
157 log_syntax(unit, LOG_WARNING, filename, line, r,
158 "Failed to parse DNS server string '%s', ignoring.", rvalue);
159 return 0;
160 }
161 }
162
163 /* If we have a manual setting, then we stop reading
164 * /etc/resolv.conf */
165 if (ltype == DNS_SERVER_SYSTEM)
166 m->read_resolv_conf = false;
167 if (ltype == DNS_SERVER_FALLBACK)
168 m->need_builtin_fallbacks = false;
169
170 return 0;
171 }
172
173 int config_parse_search_domains(
174 const char *unit,
175 const char *filename,
176 unsigned line,
177 const char *section,
178 unsigned section_line,
179 const char *lvalue,
180 int ltype,
181 const char *rvalue,
182 void *data,
183 void *userdata) {
184
185 Manager *m = ASSERT_PTR(userdata);
186 int r;
187
188 assert(filename);
189 assert(lvalue);
190 assert(rvalue);
191
192 if (isempty(rvalue))
193 /* Empty assignment means clear the list */
194 dns_search_domain_unlink_all(m->search_domains);
195 else {
196 /* Otherwise, add to the list */
197 r = manager_parse_search_domains_and_warn(m, rvalue);
198 if (r < 0) {
199 log_syntax(unit, LOG_WARNING, filename, line, r,
200 "Failed to parse search domains string '%s', ignoring.", rvalue);
201 return 0;
202 }
203 }
204
205 /* If we have a manual setting, then we stop reading
206 * /etc/resolv.conf */
207 m->read_resolv_conf = false;
208
209 return 0;
210 }
211
212 int config_parse_dnssd_service_name(
213 const char *unit,
214 const char *filename,
215 unsigned line,
216 const char *section,
217 unsigned section_line,
218 const char *lvalue,
219 int ltype,
220 const char *rvalue,
221 void *data,
222 void *userdata) {
223
224 static const Specifier specifier_table[] = {
225 { 'a', specifier_architecture, NULL },
226 { 'b', specifier_boot_id, NULL },
227 { 'B', specifier_os_build_id, NULL },
228 { 'H', specifier_hostname, NULL }, /* We will use specifier_dnssd_hostname(). */
229 { 'm', specifier_machine_id, NULL },
230 { 'o', specifier_os_id, NULL },
231 { 'v', specifier_kernel_release, NULL },
232 { 'w', specifier_os_version_id, NULL },
233 { 'W', specifier_os_variant_id, NULL },
234 {}
235 };
236 DnssdService *s = ASSERT_PTR(userdata);
237 _cleanup_free_ char *name = NULL;
238 int r;
239
240 assert(filename);
241 assert(lvalue);
242 assert(rvalue);
243
244 if (isempty(rvalue)) {
245 s->name_template = mfree(s->name_template);
246 return 0;
247 }
248
249 r = specifier_printf(rvalue, DNS_LABEL_MAX, specifier_table, NULL, NULL, &name);
250 if (r < 0) {
251 log_syntax(unit, LOG_WARNING, filename, line, r,
252 "Invalid service instance name template '%s', ignoring assignment: %m", rvalue);
253 return 0;
254 }
255
256 if (!dns_service_name_is_valid(name)) {
257 log_syntax(unit, LOG_WARNING, filename, line, 0,
258 "Service instance name template '%s' renders to invalid name '%s'. Ignoring assignment.",
259 rvalue, name);
260 return 0;
261 }
262
263 return free_and_strdup_warn(&s->name_template, rvalue);
264 }
265
266 int config_parse_dnssd_service_type(
267 const char *unit,
268 const char *filename,
269 unsigned line,
270 const char *section,
271 unsigned section_line,
272 const char *lvalue,
273 int ltype,
274 const char *rvalue,
275 void *data,
276 void *userdata) {
277
278 DnssdService *s = ASSERT_PTR(userdata);
279 int r;
280
281 assert(filename);
282 assert(lvalue);
283 assert(rvalue);
284
285 if (isempty(rvalue)) {
286 s->type = mfree(s->type);
287 return 0;
288 }
289
290 if (!dnssd_srv_type_is_valid(rvalue)) {
291 log_syntax(unit, LOG_WARNING, filename, line, 0, "Service type is invalid. Ignoring.");
292 return 0;
293 }
294
295 r = free_and_strdup(&s->type, rvalue);
296 if (r < 0)
297 return log_oom();
298
299 return 0;
300 }
301
302 int config_parse_dnssd_service_subtype(
303 const char *unit,
304 const char *filename,
305 unsigned line,
306 const char *section,
307 unsigned section_line,
308 const char *lvalue,
309 int ltype,
310 const char *rvalue,
311 void *data,
312 void *userdata) {
313
314 DnssdService *s = ASSERT_PTR(userdata);
315
316 assert(filename);
317 assert(lvalue);
318 assert(rvalue);
319
320 if (isempty(rvalue)) {
321 s->subtype = mfree(s->subtype);
322 return 0;
323 }
324
325 if (!dns_subtype_name_is_valid(rvalue)) {
326 log_syntax(unit, LOG_WARNING, filename, line, 0, "Service subtype is invalid. Ignoring.");
327 return 0;
328 }
329
330 return free_and_strdup_warn(&s->subtype, rvalue);
331 }
332
333 int config_parse_dnssd_txt(
334 const char *unit,
335 const char *filename,
336 unsigned line,
337 const char *section,
338 unsigned section_line,
339 const char *lvalue,
340 int ltype,
341 const char *rvalue,
342 void *data,
343 void *userdata) {
344
345 _cleanup_(dnssd_txtdata_freep) DnssdTxtData *txt_data = NULL;
346 DnssdService *s = ASSERT_PTR(userdata);
347 DnsTxtItem *last = NULL;
348
349 assert(filename);
350 assert(lvalue);
351 assert(rvalue);
352
353 if (isempty(rvalue)) {
354 /* Flush out collected items */
355 s->txt_data_items = dnssd_txtdata_free_all(s->txt_data_items);
356 return 0;
357 }
358
359 txt_data = new0(DnssdTxtData, 1);
360 if (!txt_data)
361 return log_oom();
362
363 for (;;) {
364 _cleanup_free_ char *word = NULL, *key = NULL, *value = NULL;
365 _cleanup_free_ void *decoded = NULL;
366 size_t length = 0;
367 DnsTxtItem *i;
368 int r;
369
370 r = extract_first_word(&rvalue, &word, NULL,
371 EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX);
372 if (r == 0)
373 break;
374 if (r == -ENOMEM)
375 return log_oom();
376 if (r < 0) {
377 log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
378 return 0;
379 }
380
381 r = split_pair(word, "=", &key, &value);
382 if (r == -ENOMEM)
383 return log_oom();
384 if (r == -EINVAL)
385 key = TAKE_PTR(word);
386
387 if (!ascii_is_valid(key)) {
388 log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid key, ignoring: %s", key);
389 continue;
390 }
391
392 switch (ltype) {
393
394 case DNS_TXT_ITEM_DATA:
395 if (value) {
396 r = unbase64mem(value, &decoded, &length);
397 if (r == -ENOMEM)
398 return log_oom();
399 if (r < 0) {
400 log_syntax(unit, LOG_WARNING, filename, line, r,
401 "Invalid base64 encoding, ignoring: %s", value);
402 continue;
403 }
404 }
405
406 r = dnssd_txt_item_new_from_data(key, decoded, length, &i);
407 if (r < 0)
408 return log_oom();
409 break;
410
411 case DNS_TXT_ITEM_TEXT:
412 r = dnssd_txt_item_new_from_string(key, value, &i);
413 if (r < 0)
414 return log_oom();
415 break;
416
417 default:
418 assert_not_reached();
419 }
420
421 LIST_INSERT_AFTER(items, txt_data->txts, last, i);
422 last = i;
423 }
424
425 if (txt_data->txts) {
426 LIST_PREPEND(items, s->txt_data_items, txt_data);
427 TAKE_PTR(txt_data);
428 }
429
430 return 0;
431 }
432
433 int config_parse_dns_stub_listener_extra(
434 const char *unit,
435 const char *filename,
436 unsigned line,
437 const char *section,
438 unsigned section_line,
439 const char *lvalue,
440 int ltype,
441 const char *rvalue,
442 void *data,
443 void *userdata) {
444
445 _cleanup_free_ DnsStubListenerExtra *stub = NULL;
446 Manager *m = userdata;
447 const char *p;
448 int r;
449
450 assert(filename);
451 assert(lvalue);
452 assert(rvalue);
453 assert(data);
454
455 if (isempty(rvalue)) {
456 m->dns_extra_stub_listeners = ordered_set_free(m->dns_extra_stub_listeners);
457 return 0;
458 }
459
460 r = dns_stub_listener_extra_new(m, &stub);
461 if (r < 0)
462 return log_oom();
463
464 p = startswith(rvalue, "udp:");
465 if (p)
466 stub->mode = DNS_STUB_LISTENER_UDP;
467 else {
468 p = startswith(rvalue, "tcp:");
469 if (p)
470 stub->mode = DNS_STUB_LISTENER_TCP;
471 else {
472 stub->mode = DNS_STUB_LISTENER_YES;
473 p = rvalue;
474 }
475 }
476
477 r = in_addr_port_ifindex_name_from_string_auto(p, &stub->family, &stub->address, &stub->port, NULL, NULL);
478 if (r < 0) {
479 log_syntax(unit, LOG_WARNING, filename, line, r,
480 "Failed to parse address in %s=%s, ignoring assignment: %m",
481 lvalue, rvalue);
482 return 0;
483 }
484
485 r = ordered_set_ensure_put(&m->dns_extra_stub_listeners, &dns_stub_listener_extra_hash_ops, stub);
486 if (r == -ENOMEM)
487 return log_oom();
488 if (r < 0) {
489 log_syntax(unit, LOG_WARNING, filename, line, r,
490 "Failed to store %s=%s, ignoring assignment: %m", lvalue, rvalue);
491 return 0;
492 }
493
494 TAKE_PTR(stub);
495
496 return 0;
497 }
498
499 static void read_credentials(Manager *m) {
500 _cleanup_free_ char *dns = NULL, *domains = NULL;
501 int r;
502
503 assert(m);
504
505 /* Hmm, if we aren't supposed to read /etc/resolv.conf because the DNS settings were already
506 * configured explicitly in our config file, we don't want to honour credentials either */
507 if (!m->read_resolv_conf)
508 return;
509
510 r = read_credential_strings_many("network.dns", &dns,
511 "network.search_domains", &domains);
512 if (r < 0)
513 log_warning_errno(r, "Failed to read credentials, ignoring: %m");
514
515 if (dns) {
516 r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_SYSTEM, dns);
517 if (r < 0)
518 log_warning_errno(r, "Failed to parse credential network.dns '%s', ignoring.", dns);
519
520 m->read_resolv_conf = false;
521 }
522
523 if (domains) {
524 r = manager_parse_search_domains_and_warn(m, domains);
525 if (r < 0)
526 log_warning_errno(r, "Failed to parse credential network.search_domains '%s', ignoring.", domains);
527
528 m->read_resolv_conf = false;
529 }
530 }
531
532 struct ProcCmdlineInfo {
533 Manager *manager;
534
535 /* If there's a setting configured via /proc/cmdline we want to reset the configured lists, but only
536 * once, so that multiple nameserver= or domain= settings can be specified on the kernel command line
537 * and will be combined. These booleans will be set once we erase the list once. */
538 bool dns_server_unlinked;
539 bool search_domain_unlinked;
540 };
541
542 static int proc_cmdline_callback(const char *key, const char *value, void *data) {
543 struct ProcCmdlineInfo *info = ASSERT_PTR(data);
544 int r;
545
546 assert(key);
547 assert(info->manager);
548
549 /* The kernel command line option names are chosen to be compatible with what various tools already
550 * interpret, for example dracut and SUSE Linux. */
551
552 if (streq(key, "nameserver")) {
553
554 if (proc_cmdline_value_missing(key, value))
555 return 0;
556
557 if (!info->dns_server_unlinked) {
558 /* The kernel command line overrides any prior configuration */
559 dns_server_unlink_all(manager_get_first_dns_server(info->manager, DNS_SERVER_SYSTEM));
560 info->dns_server_unlinked = true;
561 }
562
563 r = manager_parse_dns_server_string_and_warn(info->manager, DNS_SERVER_SYSTEM, value);
564 if (r < 0)
565 log_warning_errno(r, "Failed to parse DNS server string '%s', ignoring.", value);
566
567 info->manager->read_resolv_conf = false;
568
569 } else if (streq(key, "domain")) {
570
571 if (proc_cmdline_value_missing(key, value))
572 return 0;
573
574 if (!info->search_domain_unlinked) {
575 dns_search_domain_unlink_all(info->manager->search_domains);
576 info->search_domain_unlinked = true;
577 }
578
579 r = manager_parse_search_domains_and_warn(info->manager, value);
580 if (r < 0)
581 log_warning_errno(r, "Failed to parse credential provided search domain string '%s', ignoring.", value);
582
583 info->manager->read_resolv_conf = false;
584 }
585
586 return 0;
587 }
588
589 static void read_proc_cmdline(Manager *m) {
590 int r;
591
592 assert(m);
593
594 r = proc_cmdline_parse(proc_cmdline_callback, &(struct ProcCmdlineInfo) { .manager = m }, 0);
595 if (r < 0)
596 log_warning_errno(r, "Failed to read kernel command line, ignoring: %m");
597 }
598
599 int manager_parse_config_file(Manager *m) {
600 int r;
601
602 assert(m);
603
604 r = config_parse_standard_file_with_dropins(
605 "systemd/resolved.conf",
606 "Resolve\0",
607 config_item_perf_lookup, resolved_gperf_lookup,
608 CONFIG_PARSE_WARN,
609 /* userdata= */ m);
610 if (r < 0)
611 return r;
612
613 read_credentials(m); /* credentials are only used when nothing is explicitly configured … */
614 read_proc_cmdline(m); /* … but kernel command line overrides local configuration. */
615
616 if (m->need_builtin_fallbacks) {
617 r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_FALLBACK, DNS_SERVERS);
618 if (r < 0)
619 return r;
620 }
621
622 #if !HAVE_OPENSSL_OR_GCRYPT
623 if (m->dnssec_mode != DNSSEC_NO) {
624 log_warning("DNSSEC option cannot be enabled or set to allow-downgrade when systemd-resolved is built without a cryptographic library. Turning off DNSSEC support.");
625 m->dnssec_mode = DNSSEC_NO;
626 }
627 #endif
628
629 #if !ENABLE_DNS_OVER_TLS
630 if (m->dns_over_tls_mode != DNS_OVER_TLS_NO) {
631 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.");
632 m->dns_over_tls_mode = DNS_OVER_TLS_NO;
633 }
634 #endif
635 return 0;
636
637 }