1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include "alloc-util.h"
4 #include "conf-parser.h"
6 #include "creds-util.h"
7 #include "dns-domain.h"
8 #include "extract-word.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"
24 DEFINE_CONFIG_PARSE_ENUM(config_parse_dns_stub_listener_mode
, dns_stub_listener_mode
, DnsStubListenerMode
, "Failed to parse DNS stub listener mode setting");
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;
36 r
= in_addr_port_ifindex_name_from_string_auto(word
, &family
, &address
, &port
, &ifindex
, &server_name
);
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
))
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))
49 /* Filter out duplicates */
50 s
= dns_server_find(manager_get_first_dns_server(m
, type
), family
, &address
, port
, ifindex
, server_name
);
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
);
58 return dns_server_new(m
, NULL
, type
, NULL
, family
, &address
, port
, ifindex
, server_name
, RESOLVE_CONFIG_SOURCE_FILE
);
61 int manager_parse_dns_server_string_and_warn(Manager
*m
, DnsServerType type
, const char *string
) {
68 _cleanup_free_
char *word
= NULL
;
70 r
= extract_first_word(&string
, &word
, NULL
, 0);
74 r
= manager_add_dns_server_by_string(m
, type
, word
);
76 log_warning_errno(r
, "Failed to add DNS server address '%s', ignoring: %m", word
);
80 static int manager_add_search_domain_by_string(Manager
*m
, const char *domain
) {
88 route_only
= *domain
== '~';
92 if (dns_name_is_root(domain
) || streq(domain
, "*")) {
97 r
= dns_search_domain_find(m
->search_domains
, domain
, &d
);
101 dns_search_domain_move_back_and_unmark(d
);
103 r
= dns_search_domain_new(m
, &d
, DNS_SEARCH_DOMAIN_SYSTEM
, NULL
, domain
);
108 d
->route_only
= route_only
;
112 int manager_parse_search_domains_and_warn(Manager
*m
, const char *string
) {
119 _cleanup_free_
char *word
= NULL
;
121 r
= extract_first_word(&string
, &word
, NULL
, EXTRACT_UNQUOTE
);
125 r
= manager_add_search_domain_by_string(m
, word
);
127 log_warning_errno(r
, "Failed to add search domain '%s', ignoring: %m", word
);
131 int config_parse_dns_servers(
133 const char *filename
,
136 unsigned section_line
,
143 Manager
*m
= ASSERT_PTR(userdata
);
151 /* Empty assignment means clear the list */
152 dns_server_unlink_all(manager_get_first_dns_server(m
, ltype
));
154 /* Otherwise, add to the list */
155 r
= manager_parse_dns_server_string_and_warn(m
, ltype
, rvalue
);
157 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
158 "Failed to parse DNS server string '%s', ignoring.", rvalue
);
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;
173 int config_parse_search_domains(
175 const char *filename
,
178 unsigned section_line
,
185 Manager
*m
= ASSERT_PTR(userdata
);
193 /* Empty assignment means clear the list */
194 dns_search_domain_unlink_all(m
->search_domains
);
196 /* Otherwise, add to the list */
197 r
= manager_parse_search_domains_and_warn(m
, rvalue
);
199 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
200 "Failed to parse search domains string '%s', ignoring.", rvalue
);
205 /* If we have a manual setting, then we stop reading
206 * /etc/resolv.conf */
207 m
->read_resolv_conf
= false;
212 int config_parse_dnssd_service_name(
214 const char *filename
,
217 unsigned section_line
,
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
},
236 DnssdService
*s
= ASSERT_PTR(userdata
);
237 _cleanup_free_
char *name
= NULL
;
244 if (isempty(rvalue
)) {
245 s
->name_template
= mfree(s
->name_template
);
249 r
= specifier_printf(rvalue
, DNS_LABEL_MAX
, specifier_table
, NULL
, NULL
, &name
);
251 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
252 "Invalid service instance name template '%s', ignoring assignment: %m", rvalue
);
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.",
263 return free_and_strdup_warn(&s
->name_template
, rvalue
);
266 int config_parse_dnssd_service_type(
268 const char *filename
,
271 unsigned section_line
,
278 DnssdService
*s
= ASSERT_PTR(userdata
);
285 if (isempty(rvalue
)) {
286 s
->type
= mfree(s
->type
);
290 if (!dnssd_srv_type_is_valid(rvalue
)) {
291 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0, "Service type is invalid. Ignoring.");
295 r
= free_and_strdup(&s
->type
, rvalue
);
302 int config_parse_dnssd_service_subtype(
304 const char *filename
,
307 unsigned section_line
,
314 DnssdService
*s
= ASSERT_PTR(userdata
);
320 if (isempty(rvalue
)) {
321 s
->subtype
= mfree(s
->subtype
);
325 if (!dns_subtype_name_is_valid(rvalue
)) {
326 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0, "Service subtype is invalid. Ignoring.");
330 return free_and_strdup_warn(&s
->subtype
, rvalue
);
333 int config_parse_dnssd_txt(
335 const char *filename
,
338 unsigned section_line
,
345 _cleanup_(dnssd_txtdata_freep
) DnssdTxtData
*txt_data
= NULL
;
346 DnssdService
*s
= ASSERT_PTR(userdata
);
347 DnsTxtItem
*last
= NULL
;
353 if (isempty(rvalue
)) {
354 /* Flush out collected items */
355 s
->txt_data_items
= dnssd_txtdata_free_all(s
->txt_data_items
);
359 txt_data
= new0(DnssdTxtData
, 1);
364 _cleanup_free_
char *word
= NULL
, *key
= NULL
, *value
= NULL
;
365 _cleanup_free_
void *decoded
= NULL
;
370 r
= extract_first_word(&rvalue
, &word
, NULL
,
371 EXTRACT_UNQUOTE
|EXTRACT_CUNESCAPE
|EXTRACT_UNESCAPE_RELAX
);
377 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
, "Invalid syntax, ignoring: %s", rvalue
);
381 r
= split_pair(word
, "=", &key
, &value
);
385 key
= TAKE_PTR(word
);
387 if (!ascii_is_valid(key
)) {
388 log_syntax(unit
, LOG_WARNING
, filename
, line
, 0, "Invalid key, ignoring: %s", key
);
394 case DNS_TXT_ITEM_DATA
:
396 r
= unbase64mem(value
, &decoded
, &length
);
400 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
401 "Invalid base64 encoding, ignoring: %s", value
);
406 r
= dnssd_txt_item_new_from_data(key
, decoded
, length
, &i
);
411 case DNS_TXT_ITEM_TEXT
:
412 r
= dnssd_txt_item_new_from_string(key
, value
, &i
);
418 assert_not_reached();
421 LIST_INSERT_AFTER(items
, txt_data
->txts
, last
, i
);
425 if (txt_data
->txts
) {
426 LIST_PREPEND(items
, s
->txt_data_items
, txt_data
);
433 int config_parse_dns_stub_listener_extra(
435 const char *filename
,
438 unsigned section_line
,
445 _cleanup_free_ DnsStubListenerExtra
*stub
= NULL
;
446 Manager
*m
= userdata
;
455 if (isempty(rvalue
)) {
456 m
->dns_extra_stub_listeners
= ordered_set_free(m
->dns_extra_stub_listeners
);
460 r
= dns_stub_listener_extra_new(m
, &stub
);
464 p
= startswith(rvalue
, "udp:");
466 stub
->mode
= DNS_STUB_LISTENER_UDP
;
468 p
= startswith(rvalue
, "tcp:");
470 stub
->mode
= DNS_STUB_LISTENER_TCP
;
472 stub
->mode
= DNS_STUB_LISTENER_YES
;
477 r
= in_addr_port_ifindex_name_from_string_auto(p
, &stub
->family
, &stub
->address
, &stub
->port
, NULL
, NULL
);
479 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
480 "Failed to parse address in %s=%s, ignoring assignment: %m",
485 r
= ordered_set_ensure_put(&m
->dns_extra_stub_listeners
, &dns_stub_listener_extra_hash_ops
, stub
);
489 log_syntax(unit
, LOG_WARNING
, filename
, line
, r
,
490 "Failed to store %s=%s, ignoring assignment: %m", lvalue
, rvalue
);
499 static void read_credentials(Manager
*m
) {
500 _cleanup_free_
char *dns
= NULL
, *domains
= NULL
;
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
)
510 r
= read_credential_strings_many("network.dns", &dns
,
511 "network.search_domains", &domains
);
513 log_warning_errno(r
, "Failed to read credentials, ignoring: %m");
516 r
= manager_parse_dns_server_string_and_warn(m
, DNS_SERVER_SYSTEM
, dns
);
518 log_warning_errno(r
, "Failed to parse credential network.dns '%s', ignoring.", dns
);
520 m
->read_resolv_conf
= false;
524 r
= manager_parse_search_domains_and_warn(m
, domains
);
526 log_warning_errno(r
, "Failed to parse credential network.search_domains '%s', ignoring.", domains
);
528 m
->read_resolv_conf
= false;
532 struct ProcCmdlineInfo
{
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
;
542 static int proc_cmdline_callback(const char *key
, const char *value
, void *data
) {
543 struct ProcCmdlineInfo
*info
= ASSERT_PTR(data
);
547 assert(info
->manager
);
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. */
552 if (streq(key
, "nameserver")) {
554 if (proc_cmdline_value_missing(key
, value
))
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;
563 r
= manager_parse_dns_server_string_and_warn(info
->manager
, DNS_SERVER_SYSTEM
, value
);
565 log_warning_errno(r
, "Failed to parse DNS server string '%s', ignoring.", value
);
567 info
->manager
->read_resolv_conf
= false;
569 } else if (streq(key
, "domain")) {
571 if (proc_cmdline_value_missing(key
, value
))
574 if (!info
->search_domain_unlinked
) {
575 dns_search_domain_unlink_all(info
->manager
->search_domains
);
576 info
->search_domain_unlinked
= true;
579 r
= manager_parse_search_domains_and_warn(info
->manager
, value
);
581 log_warning_errno(r
, "Failed to parse credential provided search domain string '%s', ignoring.", value
);
583 info
->manager
->read_resolv_conf
= false;
589 static void read_proc_cmdline(Manager
*m
) {
594 r
= proc_cmdline_parse(proc_cmdline_callback
, &(struct ProcCmdlineInfo
) { .manager
= m
}, 0);
596 log_warning_errno(r
, "Failed to read kernel command line, ignoring: %m");
599 int manager_parse_config_file(Manager
*m
) {
604 r
= config_parse_standard_file_with_dropins(
605 "systemd/resolved.conf",
607 config_item_perf_lookup
, resolved_gperf_lookup
,
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. */
616 if (m
->need_builtin_fallbacks
) {
617 r
= manager_parse_dns_server_string_and_warn(m
, DNS_SERVER_FALLBACK
, DNS_SERVERS
);
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
;
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
;