]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/resolve/resolved-conf.c
libfido2: pick up debug logging from libfido2, and funnel it through our log subsystem
[thirdparty/systemd.git] / src / resolve / resolved-conf.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
4e945a6f 2
b5efdb8a 3#include "alloc-util.h"
4e945a6f 4#include "conf-parser.h"
28db6fbf 5#include "constants.h"
116687f2
LP
6#include "creds-util.h"
7#include "dns-domain.h"
b5efdb8a 8#include "extract-word.h"
6501dd31 9#include "hexdecoct.h"
6bedfcbb 10#include "parse-util.h"
116687f2 11#include "proc-cmdline.h"
4e945a6f 12#include "resolved-conf.h"
be28f72d 13#include "resolved-dns-search-domain.h"
1f05101f 14#include "resolved-dns-stub.h"
116687f2
LP
15#include "resolved-dnssd.h"
16#include "resolved-manager.h"
2d95d81f 17#include "socket-netlink.h"
6501dd31 18#include "specifier.h"
1ae43295 19#include "string-table.h"
6bedfcbb 20#include "string-util.h"
1f05101f 21#include "strv.h"
6501dd31 22#include "utf8.h"
4e945a6f 23
1ae43295
DM
24DEFINE_CONFIG_PARSE_ENUM(config_parse_dns_stub_listener_mode, dns_stub_listener_mode, DnsStubListenerMode, "Failed to parse DNS stub listener mode setting");
25
8e97dc67 26static int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word) {
da9de738 27 _cleanup_free_ char *server_name = NULL;
636e813d 28 union in_addr_union address;
2817157b 29 int family, r, ifindex = 0;
da9de738 30 uint16_t port;
0eac4623 31 DnsServer *s;
4e945a6f
LP
32
33 assert(m);
636e813d
LP
34 assert(word);
35
da9de738 36 r = in_addr_port_ifindex_name_from_string_auto(word, &family, &address, &port, &ifindex, &server_name);
636e813d
LP
37 if (r < 0)
38 return r;
4e945a6f 39
281df579
LP
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.
da9de738
YW
45 * See dns_transaction_port() and dns_server_port(). */
46 if (IN_SET(port, 53, 853))
47 port = 0;
48
636e813d 49 /* Filter out duplicates */
1b860092 50 s = dns_server_find(manager_get_first_dns_server(m, type), family, &address, port, ifindex, server_name);
636e813d 51 if (s) {
c4c6ee3a
ZJS
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(). */
0b58db65 54 dns_server_move_back_and_unmark(s);
636e813d
LP
55 return 0;
56 }
57
da9de738 58 return dns_server_new(m, NULL, type, NULL, family, &address, port, ifindex, server_name);
636e813d
LP
59}
60
281df579 61int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string) {
636e813d
LP
62 int r;
63
64 assert(m);
65 assert(string);
66
9ed794a3 67 for (;;) {
b5efdb8a 68 _cleanup_free_ char *word = NULL;
880603a1
SS
69
70 r = extract_first_word(&string, &word, NULL, 0);
9d84fdec 71 if (r <= 0)
636e813d 72 return r;
4e945a6f 73
636e813d 74 r = manager_add_dns_server_by_string(m, type, word);
281df579 75 if (r < 0)
2817157b 76 log_warning_errno(r, "Failed to add DNS server address '%s', ignoring: %m", word);
a51c1048 77 }
a51c1048
LP
78}
79
8e97dc67 80static int manager_add_search_domain_by_string(Manager *m, const char *domain) {
a51c1048 81 DnsSearchDomain *d;
adc800a6 82 bool route_only;
a51c1048
LP
83 int r;
84
85 assert(m);
86 assert(domain);
87
adc800a6
LP
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
a51c1048
LP
97 r = dns_search_domain_find(m->search_domains, domain, &d);
98 if (r < 0)
99 return r;
adc800a6 100 if (r > 0)
a51c1048 101 dns_search_domain_move_back_and_unmark(d);
adc800a6
LP
102 else {
103 r = dns_search_domain_new(m, &d, DNS_SEARCH_DOMAIN_SYSTEM, NULL, domain);
104 if (r < 0)
105 return r;
a51c1048
LP
106 }
107
adc800a6
LP
108 d->route_only = route_only;
109 return 0;
a51c1048
LP
110}
111
112int manager_parse_search_domains_and_warn(Manager *m, const char *string) {
113 int r;
114
115 assert(m);
116 assert(string);
117
9ed794a3 118 for (;;) {
a51c1048
LP
119 _cleanup_free_ char *word = NULL;
120
4ec85141 121 r = extract_first_word(&string, &word, NULL, EXTRACT_UNQUOTE);
9d84fdec 122 if (r <= 0)
a51c1048 123 return r;
a51c1048
LP
124
125 r = manager_add_search_domain_by_string(m, word);
126 if (r < 0)
2817157b 127 log_warning_errno(r, "Failed to add search domain '%s', ignoring: %m", word);
4e945a6f 128 }
4e945a6f
LP
129}
130
636e813d 131int config_parse_dns_servers(
4e945a6f
LP
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
99534007 143 Manager *m = ASSERT_PTR(userdata);
4e945a6f
LP
144 int r;
145
146 assert(filename);
147 assert(lvalue);
148 assert(rvalue);
4e945a6f 149
3e684349 150 if (isempty(rvalue))
5cb36f41 151 /* Empty assignment means clear the list */
4b95f179 152 dns_server_unlink_all(manager_get_first_dns_server(m, ltype));
3e684349 153 else {
b938cb90 154 /* Otherwise, add to the list */
281df579 155 r = manager_parse_dns_server_string_and_warn(m, ltype, rvalue);
5cb36f41 156 if (r < 0) {
94069bef
YW
157 log_syntax(unit, LOG_WARNING, filename, line, r,
158 "Failed to parse DNS server string '%s', ignoring.", rvalue);
5cb36f41
LP
159 return 0;
160 }
4e945a6f
LP
161 }
162
281df579
LP
163 /* If we have a manual setting, then we stop reading
164 * /etc/resolv.conf */
5cb36f41
LP
165 if (ltype == DNS_SERVER_SYSTEM)
166 m->read_resolv_conf = false;
00fa60ae
LP
167 if (ltype == DNS_SERVER_FALLBACK)
168 m->need_builtin_fallbacks = false;
5cb36f41 169
4e945a6f
LP
170 return 0;
171}
172
a51c1048
LP
173int 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
99534007 185 Manager *m = ASSERT_PTR(userdata);
a51c1048
LP
186 int r;
187
188 assert(filename);
189 assert(lvalue);
190 assert(rvalue);
a51c1048
LP
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) {
94069bef
YW
199 log_syntax(unit, LOG_WARNING, filename, line, r,
200 "Failed to parse search domains string '%s', ignoring.", rvalue);
a51c1048
LP
201 return 0;
202 }
203 }
204
281df579
LP
205 /* If we have a manual setting, then we stop reading
206 * /etc/resolv.conf */
a51c1048 207 m->read_resolv_conf = false;
6501dd31
DR
208
209 return 0;
210}
211
d63542bc
YW
212int 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
07e4a8dc 224 static const Specifier specifier_table[] = {
e93387f3 225 { 'a', specifier_architecture, NULL },
07e4a8dc 226 { 'b', specifier_boot_id, NULL },
e93387f3 227 { 'B', specifier_os_build_id, NULL },
9a5893e9 228 { 'H', specifier_hostname, NULL }, /* We will use specifier_dnssd_hostname(). */
e93387f3 229 { 'm', specifier_machine_id, NULL },
07e4a8dc 230 { 'o', specifier_os_id, NULL },
e93387f3 231 { 'v', specifier_kernel_release, NULL },
07e4a8dc 232 { 'w', specifier_os_version_id, NULL },
07e4a8dc
RB
233 { 'W', specifier_os_variant_id, NULL },
234 {}
235 };
99534007 236 DnssdService *s = ASSERT_PTR(userdata);
07e4a8dc 237 _cleanup_free_ char *name = NULL;
6501dd31
DR
238 int r;
239
240 assert(filename);
241 assert(lvalue);
242 assert(rvalue);
6501dd31
DR
243
244 if (isempty(rvalue)) {
0c949643
YW
245 s->name_template = mfree(s->name_template);
246 return 0;
34136e15 247 }
6501dd31 248
de61a04b 249 r = specifier_printf(rvalue, DNS_LABEL_MAX, specifier_table, NULL, NULL, &name);
dd2e9e1d
YW
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 }
07e4a8dc
RB
255
256 if (!dns_service_name_is_valid(name)) {
dd2e9e1d
YW
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;
07e4a8dc
RB
261 }
262
dd2e9e1d 263 return free_and_strdup_warn(&s->name_template, rvalue);
6501dd31
DR
264}
265
94069bef
YW
266int 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
99534007 278 DnssdService *s = ASSERT_PTR(userdata);
6501dd31
DR
279 int r;
280
281 assert(filename);
282 assert(lvalue);
283 assert(rvalue);
6501dd31
DR
284
285 if (isempty(rvalue)) {
94069bef
YW
286 s->type = mfree(s->type);
287 return 0;
6501dd31
DR
288 }
289
290 if (!dnssd_srv_type_is_valid(rvalue)) {
94069bef
YW
291 log_syntax(unit, LOG_WARNING, filename, line, 0, "Service type is invalid. Ignoring.");
292 return 0;
6501dd31
DR
293 }
294
295 r = free_and_strdup(&s->type, rvalue);
296 if (r < 0)
297 return log_oom();
298
299 return 0;
300}
301
94069bef
YW
302int config_parse_dnssd_txt(
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
400f54fb 314 _cleanup_(dnssd_txtdata_freep) DnssdTxtData *txt_data = NULL;
99534007 315 DnssdService *s = ASSERT_PTR(userdata);
6501dd31
DR
316 DnsTxtItem *last = NULL;
317
318 assert(filename);
319 assert(lvalue);
320 assert(rvalue);
6501dd31 321
400f54fb
DR
322 if (isempty(rvalue)) {
323 /* Flush out collected items */
324 s->txt_data_items = dnssd_txtdata_free_all(s->txt_data_items);
6501dd31 325 return 0;
400f54fb
DR
326 }
327
328 txt_data = new0(DnssdTxtData, 1);
329 if (!txt_data)
330 return log_oom();
6501dd31
DR
331
332 for (;;) {
94069bef 333 _cleanup_free_ char *word = NULL, *key = NULL, *value = NULL;
6501dd31
DR
334 _cleanup_free_ void *decoded = NULL;
335 size_t length = 0;
336 DnsTxtItem *i;
337 int r;
338
339 r = extract_first_word(&rvalue, &word, NULL,
3141089f 340 EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX);
6501dd31
DR
341 if (r == 0)
342 break;
343 if (r == -ENOMEM)
344 return log_oom();
94069bef
YW
345 if (r < 0) {
346 log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
347 return 0;
348 }
6501dd31
DR
349
350 r = split_pair(word, "=", &key, &value);
351 if (r == -ENOMEM)
352 return log_oom();
1cc6c93a
YW
353 if (r == -EINVAL)
354 key = TAKE_PTR(word);
6501dd31
DR
355
356 if (!ascii_is_valid(key)) {
94069bef
YW
357 log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid key, ignoring: %s", key);
358 continue;
6501dd31
DR
359 }
360
361 switch (ltype) {
362
363 case DNS_TXT_ITEM_DATA:
364 if (value) {
365 r = unbase64mem(value, strlen(value), &decoded, &length);
366 if (r == -ENOMEM)
367 return log_oom();
94069bef
YW
368 if (r < 0) {
369 log_syntax(unit, LOG_WARNING, filename, line, r,
370 "Invalid base64 encoding, ignoring: %s", value);
371 continue;
372 }
6501dd31
DR
373 }
374
375 r = dnssd_txt_item_new_from_data(key, decoded, length, &i);
376 if (r < 0)
377 return log_oom();
378 break;
379
380 case DNS_TXT_ITEM_TEXT:
381 r = dnssd_txt_item_new_from_string(key, value, &i);
382 if (r < 0)
383 return log_oom();
384 break;
385
386 default:
04499a70 387 assert_not_reached();
6501dd31
DR
388 }
389
d70f15f5 390 LIST_INSERT_AFTER(items, txt_data->txts, last, i);
6501dd31
DR
391 last = i;
392 }
400f54fb 393
d70f15f5 394 if (txt_data->txts) {
400f54fb 395 LIST_PREPEND(items, s->txt_data_items, txt_data);
94069bef 396 TAKE_PTR(txt_data);
400f54fb 397 }
a51c1048
LP
398
399 return 0;
400}
401
1f05101f
SS
402int config_parse_dns_stub_listener_extra(
403 const char *unit,
404 const char *filename,
405 unsigned line,
406 const char *section,
407 unsigned section_line,
408 const char *lvalue,
409 int ltype,
410 const char *rvalue,
411 void *data,
412 void *userdata) {
413
36aaabc3 414 _cleanup_free_ DnsStubListenerExtra *stub = NULL;
1f05101f 415 Manager *m = userdata;
1f05101f
SS
416 const char *p;
417 int r;
418
419 assert(filename);
420 assert(lvalue);
421 assert(rvalue);
422 assert(data);
423
424 if (isempty(rvalue)) {
425 m->dns_extra_stub_listeners = ordered_set_free(m->dns_extra_stub_listeners);
426 return 0;
427 }
428
0354029b 429 r = dns_stub_listener_extra_new(m, &stub);
7314b397 430 if (r < 0)
1f05101f 431 return log_oom();
1f05101f 432
7314b397
YW
433 p = startswith(rvalue, "udp:");
434 if (p)
435 stub->mode = DNS_STUB_LISTENER_UDP;
436 else {
437 p = startswith(rvalue, "tcp:");
438 if (p)
439 stub->mode = DNS_STUB_LISTENER_TCP;
440 else {
441 stub->mode = DNS_STUB_LISTENER_YES;
442 p = rvalue;
1f05101f
SS
443 }
444 }
445
222eaaf9 446 r = in_addr_port_ifindex_name_from_string_auto(p, &stub->family, &stub->address, &stub->port, NULL, NULL);
7314b397
YW
447 if (r < 0) {
448 log_syntax(unit, LOG_WARNING, filename, line, r,
449 "Failed to parse address in %s=%s, ignoring assignment: %m",
450 lvalue, rvalue);
451 return 0;
1f05101f
SS
452 }
453
7314b397
YW
454 r = ordered_set_ensure_put(&m->dns_extra_stub_listeners, &dns_stub_listener_extra_hash_ops, stub);
455 if (r == -ENOMEM)
456 return log_oom();
457 if (r < 0) {
458 log_syntax(unit, LOG_WARNING, filename, line, r,
459 "Failed to store %s=%s, ignoring assignment: %m", lvalue, rvalue);
460 return 0;
1f05101f
SS
461 }
462
7314b397 463 TAKE_PTR(stub);
1f05101f
SS
464
465 return 0;
466}
467
116687f2
LP
468static void read_credentials(Manager *m) {
469 _cleanup_free_ char *dns = NULL, *domains = NULL;
470 int r;
471
472 assert(m);
473
474 /* Hmm, if we aren't supposed to read /etc/resolv.conf because the DNS settings were already
475 * configured explicitly in our config file, we don't want to honour credentials either */
476 if (!m->read_resolv_conf)
477 return;
478
55ace8e5
ZJS
479 r = read_credential_strings_many("network.dns", &dns,
480 "network.search_domains", &domains);
481 if (r < 0)
116687f2
LP
482 log_warning_errno(r, "Failed to read credentials, ignoring: %m");
483
484 if (dns) {
485 r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_SYSTEM, dns);
486 if (r < 0)
1c7ed990 487 log_warning_errno(r, "Failed to parse credential network.dns '%s', ignoring.", dns);
116687f2
LP
488
489 m->read_resolv_conf = false;
490 }
491
492 if (domains) {
493 r = manager_parse_search_domains_and_warn(m, domains);
494 if (r < 0)
1c7ed990 495 log_warning_errno(r, "Failed to parse credential network.search_domains '%s', ignoring.", domains);
116687f2
LP
496
497 m->read_resolv_conf = false;
498 }
499}
500
501struct ProcCmdlineInfo {
502 Manager *manager;
503
504 /* If there's a setting configured via /proc/cmdline we want to reset the configured lists, but only
505 * once, so that multiple nameserver= or domain= settings can be specified on the kernel command line
506 * and will be combined. These booleans will be set once we erase the list once. */
507 bool dns_server_unlinked;
508 bool search_domain_unlinked;
509};
510
511static int proc_cmdline_callback(const char *key, const char *value, void *data) {
512 struct ProcCmdlineInfo *info = ASSERT_PTR(data);
513 int r;
514
515 assert(info->manager);
516
517 /* The kernel command line option names are chosen to be compatible with what various tools already
518 * interpret, for example dracut and SUSE Linux. */
519
520 if (proc_cmdline_key_streq(key, "nameserver")) {
521 if (!info->dns_server_unlinked) {
522 /* The kernel command line overrides any prior configuration */
523 dns_server_unlink_all(manager_get_first_dns_server(info->manager, DNS_SERVER_SYSTEM));
524 info->dns_server_unlinked = true;
525 }
526
527 r = manager_parse_dns_server_string_and_warn(info->manager, DNS_SERVER_SYSTEM, value);
528 if (r < 0)
529 log_warning_errno(r, "Failed to parse DNS server string '%s', ignoring.", value);
530
531 info->manager->read_resolv_conf = false;
532
533 } else if (proc_cmdline_key_streq(key, "domain")) {
534
535 if (!info->search_domain_unlinked) {
536 dns_search_domain_unlink_all(info->manager->search_domains);
537 info->search_domain_unlinked = true;
538 }
539
540 r = manager_parse_search_domains_and_warn(info->manager, value);
541 if (r < 0)
542 log_warning_errno(r, "Failed to parse credential provided search domain string '%s', ignoring.", value);
543
544 info->manager->read_resolv_conf = false;
545 }
546
547 return 0;
548}
549
550static void read_proc_cmdline(Manager *m) {
551 int r;
552
553 assert(m);
554
555 r = proc_cmdline_parse(proc_cmdline_callback, &(struct ProcCmdlineInfo) { .manager = m }, 0);
556 if (r < 0)
557 log_warning_errno(r, "Failed to read kernel command line, ignoring: %m");
558}
559
4e945a6f 560int manager_parse_config_file(Manager *m) {
00fa60ae
LP
561 int r;
562
4e945a6f
LP
563 assert(m);
564
07e0ffc8
FB
565 r = config_parse_config_file("resolved.conf", "Resolve\0",
566 config_item_perf_lookup, resolved_gperf_lookup,
567 CONFIG_PARSE_WARN, m);
00fa60ae
LP
568 if (r < 0)
569 return r;
570
116687f2
LP
571 read_credentials(m); /* credentials are only used when nothing is explicitly configured … */
572 read_proc_cmdline(m); /* … but kernel command line overrides local configuration. */
573
00fa60ae 574 if (m->need_builtin_fallbacks) {
281df579 575 r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_FALLBACK, DNS_SERVERS);
00fa60ae
LP
576 if (r < 0)
577 return r;
578 }
579
7e8facb3 580#if !HAVE_OPENSSL_OR_GCRYPT
42303dcb 581 if (m->dnssec_mode != DNSSEC_NO) {
7e8facb3 582 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.");
42303dcb
YW
583 m->dnssec_mode = DNSSEC_NO;
584 }
585#endif
5d67a7ae 586
7e8facb3 587#if !ENABLE_DNS_OVER_TLS
c9299be2 588 if (m->dns_over_tls_mode != DNS_OVER_TLS_NO) {
4310bfc2 589 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.");
c9299be2 590 m->dns_over_tls_mode = DNS_OVER_TLS_NO;
5d67a7ae
IT
591 }
592#endif
00fa60ae
LP
593 return 0;
594
4e945a6f 595}