Merge pull request #2276 from poettering/dnssec12

Twelfth DNSSEC PR
This commit is contained in:
Tom Gundersen 2016-01-07 15:05:58 +01:00
commit 4b4310db94
31 changed files with 880 additions and 436 deletions

View file

@ -1054,7 +1054,9 @@ libshared_la_SOURCES = \
src/shared/machine-image.c \
src/shared/machine-image.h \
src/shared/machine-pool.c \
src/shared/machine-pool.h
src/shared/machine-pool.h \
src/shared/resolve-util.c \
src/shared/resolve-util.h
if HAVE_UTMP
libshared_la_SOURCES += \

View file

@ -175,6 +175,16 @@
<para><ulink url="https://tools.ietf.org/html/rfc7646">RFC
7646</ulink> for details on negative trust anchors.</para>
<para>If no negative trust anchor files are configured a built-in
set of well-known private DNS zone domains is used as negative
trust anchors.</para>
<para>It is also possibly to define per-interface negative trust
anchors using the <varname>DNSSECNegativeTrustAnchors=</varname>
setting in
<citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
files.</para>
</refsect1>
<refsect1>
@ -182,7 +192,8 @@
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
<citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>
</para>
</refsect1>

View file

@ -124,23 +124,39 @@
global setting is on.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>MulticastDNS=</varname></term>
<listitem><para>Takes a boolean argument or
<literal>resolve</literal>. Controls Multicast DNS support
(<ulink url="https://tools.ietf.org/html/rfc6762">RFC
6762</ulink>) on the local host. If true, enables full
Multicast DNS responder and resolver support. If false,
disables both. If set to <literal>resolve</literal>, only
resolution support is enabled, but responding is
disabled. Note that
<citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
also maintains per-interface Multicast DNS settings. Multicast
DNS will be enabled on an interface only if the per-interface
and the global setting is on.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>DNSSEC=</varname></term>
<listitem><para>Takes a boolean argument or
<literal>downgrade-ok</literal>. If true all DNS lookups are
DNSSEC-validated locally. If a response for a lookup request
is detected invalid this is returned as lookup failure to
applications. Note that this mode requires a DNS server that
supports DNSSEC. If the DNS server does not properly support
DNSSEC all validations will fail. If set to
<literal>downgrade-ok</literal> DNSSEC validation is
attempted, but if the server does not support DNSSEC properly,
DNSSEC mode is automatically disabled. Note that this mode
makes DNSSEC validation vulnerable to "downgrade" attacks,
where an attacker might be able to trigger a downgrade to
non-DNSSEC mode by synthesizing a DNS response that suggests
DNSSEC was not supported. If set to false, DNS lookups are not
DNSSEC validated.</para>
<literal>allow-downgrade</literal>. If true all DNS lookups are
DNSSEC-validated locally (excluding LLMNR and Multicast
DNS). If a response for a lookup request is detected invalid
this is returned as lookup failure to applications. Note that
this mode requires a DNS server that supports DNSSEC. If the
DNS server does not properly support DNSSEC all validations
will fail. If set to <literal>allow-downgrade</literal> DNSSEC
validation is attempted, but if the server does not support
DNSSEC properly, DNSSEC mode is automatically disabled. Note
that this mode makes DNSSEC validation vulnerable to
"downgrade" attacks, where an attacker might be able to
trigger a downgrade to non-DNSSEC mode by synthesizing a DNS
response that suggests DNSSEC was not supported. If set to
false, DNS lookups are not DNSSEC validated.</para>
<para>Note that DNSSEC validation requires retrieval of
additional DNS data, and thus results in a small DNS look-up
@ -160,8 +176,8 @@
lookups will fail, as it cannot be proved anymore whether
lookups are correctly signed, or validly unsigned. If
<varname>DNSSEC=</varname> is set to
<literal>downgrade-ok</literal> the resolver will
automatically turn of DNSSEC validation in such a case.</para>
<literal>allow-downgrade</literal> the resolver will
automatically turn off DNSSEC validation in such a case.</para>
<para>Client programs looking up DNS data will be informed
whether lookups could be verified using DNSSEC, or whether the
@ -173,11 +189,30 @@
this be required.</para>
<para>It is recommended to set <varname>DNSSEC=</varname> to
true on systems where it is kown that the DNS server supports
true on systems where it is known that the DNS server supports
DNSSEC correctly, and where software or trust anchor updates
happen regularly. On other systems it is recommended to set
<varname>DNSSEC=</varname> to
<literal>missing-ok</literal>.</para>
<literal>allow-downgrade</literal>.</para>
<para>In addition to this global DNSSEC setting
<citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
also maintains per-interface DNSSEC settings. For system DNS
servers (see above), only the global DNSSEC setting is in
effect. For per-interface DNS servers the per-interface
setting is in effect, unless it is unset in which case the
global setting is used instead.</para>
<para>Site-private DNS zones generally conflict with DNSSEC
operation, unless a negative (if the private zone is not
signed) or positive (if the private zone is signed) trust
anchor is configured for them. If
<literal>allow-downgrade</literal> mode is selected, it is
attempted to detect site-private DNS zones using top-level
domains (TLDs) that are not known by the DNS root server. This
logic does not work in all private zone setups.</para>
<para>Defaults to off.</para>
</listitem>
</varlistentry>

View file

@ -277,10 +277,59 @@
<varlistentry>
<term><varname>LLMNR=</varname></term>
<listitem>
<para>A boolean or <literal>resolve</literal>. When true, enables
Link-Local Multicast Name Resolution on the link. When set to
<literal>resolve</literal>, only resolution is enabled, but not
announcement. Defaults to true.</para>
<para>A boolean or <literal>resolve</literal>. When true,
enables <ulink
url="https://tools.ietf.org/html/rfc4795">Link-Local
Multicast Name Resolution</ulink> on the link. When set to
<literal>resolve</literal>, only resolution is enabled,
but not host registration and announcement. Defaults to
true. This setting is read by
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>MulticastDNS=</varname></term>
<listitem>
<para>A boolean or <literal>resolve</literal>. When true,
enables <ulink
url="https://tools.ietf.org/html/rfc6762">Multicast
DNS</ulink> support on the link. When set to
<literal>resolve</literal>, only resolution is enabled,
but not host or service registration and
announcement. Defaults to false. This setting is read by
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>DNSSEC=</varname></term>
<listitem>
<para>A boolean or
<literal>allow-downgrade</literal>. When true, enables
<ulink
url="https://tools.ietf.org/html/rfc4033">DNSSEC</ulink>
DNS validation support on the link. When set to
<literal>allow-downgrade</literal>, compatibility with
non-DNSSEC capable networks is increased, by automatically
turning off DNSEC in this case. This option defines a
per-interface setting for
<citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>'s
global <varname>DNSSEC=</varname> option. Defaults to
false. This setting is read by
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>DNSSECNegativeTrustAnchors=</varname></term>
<listitem><para>A space-separated list of DNSSEC negative
trust anchor domains. If specified and DNSSEC is enabled,
look-ups done via the interface's DNS server will be subject
to the list of negative trust anchors, and not require
authentication for the specified domains, or anything below
it. Use this to disable DNSSEC authentication for specific
private domains, that cannot be proven valid using the
Internet DNS hierarchy. Defaults to the empty list. This
setting is read by
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
</listitem>
</varlistentry>
<varlistentry>
@ -346,19 +395,22 @@
<para>A DNS server address, which must be in the format
described in
<citerefentry project='man-pages'><refentrytitle>inet_pton</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
This option may be specified more than once.</para>
This option may be specified more than once. This setting is read by
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>Domains=</varname></term>
<listitem>
<para>The domains used for DNS resolution over this link.</para>
<para>The domains used for DNS resolution over this link. This setting is read by
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>NTP=</varname></term>
<listitem>
<para>An NTP server address. This option may be specified more than once.</para>
<para>An NTP server address. This option may be specified more than once. This setting is read by
<citerefentry><refentrytitle>systemd-timesyncd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></para>
</listitem>
</varlistentry>
<varlistentry>
@ -1011,9 +1063,10 @@ DHCP=yes
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-networkd</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.link</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>
<citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>
</refsect1>

View file

@ -47,16 +47,34 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k
return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \
}
#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \
scope type name##_from_string(const char *s) { \
int b; \
b = parse_boolean(s); \
if (b == 0) \
return (type) 0; \
else if (b > 0) \
return yes; \
return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \
}
#define _DEFINE_STRING_TABLE_LOOKUP(name,type,scope) \
_DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \
_DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope) \
struct __useless_struct_to_allow_trailing_semicolon__
#define _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,scope) \
_DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \
_DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \
struct __useless_struct_to_allow_trailing_semicolon__
#define DEFINE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,)
#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,static)
#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,static)
#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,static)
#define DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes) _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,)
/* For string conversions where numbers are also acceptable */
#define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max) \
int name##_to_string_alloc(type i, char **str) { \

View file

@ -99,138 +99,17 @@ _public_ int sd_network_get_domains(char ***ret) {
return network_get_strv("DOMAINS", ret);
}
_public_ int sd_network_link_get_setup_state(int ifindex, char **state) {
_cleanup_free_ char *s = NULL, *p = NULL;
int r;
assert_return(ifindex > 0, -EINVAL);
assert_return(state, -EINVAL);
if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0)
return -ENOMEM;
r = parse_env_file(p, NEWLINE, "ADMIN_STATE", &s, NULL);
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
return r;
if (isempty(s))
return -ENODATA;
*state = s;
s = NULL;
return 0;
}
_public_ int sd_network_link_get_network_file(int ifindex, char **filename) {
_cleanup_free_ char *s = NULL, *p = NULL;
int r;
assert_return(ifindex > 0, -EINVAL);
assert_return(filename, -EINVAL);
if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0)
return -ENOMEM;
r = parse_env_file(p, NEWLINE, "NETWORK_FILE", &s, NULL);
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
return r;
if (isempty(s))
return -ENODATA;
*filename = s;
s = NULL;
return 0;
}
_public_ int sd_network_link_get_operational_state(int ifindex, char **state) {
_cleanup_free_ char *s = NULL, *p = NULL;
int r;
assert_return(ifindex > 0, -EINVAL);
assert_return(state, -EINVAL);
if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0)
return -ENOMEM;
r = parse_env_file(p, NEWLINE, "OPER_STATE", &s, NULL);
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
return r;
if (isempty(s))
return -ENODATA;
*state = s;
s = NULL;
return 0;
}
_public_ int sd_network_link_get_llmnr(int ifindex, char **llmnr) {
_cleanup_free_ char *s = NULL, *p = NULL;
int r;
assert_return(ifindex > 0, -EINVAL);
assert_return(llmnr, -EINVAL);
if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0)
return -ENOMEM;
r = parse_env_file(p, NEWLINE, "LLMNR", &s, NULL);
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
return r;
if (isempty(s))
return -ENODATA;
*llmnr = s;
s = NULL;
return 0;
}
_public_ int sd_network_link_get_lldp(int ifindex, char **lldp) {
_cleanup_free_ char *s = NULL, *p = NULL;
size_t size;
int r;
assert_return(ifindex > 0, -EINVAL);
assert_return(lldp, -EINVAL);
if (asprintf(&p, "/run/systemd/netif/lldp/%d", ifindex) < 0)
return -ENOMEM;
r = read_full_file(p, &s, &size);
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
return r;
if (size <= 0)
return -ENODATA;
*lldp = s;
s = NULL;
return 0;
}
int sd_network_link_get_timezone(int ifindex, char **ret) {
static int network_link_get_string(int ifindex, const char *field, char **ret) {
_cleanup_free_ char *s = NULL, *p = NULL;
int r;
assert_return(ifindex > 0, -EINVAL);
assert_return(ret, -EINVAL);
if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0)
if (asprintf(&p, "/run/systemd/netif/links/%i", ifindex) < 0)
return -ENOMEM;
r = parse_env_file(p, NEWLINE, "TIMEZONE", &s, NULL);
r = parse_env_file(p, NEWLINE, field, &s, NULL);
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
@ -240,10 +119,11 @@ int sd_network_link_get_timezone(int ifindex, char **ret) {
*ret = s;
s = NULL;
return 0;
}
static int network_get_link_strv(const char *key, int ifindex, char ***ret) {
static int network_link_get_strv(int ifindex, const char *key, char ***ret) {
_cleanup_free_ char *p = NULL, *s = NULL;
_cleanup_strv_free_ char **a = NULL;
int r;
@ -277,29 +157,86 @@ static int network_get_link_strv(const char *key, int ifindex, char ***ret) {
return r;
}
_public_ int sd_network_link_get_setup_state(int ifindex, char **state) {
return network_link_get_string(ifindex, "ADMIN_STATE", state);
}
_public_ int sd_network_link_get_network_file(int ifindex, char **filename) {
return network_link_get_string(ifindex, "NETWORK_FILE", filename);
}
_public_ int sd_network_link_get_operational_state(int ifindex, char **state) {
return network_link_get_string(ifindex, "OPER_STATE", state);
}
_public_ int sd_network_link_get_llmnr(int ifindex, char **llmnr) {
return network_link_get_string(ifindex, "LLMNR", llmnr);
}
_public_ int sd_network_link_get_mdns(int ifindex, char **mdns) {
return network_link_get_string(ifindex, "MDNS", mdns);
}
_public_ int sd_network_link_get_dnssec(int ifindex, char **dnssec) {
return network_link_get_string(ifindex, "DNSSEC", dnssec);
}
_public_ int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char ***nta) {
return network_link_get_strv(ifindex, "DNSSEC_NTA", nta);
}
_public_ int sd_network_link_get_lldp(int ifindex, char **lldp) {
_cleanup_free_ char *s = NULL, *p = NULL;
size_t size;
int r;
assert_return(ifindex > 0, -EINVAL);
assert_return(lldp, -EINVAL);
if (asprintf(&p, "/run/systemd/netif/lldp/%d", ifindex) < 0)
return -ENOMEM;
r = read_full_file(p, &s, &size);
if (r == -ENOENT)
return -ENODATA;
if (r < 0)
return r;
if (size <= 0)
return -ENODATA;
*lldp = s;
s = NULL;
return 0;
}
int sd_network_link_get_timezone(int ifindex, char **ret) {
return network_link_get_string(ifindex, "TIMEZONE", ret);
}
_public_ int sd_network_link_get_dns(int ifindex, char ***ret) {
return network_get_link_strv("DNS", ifindex, ret);
return network_link_get_strv(ifindex, "DNS", ret);
}
_public_ int sd_network_link_get_ntp(int ifindex, char ***ret) {
return network_get_link_strv("NTP", ifindex, ret);
return network_link_get_strv(ifindex, "NTP", ret);
}
_public_ int sd_network_link_get_domains(int ifindex, char ***ret) {
return network_get_link_strv("DOMAINS", ifindex, ret);
return network_link_get_strv(ifindex, "DOMAINS", ret);
}
_public_ int sd_network_link_get_carrier_bound_to(int ifindex, char ***ret) {
return network_get_link_strv("CARRIER_BOUND_TO", ifindex, ret);
return network_link_get_strv(ifindex, "CARRIER_BOUND_TO", ret);
}
_public_ int sd_network_link_get_carrier_bound_by(int ifindex, char ***ret) {
return network_get_link_strv("CARRIER_BOUND_BY", ifindex, ret);
return network_link_get_strv(ifindex, "CARRIER_BOUND_BY", ret);
}
_public_ int sd_network_link_get_wildcard_domain(int ifindex) {
int r;
_cleanup_free_ char *p = NULL, *s = NULL;
int r;
assert_return(ifindex > 0, -EINVAL);

View file

@ -2868,6 +2868,26 @@ int link_save(Link *link) {
fprintf(f, "LLMNR=%s\n",
resolve_support_to_string(link->network->llmnr));
fprintf(f, "MDNS=%s\n",
resolve_support_to_string(link->network->mdns));
if (link->network->dnssec_mode != _DNSSEC_MODE_INVALID)
fprintf(f, "DNSSEC=%s\n",
dnssec_mode_to_string(link->network->dnssec_mode));
if (!set_isempty(link->network->dnssec_negative_trust_anchors)) {
const char *n;
fputs("DNSSEC_NTA=", f);
space = false;
SET_FOREACH(n, link->network->dnssec_negative_trust_anchors, i) {
if (space)
fputc(' ', f);
fputs(n, f);
space = true;
}
fputc('\n', f);
}
fputs("ADDRESSES=", f);
space = false;
@ -2881,7 +2901,6 @@ int link_save(Link *link) {
fprintf(f, "%s%s/%u", space ? " " : "", address_str, a->prefixlen);
space = true;
}
fputc('\n', f);
fputs("ROUTES=", f);

View file

@ -45,7 +45,10 @@ Network.Address, config_parse_address,
Network.Gateway, config_parse_gateway, 0, 0
Network.Domains, config_parse_domains, 0, offsetof(Network, domains)
Network.DNS, config_parse_strv, 0, offsetof(Network, dns)
Network.LLMNR, config_parse_resolve, 0, offsetof(Network, llmnr)
Network.LLMNR, config_parse_resolve_support, 0, offsetof(Network, llmnr)
Network.MulticastDNS, config_parse_resolve_support, 0, offsetof(Network, mdns)
Network.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Network, dnssec_mode)
Network.DNSSECNegativeTrustAnchors, config_parse_dnssec_negative_trust_anchors, 0, offsetof(Network, dnssec_negative_trust_anchors)
Network.NTP, config_parse_strv, 0, offsetof(Network, ntp)
Network.IPForward, config_parse_address_family_boolean_with_kernel,0, offsetof(Network, ip_forward)
Network.IPMasquerade, config_parse_bool, 0, offsetof(Network, ip_masquerade)

View file

@ -32,6 +32,7 @@
#include "networkd-network.h"
#include "networkd.h"
#include "parse-util.h"
#include "set.h"
#include "stat-util.h"
#include "string-table.h"
#include "string-util.h"
@ -121,6 +122,8 @@ static int network_load_one(Manager *manager, const char *filename) {
network->unicast_flood = true;
network->llmnr = RESOLVE_SUPPORT_YES;
network->mdns = RESOLVE_SUPPORT_NO;
network->dnssec_mode = _DNSSEC_MODE_INVALID;
network->link_local = ADDRESS_FAMILY_IPV6;
@ -275,6 +278,8 @@ void network_free(Network *network) {
free(network->dhcp_server_dns);
free(network->dhcp_server_ntp);
set_free_free(network->dnssec_negative_trust_anchors);
free(network);
}
@ -908,3 +913,55 @@ int config_parse_dhcp_server_ntp(
n->dhcp_server_ntp = m;
}
}
int config_parse_dnssec_negative_trust_anchors(
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) {
const char *p = rvalue;
Network *n = data;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
if (isempty(rvalue)) {
n->dnssec_negative_trust_anchors = set_free_free(n->dnssec_negative_trust_anchors);
return 0;
}
for (;;) {
_cleanup_free_ char *w = NULL;
r = extract_first_word(&p, &w, NULL, 0);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract negative trust anchor domain, ignoring: %s", rvalue);
break;
}
if (r == 0)
break;
r = dns_name_is_valid(w);
if (r <= 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "%s is not a valid domain name, ignoring.", w);
continue;
}
r = set_put(n->dnssec_negative_trust_anchors, w);
if (r < 0)
return log_oom();
if (r > 0)
w = NULL;
}
return 0;
}

View file

@ -22,6 +22,7 @@
***/
#include "condition.h"
#include "resolve-util.h"
typedef struct Network Network;
@ -144,6 +145,9 @@ struct Network {
char **domains, **dns, **ntp, **bind_carrier;
ResolveSupport llmnr;
ResolveSupport mdns;
DnssecMode dnssec_mode;
Set *dnssec_negative_trust_anchors;
LIST_FIELDS(Network, networks);
};
@ -170,6 +174,7 @@ int config_parse_hostname(const char *unit, const char *filename, unsigned line,
int config_parse_timezone(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);
int config_parse_dhcp_server_dns(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);
int config_parse_dhcp_server_ntp(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);
int config_parse_dnssec_negative_trust_anchors(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);
/* Legacy IPv4LL support */
int config_parse_ipv4ll(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);

View file

@ -101,54 +101,3 @@ int config_parse_address_family_boolean_with_kernel(
return 0;
}
static const char* const resolve_support_table[_RESOLVE_SUPPORT_MAX] = {
[RESOLVE_SUPPORT_NO] = "no",
[RESOLVE_SUPPORT_YES] = "yes",
[RESOLVE_SUPPORT_RESOLVE] = "resolve",
};
DEFINE_STRING_TABLE_LOOKUP(resolve_support, ResolveSupport);
int config_parse_resolve(
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) {
ResolveSupport *resolve = data;
int k;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(resolve);
/* Our enum shall be a superset of booleans, hence first try
* to parse as boolean, and then as enum */
k = parse_boolean(rvalue);
if (k > 0)
*resolve = RESOLVE_SUPPORT_YES;
else if (k == 0)
*resolve = RESOLVE_SUPPORT_NO;
else {
ResolveSupport s;
s = resolve_support_from_string(rvalue);
if (s < 0){
log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse %s= option, ignoring: %s", lvalue, rvalue);
return 0;
}
*resolve = s;
}
return 0;
}

View file

@ -33,20 +33,8 @@ typedef enum AddressFamilyBoolean {
_ADDRESS_FAMILY_BOOLEAN_INVALID = -1,
} AddressFamilyBoolean;
typedef enum ResolveSupport {
RESOLVE_SUPPORT_NO,
RESOLVE_SUPPORT_YES,
RESOLVE_SUPPORT_RESOLVE,
_RESOLVE_SUPPORT_MAX,
_RESOLVE_SUPPORT_INVALID = -1,
} ResolveSupport;
int config_parse_resolve(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);
int config_parse_address_family_boolean(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);
int config_parse_address_family_boolean_with_kernel(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);
const char* resolve_support_to_string(ResolveSupport i) _const_;
ResolveSupport resolve_support_from_string(const char *s) _pure_;
const char *address_family_boolean_to_string(AddressFamilyBoolean b) _const_;
AddressFamilyBoolean address_family_boolean_from_string(const char *s) _const_;

View file

@ -200,75 +200,6 @@ int config_parse_search_domains(
return 0;
}
int config_parse_support(
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) {
Support support, *v = data;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
support = support_from_string(rvalue);
if (support < 0) {
r = parse_boolean(rvalue);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse support level '%s'. Ignoring.", rvalue);
return 0;
}
support = r ? SUPPORT_YES : SUPPORT_NO;
}
*v = support;
return 0;
}
int config_parse_dnssec(
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) {
Manager *m = data;
DnssecMode mode;
int r;
assert(filename);
assert(lvalue);
assert(rvalue);
mode = dnssec_mode_from_string(rvalue);
if (mode < 0) {
r = parse_boolean(rvalue);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse DNSSEC mode '%s'. Ignoring.", rvalue);
return 0;
}
mode = r ? DNSSEC_YES : DNSSEC_NO;
}
m->unicast_scope->dnssec_mode = mode;
return 0;
}
int manager_parse_config_file(Manager *m) {
int r;

View file

@ -35,5 +35,4 @@ const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned len
int config_parse_dns_servers(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);
int config_parse_search_domains(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);
int config_parse_support(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);
int config_parse_dnssec(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);

View file

@ -39,7 +39,10 @@
* - multi-label zone compatibility
* - cname/dname compatibility
* - nxdomain on qname
* - per-interface DNSSEC setting
* - workable hack for the .corp, .home, .box case
* - bus calls to override DNSEC setting per interface
* - log all DNSSEC downgrades
* - enable by default
*
* */
@ -1566,13 +1569,6 @@ int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r
return 0;
}
static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
[DNSSEC_NO] = "no",
[DNSSEC_DOWNGRADE_OK] = "downgrade-ok",
[DNSSEC_YES] = "yes",
};
DEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode);
static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = {
[DNSSEC_VALIDATED] = "validated",
[DNSSEC_INVALID] = "invalid",

View file

@ -28,24 +28,6 @@ typedef enum DnssecResult DnssecResult;
#include "resolved-dns-answer.h"
#include "resolved-dns-rr.h"
enum DnssecMode {
/* No DNSSEC validation is done */
DNSSEC_NO,
/* Validate locally, if the server knows DO, but if not,
* don't. Don't trust the AD bit. If the server doesn't do
* DNSSEC properly, downgrade to non-DNSSEC operation. Of
* course, we then are vulnerable to a downgrade attack, but
* that's life and what is configured. */
DNSSEC_DOWNGRADE_OK,
/* Insist on DNSSEC server support, and rather fail than downgrading. */
DNSSEC_YES,
_DNSSEC_MODE_MAX,
_DNSSEC_MODE_INVALID = -1
};
enum DnssecResult {
/* These four are returned by dnssec_verify_rrset() */
DNSSEC_VALIDATED,
@ -101,8 +83,5 @@ typedef enum DnssecNsecResult {
int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl);
const char* dnssec_mode_to_string(DnssecMode m) _const_;
DnssecMode dnssec_mode_from_string(const char *s) _pure_;
const char* dnssec_result_to_string(DnssecResult m) _const_;
DnssecResult dnssec_result_from_string(const char *s) _pure_;

View file

@ -57,6 +57,23 @@ int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int
s->family = family;
s->resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC;
s->dnssec_mode = _DNSSEC_MODE_INVALID;
if (protocol == DNS_PROTOCOL_DNS) {
/* Copy DNSSEC mode from the link if it is set there,
* otherwise take the manager's DNSSEC mode. Note that
* we copy this only at scope creation time, and do
* not update it from the on, even if the setting
* changes. */
if (l)
s->dnssec_mode = l->dnssec_mode;
if (s->dnssec_mode == _DNSSEC_MODE_INVALID)
s->dnssec_mode = m->dnssec_mode;
if (s->dnssec_mode == _DNSSEC_MODE_INVALID)
s->dnssec_mode = DNSSEC_NO;
}
LIST_PREPEND(scopes, m->dns_scopes, s);
dns_scope_llmnr_membership(s, true);

View file

@ -939,7 +939,7 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
* this means we cannot do any DNSSEC logic
* anymore. */
if (t->scope->dnssec_mode == DNSSEC_DOWNGRADE_OK) {
if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) {
/* We are in downgrade mode. In this
* case, synthesize an unsigned empty
* response, so that the any lookup
@ -1284,13 +1284,13 @@ static int dns_transaction_find_cyclic(DnsTransaction *t, DnsTransaction *aux) {
if (t == aux)
return 1;
SET_FOREACH(n, aux->notify_transactions, i) {
SET_FOREACH(n, aux->dnssec_transactions, i) {
r = dns_transaction_find_cyclic(t, n);
if (r != 0)
return r;
}
return r;
return 0;
}
static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResourceKey *key, DnsTransaction **ret) {
@ -1406,6 +1406,25 @@ static int dns_transaction_has_positive_answer(DnsTransaction *t, DnsAnswerFlags
return false;
}
static int dns_transaction_negative_trust_anchor_lookup(DnsTransaction *t, const char *name) {
int r;
assert(t);
/* Check whether the specified name is in the the NTA
* database, either in the global one, or the link-local
* one. */
r = dns_trust_anchor_lookup_negative(&t->scope->manager->trust_anchor, name);
if (r != 0)
return r;
if (!t->scope->link)
return 0;
return set_contains(t->scope->link->dnssec_negative_trust_anchors, name);
}
static int dns_transaction_has_unsigned_negative_answer(DnsTransaction *t) {
int r;
@ -1422,7 +1441,7 @@ static int dns_transaction_has_unsigned_negative_answer(DnsTransaction *t) {
/* Is this key explicitly listed as a negative trust anchor?
* If so, it's nothing we need to care about */
r = dns_trust_anchor_lookup_negative(&t->scope->manager->trust_anchor, DNS_RESOURCE_KEY_NAME(t->key));
r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(t->key));
if (r < 0)
return r;
if (r > 0)
@ -1513,7 +1532,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) {
continue;
/* If this RR is in the negative trust anchor, we don't need to validate it. */
r = dns_trust_anchor_lookup_negative(&t->scope->manager->trust_anchor, DNS_RESOURCE_KEY_NAME(rr->key));
r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key));
if (r < 0)
return r;
if (r > 0)
@ -1863,7 +1882,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *
if (dns_type_is_pseudo(rr->key->type))
return -EINVAL;
r = dns_trust_anchor_lookup_negative(&t->scope->manager->trust_anchor, DNS_RESOURCE_KEY_NAME(rr->key));
r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key));
if (r < 0)
return r;
if (r > 0)
@ -1989,6 +2008,66 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *
}}
}
static int dns_transaction_in_private_tld(DnsTransaction *t, const DnsResourceKey *key) {
DnsTransaction *dt;
const char *tld;
Iterator i;
int r;
/* If DNSSEC downgrade mode is on, checks whether the
* specified RR is one level below a TLD we have proven not to
* exist. In such a case we assume that this is a private
* domain, and permit it.
*
* This detects cases like the Fritz!Box router networks. Each
* Fritz!Box router serves a private "fritz.box" zone, in the
* non-existing TLD "box". Requests for the "fritz.box" domain
* are served by the router itself, while requests for the
* "box" domain will result in NXDOMAIN.
*
* Note that this logic is unable to detect cases where a
* router serves a private DNS zone directly under
* non-existing TLD. In such a case we cannot detect whether
* the TLD is supposed to exist or not, as all requests we
* make for it will be answered by the router's zone, and not
* by the root zone. */
assert(t);
if (t->scope->dnssec_mode != DNSSEC_ALLOW_DOWNGRADE)
return false; /* In strict DNSSEC mode what doesn't exist, doesn't exist */
tld = DNS_RESOURCE_KEY_NAME(key);
r = dns_name_parent(&tld);
if (r < 0)
return r;
if (r == 0)
return false; /* Already the root domain */
if (!dns_name_is_single_label(tld))
return false;
SET_FOREACH(dt, t->dnssec_transactions, i) {
if (dt->key->class != key->class)
continue;
r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), tld);
if (r < 0)
return r;
if (r == 0)
continue;
/* We found an auxiliary lookup we did for the TLD. If
* that returned with NXDOMAIN, we know the TLD didn't
* exist, and hence this might be a private zone. */
return dt->answer_rcode == DNS_RCODE_NXDOMAIN;
}
return false;
}
static int dns_transaction_requires_nsec(DnsTransaction *t) {
DnsTransaction *dt;
const char *name;
@ -2006,12 +2085,24 @@ static int dns_transaction_requires_nsec(DnsTransaction *t) {
if (dns_type_is_pseudo(t->key->type))
return -EINVAL;
r = dns_trust_anchor_lookup_negative(&t->scope->manager->trust_anchor, DNS_RESOURCE_KEY_NAME(t->key));
r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(t->key));
if (r < 0)
return r;
if (r > 0)
return false;
r = dns_transaction_in_private_tld(t, t->key);
if (r < 0)
return r;
if (r > 0) {
/* The lookup is from a TLD that is proven not to
* exist, and we are in downgrade mode, hence ignore
* that fact that we didn't get any NSEC RRs.*/
log_info("Detected a negative query %s in a private DNS zone, permitting unsigned response.", dns_transaction_key_string(t));
return false;
}
name = DNS_RESOURCE_KEY_NAME(t->key);
if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS, DNS_TYPE_DS)) {
@ -2063,7 +2154,7 @@ static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRe
* the specified RRset is authenticated (i.e. has a matching
* DS RR). */
r = dns_trust_anchor_lookup_negative(&t->scope->manager->trust_anchor, DNS_RESOURCE_KEY_NAME(rr->key));
r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key));
if (r < 0)
return r;
if (r > 0)
@ -2266,7 +2357,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
dns_server_packet_rrsig_missing(t->server);
if (t->scope->dnssec_mode == DNSSEC_DOWNGRADE_OK) {
if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) {
/* Downgrading is OK? If so, just consider the information unsigned */
@ -2283,6 +2374,27 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER;
return 0;
}
r = dns_transaction_in_private_tld(t, rr->key);
if (r < 0)
return r;
if (r > 0) {
_cleanup_free_ char *s = NULL;
/* The data is from a TLD that is proven not to exist, and we are in downgrade
* mode, hence ignore the fact that this was not signed. */
(void) dns_resource_key_to_string(rr->key, &s);
log_info("Detected RRset %s is in a private DNS zone, permitting unsigned RRs.", strna(s ? strstrip(s) : NULL));
r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0);
if (r < 0)
return r;
t->scope->manager->n_dnssec_insecure++;
changed = true;
break;
}
}
if (IN_SET(result,
@ -2311,10 +2423,9 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) {
if (IN_SET(result,
DNSSEC_INVALID,
DNSSEC_SIGNATURE_EXPIRED,
DNSSEC_NO_SIGNATURE,
DNSSEC_UNSUPPORTED_ALGORITHM))
DNSSEC_NO_SIGNATURE))
t->scope->manager->n_dnssec_bogus++;
else
else /* DNSSEC_MISSING_KEY or DNSSEC_UNSUPPORTED_ALGORITHM */
t->scope->manager->n_dnssec_indeterminate++;
r = dns_transaction_is_primary_response(t, rr);

View file

@ -42,7 +42,18 @@ static const uint8_t root_digest[] =
{ 0x49, 0xAA, 0xC1, 0x1D, 0x7B, 0x6F, 0x64, 0x46, 0x70, 0x2E, 0x54, 0xA1, 0x60, 0x73, 0x71, 0x60,
0x7A, 0x1A, 0x41, 0x85, 0x52, 0x00, 0xFD, 0x2C, 0xE1, 0xCD, 0xDE, 0x32, 0xF2, 0x4E, 0x8F, 0xB5 };
static int dns_trust_anchor_add_builtin(DnsTrustAnchor *d) {
static bool dns_trust_anchor_knows_domain_positive(DnsTrustAnchor *d, const char *name) {
assert(d);
/* Returns true if there's an entry for the specified domain
* name in our trust anchor */
return
hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DNSKEY, name)) ||
hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, name));
}
static int dns_trust_anchor_add_builtin_positive(DnsTrustAnchor *d) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
int r;
@ -53,10 +64,11 @@ static int dns_trust_anchor_add_builtin(DnsTrustAnchor *d) {
if (r < 0)
return r;
if (hashmap_get(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, ".")))
return 0;
if (hashmap_get(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DNSKEY, ".")))
/* Only add the built-in trust anchor if there's neither a DS
* nor a DNSKEY defined for the root domain. That way users
* have an easy way to override the root domain DS/DNSKEY
* data. */
if (dns_trust_anchor_knows_domain_positive(d, "."))
return 0;
/* Add the RR from https://data.iana.org/root-anchors/root-anchors.xml */
@ -88,6 +100,95 @@ static int dns_trust_anchor_add_builtin(DnsTrustAnchor *d) {
return 0;
}
static int dns_trust_anchor_add_builtin_negative(DnsTrustAnchor *d) {
static const char private_domains[] =
/* RFC 6761 says that .test is a special domain for
* testing and not to be installed in the root zone */
"test\0"
/* RFC 6761 says that these reverse IP lookup ranges
* are for private addresses, and hence should not
* show up in the root zone */
"10.in-addr.arpa\0"
"16.172.in-addr.arpa\0"
"17.172.in-addr.arpa\0"
"18.172.in-addr.arpa\0"
"19.172.in-addr.arpa\0"
"20.172.in-addr.arpa\0"
"21.172.in-addr.arpa\0"
"22.172.in-addr.arpa\0"
"23.172.in-addr.arpa\0"
"24.172.in-addr.arpa\0"
"25.172.in-addr.arpa\0"
"26.172.in-addr.arpa\0"
"27.172.in-addr.arpa\0"
"28.172.in-addr.arpa\0"
"29.172.in-addr.arpa\0"
"30.172.in-addr.arpa\0"
"31.172.in-addr.arpa\0"
"168.192.in-addr.arpa\0"
/* RFC 6762 reserves the .local domain for Multicast
* DNS, it hence cannot appear in the root zone. (Note
* that we by default do not route .local traffic to
* DNS anyway, except when a configured search domain
* suggests so.) */
"local\0"
/* These two are well known, popular private zone
* TLDs, that are blocked from delegation, according
* to:
* http://icannwiki.com/Name_Collision#NGPC_Resolution
*
* There's also ongoing work on making this official
* in an RRC:
* https://www.ietf.org/archive/id/draft-chapin-additional-reserved-tlds-02.txt */
"home\0"
"corp\0"
/* The following four TLDs are suggested for private
* zones in RFC 6762, Appendix G, and are hence very
* unlikely to be made official TLDs any day soon */
"lan\0"
"intranet\0"
"internal\0"
"private\0";
const char *name;
int r;
assert(d);
/* Only add the built-in trust anchor if there's no negative
* trust anchor defined at all. This enables easy overriding
* of negative trust anchors. */
if (set_size(d->negative_by_name) > 0)
return 0;
r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops);
if (r < 0)
return r;
/* We add a couple of domains as default negative trust
* anchors, where it's very unlikely they will be installed in
* the root zone. If they exist they must be private, and thus
* unsigned. */
NULSTR_FOREACH(name, private_domains) {
if (dns_trust_anchor_knows_domain_positive(d, name))
continue;
r = set_put_strdup(d->negative_by_name, name);
if (r < 0)
return r;
}
return 0;
}
static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) {
_cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL;
_cleanup_free_ char *domain = NULL, *class = NULL, *type = NULL;
@ -236,7 +337,7 @@ static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, u
r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops);
if (r < 0)
return r;
return log_oom();
old_answer = hashmap_get(d->positive_by_key, rr->key);
answer = dns_answer_ref(old_answer);
@ -279,7 +380,7 @@ static int dns_trust_anchor_load_negative(DnsTrustAnchor *d, const char *path, u
r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops);
if (r < 0)
return r;
return log_oom();
r = set_put(d->negative_by_name, domain);
if (r < 0)
@ -340,27 +441,49 @@ static int dns_trust_anchor_load_files(
return 0;
}
static void dns_trust_anchor_dump(DnsTrustAnchor *d) {
static int domain_name_cmp(const void *a, const void *b) {
char **x = (char**) a, **y = (char**) b;
return dns_name_compare_func(*x, *y);
}
static int dns_trust_anchor_dump(DnsTrustAnchor *d) {
DnsAnswer *a;
Iterator i;
assert(d);
log_info("Positive Trust Anchors:");
HASHMAP_FOREACH(a, d->positive_by_key, i) {
DnsResourceRecord *rr;
if (hashmap_isempty(d->positive_by_key))
log_info("No positive trust anchors defined.");
else {
log_info("Positive Trust Anchors:");
HASHMAP_FOREACH(a, d->positive_by_key, i) {
DnsResourceRecord *rr;
DNS_ANSWER_FOREACH(rr, a)
log_info("%s", dns_resource_record_to_string(rr));
DNS_ANSWER_FOREACH(rr, a)
log_info("%s", dns_resource_record_to_string(rr));
}
}
if (!set_isempty(d->negative_by_name)) {
char *n;
log_info("Negative trust anchors:");
if (set_isempty(d->negative_by_name))
log_info("No negative trust anchors defined.");
else {
_cleanup_free_ char **l = NULL, *j = NULL;
SET_FOREACH(n, d->negative_by_name, i)
log_info("%s%s", n, endswith(n, ".") ? "" : ".");
l = set_get_strv(d->negative_by_name);
if (!l)
return log_oom();
qsort_safe(l, set_size(d->negative_by_name), sizeof(char*), domain_name_cmp);
j = strv_join(l, " ");
if (!j)
return log_oom();
log_info("Negative trust anchors: %s", j);
}
return 0;
}
int dns_trust_anchor_load(DnsTrustAnchor *d) {
@ -373,9 +496,13 @@ int dns_trust_anchor_load(DnsTrustAnchor *d) {
(void) dns_trust_anchor_load_files(d, ".negative", dns_trust_anchor_load_negative);
/* However, if the built-in DS fails, then we have a problem. */
r = dns_trust_anchor_add_builtin(d);
r = dns_trust_anchor_add_builtin_positive(d);
if (r < 0)
return log_error_errno(r, "Failed to add trust anchor built-in: %m");
return log_error_errno(r, "Failed to add built-in positive trust anchor: %m");
r = dns_trust_anchor_add_builtin_negative(d);
if (r < 0)
return log_error_errno(r, "Failed to add built-in negative trust anchor: %m");
dns_trust_anchor_dump(d);
@ -516,17 +643,6 @@ static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor *d, DnsResourceReco
return 0;
}
static bool dns_trust_anchor_knows_domain(DnsTrustAnchor *d, const char *name) {
assert(d);
/* Returns true if there's an entry for the specified domain
* name in our trust anchor */
return
hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DNSKEY, name)) ||
hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, name));
}
int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsAnswer *rrs, const DnsResourceKey *key) {
DnsResourceRecord *dnskey;
int r;
@ -556,7 +672,7 @@ int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsAnswer *rrs, const DnsR
/* Could this be interesting to us at all? If not,
* there's no point in looking for and verifying a
* self-signed RRSIG. */
if (!dns_trust_anchor_knows_domain(d, DNS_RESOURCE_KEY_NAME(dnskey->key)))
if (!dns_trust_anchor_knows_domain_positive(d, DNS_RESOURCE_KEY_NAME(dnskey->key)))
continue;
/* Look for a self-signed RRSIG */

View file

@ -14,8 +14,9 @@ struct ConfigPerfItem;
%struct-type
%includes
%%
Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0
Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0
Resolve.Domains, config_parse_search_domains, 0, 0
Resolve.LLMNR, config_parse_support, 0, offsetof(Manager, llmnr_support)
Resolve.DNSSEC, config_parse_dnssec, 0, 0
Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0
Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0
Resolve.Domains, config_parse_search_domains, 0, 0
Resolve.LLMNR, config_parse_resolve_support, 0, offsetof(Manager, llmnr_support)
Resolve.MulticastDNS, config_parse_resolve_support, 0, offsetof(Manager, mdns_support)
Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode)

View file

@ -46,7 +46,9 @@ int link_new(Manager *m, Link **ret, int ifindex) {
return -ENOMEM;
l->ifindex = ifindex;
l->llmnr_support = SUPPORT_YES;
l->llmnr_support = RESOLVE_SUPPORT_YES;
l->mdns_support = RESOLVE_SUPPORT_NO;
l->dnssec_mode = _DNSSEC_MODE_INVALID;
r = hashmap_put(m->links, INT_TO_PTR(ifindex), l);
if (r < 0)
@ -65,7 +67,7 @@ Link *link_free(Link *l) {
if (!l)
return NULL;
dns_server_unlink_marked(l->dns_servers);
dns_server_unlink_all(l->dns_servers);
dns_search_domain_unlink_all(l->search_domains);
while (l->addresses)
@ -80,6 +82,8 @@ Link *link_free(Link *l) {
dns_scope_free(l->mdns_ipv4_scope);
dns_scope_free(l->mdns_ipv6_scope);
set_free_free(l->dnssec_negative_trust_anchors);
free(l);
return NULL;
}
@ -99,8 +103,8 @@ static void link_allocate_scopes(Link *l) {
l->unicast_scope = dns_scope_free(l->unicast_scope);
if (link_relevant(l, AF_INET) &&
l->llmnr_support != SUPPORT_NO &&
l->manager->llmnr_support != SUPPORT_NO) {
l->llmnr_support != RESOLVE_SUPPORT_NO &&
l->manager->llmnr_support != RESOLVE_SUPPORT_NO) {
if (!l->llmnr_ipv4_scope) {
r = dns_scope_new(l->manager, &l->llmnr_ipv4_scope, l, DNS_PROTOCOL_LLMNR, AF_INET);
if (r < 0)
@ -110,8 +114,8 @@ static void link_allocate_scopes(Link *l) {
l->llmnr_ipv4_scope = dns_scope_free(l->llmnr_ipv4_scope);
if (link_relevant(l, AF_INET6) &&
l->llmnr_support != SUPPORT_NO &&
l->manager->llmnr_support != SUPPORT_NO &&
l->llmnr_support != RESOLVE_SUPPORT_NO &&
l->manager->llmnr_support != RESOLVE_SUPPORT_NO &&
socket_ipv6_is_supported()) {
if (!l->llmnr_ipv6_scope) {
r = dns_scope_new(l->manager, &l->llmnr_ipv6_scope, l, DNS_PROTOCOL_LLMNR, AF_INET6);
@ -122,8 +126,8 @@ static void link_allocate_scopes(Link *l) {
l->llmnr_ipv6_scope = dns_scope_free(l->llmnr_ipv6_scope);
if (link_relevant(l, AF_INET) &&
l->mdns_support != SUPPORT_NO &&
l->manager->mdns_support != SUPPORT_NO) {
l->mdns_support != RESOLVE_SUPPORT_NO &&
l->manager->mdns_support != RESOLVE_SUPPORT_NO) {
if (!l->mdns_ipv4_scope) {
r = dns_scope_new(l->manager, &l->mdns_ipv4_scope, l, DNS_PROTOCOL_MDNS, AF_INET);
if (r < 0)
@ -133,8 +137,8 @@ static void link_allocate_scopes(Link *l) {
l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope);
if (link_relevant(l, AF_INET6) &&
l->mdns_support != SUPPORT_NO &&
l->manager->mdns_support != SUPPORT_NO) {
l->mdns_support != RESOLVE_SUPPORT_NO &&
l->manager->mdns_support != RESOLVE_SUPPORT_NO) {
if (!l->mdns_ipv6_scope) {
r = dns_scope_new(l->manager, &l->mdns_ipv6_scope, l, DNS_PROTOCOL_MDNS, AF_INET6);
if (r < 0)
@ -233,22 +237,107 @@ static int link_update_llmnr_support(Link *l) {
if (r < 0)
goto clear;
r = parse_boolean(b);
if (r < 0) {
if (streq(b, "resolve"))
l->llmnr_support = SUPPORT_RESOLVE;
else
goto clear;
} else if (r > 0)
l->llmnr_support = SUPPORT_YES;
else
l->llmnr_support = SUPPORT_NO;
l->llmnr_support = resolve_support_from_string(b);
if (l->llmnr_support < 0) {
r = -EINVAL;
goto clear;
}
return 0;
clear:
l->llmnr_support = SUPPORT_YES;
l->llmnr_support = RESOLVE_SUPPORT_YES;
return r;
}
static int link_update_mdns_support(Link *l) {
_cleanup_free_ char *b = NULL;
int r;
assert(l);
r = sd_network_link_get_mdns(l->ifindex, &b);
if (r == -ENODATA) {
r = 0;
goto clear;
}
if (r < 0)
goto clear;
l->mdns_support = resolve_support_from_string(b);
if (l->mdns_support < 0) {
r = -EINVAL;
goto clear;
}
return 0;
clear:
l->mdns_support = RESOLVE_SUPPORT_NO;
return r;
}
static int link_update_dnssec_mode(Link *l) {
_cleanup_free_ char *m = NULL;
int r;
assert(l);
r = sd_network_link_get_dnssec(l->ifindex, &m);
if (r == -ENODATA) {
r = 0;
goto clear;
}
if (r < 0)
goto clear;
l->dnssec_mode = dnssec_mode_from_string(m);
if (l->dnssec_mode < 0) {
r = -EINVAL;
goto clear;
}
return 0;
clear:
l->dnssec_mode = _DNSSEC_MODE_INVALID;
return r;
}
static int link_update_dnssec_negative_trust_anchors(Link *l) {
_cleanup_strv_free_ char **ntas = NULL;
_cleanup_set_free_free_ Set *ns = NULL;
char **i;
int r;
assert(l);
r = sd_network_link_get_dnssec_negative_trust_anchors(l->ifindex, &ntas);
if (r == -ENODATA) {
r = 0;
goto clear;
}
if (r < 0)
goto clear;
ns = set_new(&dns_name_hash_ops);
if (!ns)
return -ENOMEM;
STRV_FOREACH(i, ntas) {
r = set_put_strdup(ns, *i);
if (r < 0)
return r;
}
set_free_free(l->dnssec_negative_trust_anchors);
l->dnssec_negative_trust_anchors = ns;
ns = NULL;
return 0;
clear:
l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors);
return r;
}
@ -299,14 +388,31 @@ int link_update_monitor(Link *l) {
assert(l);
link_update_dns_servers(l);
link_update_llmnr_support(l);
link_allocate_scopes(l);
r = link_update_dns_servers(l);
if (r < 0)
log_warning_errno(r, "Failed to read DNS servers for interface %s, ignoring: %m", l->name);
r = link_update_llmnr_support(l);
if (r < 0)
log_warning_errno(r, "Failed to read LLMNR support for interface %s, ignoring: %m", l->name);
r = link_update_mdns_support(l);
if (r < 0)
log_warning_errno(r, "Failed to read mDNS support for interface %s, ignoring: %m", l->name);
r = link_update_dnssec_mode(l);
if (r < 0)
log_warning_errno(r, "Failed to read DNSSEC mode for interface %s, ignoring: %m", l->name);
r = link_update_dnssec_negative_trust_anchors(l);
if (r < 0)
log_warning_errno(r, "Failed to read DNSSEC negative trust anchors for interface %s, ignoring: %m", l->name);
r = link_update_search_domains(l);
if (r < 0)
log_warning_errno(r, "Failed to read search domains for interface %s, ignoring: %m", l->name);
link_allocate_scopes(l);
link_add_rrs(l, false);
return 0;
@ -459,8 +565,8 @@ void link_address_add_rrs(LinkAddress *a, bool force_remove) {
if (!force_remove &&
link_address_relevant(a) &&
a->link->llmnr_ipv4_scope &&
a->link->llmnr_support == SUPPORT_YES &&
a->link->manager->llmnr_support == SUPPORT_YES) {
a->link->llmnr_support == RESOLVE_SUPPORT_YES &&
a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) {
if (!a->link->manager->llmnr_host_ipv4_key) {
a->link->manager->llmnr_host_ipv4_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, a->link->manager->llmnr_hostname);
@ -516,8 +622,8 @@ void link_address_add_rrs(LinkAddress *a, bool force_remove) {
if (!force_remove &&
link_address_relevant(a) &&
a->link->llmnr_ipv6_scope &&
a->link->llmnr_support == SUPPORT_YES &&
a->link->manager->llmnr_support == SUPPORT_YES) {
a->link->llmnr_support == RESOLVE_SUPPORT_YES &&
a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) {
if (!a->link->manager->llmnr_host_ipv6_key) {
a->link->manager->llmnr_host_ipv6_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, a->link->manager->llmnr_hostname);

View file

@ -25,6 +25,7 @@
#include "in-addr-util.h"
#include "ratelimit.h"
#include "resolve-util.h"
typedef struct Link Link;
typedef struct LinkAddress LinkAddress;
@ -66,8 +67,10 @@ struct Link {
LIST_HEAD(DnsSearchDomain, search_domains);
unsigned n_search_domains;
Support llmnr_support;
Support mdns_support;
ResolveSupport llmnr_support;
ResolveSupport mdns_support;
DnssecMode dnssec_mode;
Set *dnssec_negative_trust_anchors;
DnsScope *unicast_scope;
DnsScope *llmnr_ipv4_scope;

View file

@ -47,7 +47,7 @@ int manager_llmnr_start(Manager *m) {
assert(m);
if (m->llmnr_support == SUPPORT_NO)
if (m->llmnr_support == RESOLVE_SUPPORT_NO)
return 0;
r = manager_llmnr_ipv4_udp_fd(m);
@ -80,7 +80,7 @@ int manager_llmnr_start(Manager *m) {
eaddrinuse:
log_warning("There appears to be another LLMNR responder running. Turning off LLMNR support.");
m->llmnr_support = SUPPORT_NO;
m->llmnr_support = RESOLVE_SUPPORT_NO;
manager_llmnr_stop(m);
return 0;

View file

@ -476,7 +476,9 @@ int manager_new(Manager **ret) {
m->mdns_ipv4_fd = m->mdns_ipv6_fd = -1;
m->hostname_fd = -1;
m->llmnr_support = SUPPORT_YES;
m->llmnr_support = RESOLVE_SUPPORT_YES;
m->mdns_support = RESOLVE_SUPPORT_NO;
m->dnssec_mode = DNSSEC_NO;
m->read_resolv_conf = true;
m->need_builtin_fallbacks = true;
@ -484,6 +486,10 @@ int manager_new(Manager **ret) {
if (r < 0)
return r;
r = manager_parse_config_file(m);
if (r < 0)
return r;
r = sd_event_default(&m->event);
if (r < 0)
return r;
@ -1163,10 +1169,3 @@ int manager_compile_search_domains(Manager *m, OrderedSet **domains) {
return 0;
}
static const char* const support_table[_SUPPORT_MAX] = {
[SUPPORT_NO] = "no",
[SUPPORT_YES] = "yes",
[SUPPORT_RESOLVE] = "resolve",
};
DEFINE_STRING_TABLE_LOOKUP(support, Support);

View file

@ -28,17 +28,9 @@
#include "hashmap.h"
#include "list.h"
#include "ordered-set.h"
#include "resolve-util.h"
typedef struct Manager Manager;
typedef enum Support Support;
enum Support {
SUPPORT_NO,
SUPPORT_YES,
SUPPORT_RESOLVE,
_SUPPORT_MAX,
_SUPPORT_INVALID = -1
};
#include "resolved-dns-query.h"
#include "resolved-dns-search-domain.h"
@ -53,8 +45,9 @@ enum Support {
struct Manager {
sd_event *event;
Support llmnr_support;
Support mdns_support;
ResolveSupport llmnr_support;
ResolveSupport mdns_support;
DnssecMode dnssec_mode;
/* Network */
Hashmap *links;
@ -165,6 +158,3 @@ int manager_is_own_hostname(Manager *m, const char *name);
int manager_compile_dns_servers(Manager *m, OrderedSet **servers);
int manager_compile_search_domains(Manager *m, OrderedSet **domains);
const char* support_to_string(Support p) _const_;
int support_from_string(const char *s) _pure_;

View file

@ -42,7 +42,7 @@ int manager_mdns_start(Manager *m) {
assert(m);
if (m->mdns_support == SUPPORT_NO)
if (m->mdns_support == RESOLVE_SUPPORT_NO)
return 0;
r = manager_mdns_ipv4_fd(m);
@ -63,7 +63,7 @@ int manager_mdns_start(Manager *m) {
eaddrinuse:
log_warning("There appears to be another mDNS responder running. Turning off mDNS support.");
m->mdns_support = SUPPORT_NO;
m->mdns_support = RESOLVE_SUPPORT_NO;
manager_mdns_stop(m);
return 0;

View file

@ -81,12 +81,6 @@ int main(int argc, char *argv[]) {
goto finish;
}
r = manager_parse_config_file(m);
if (r < 0) {
log_error_errno(r, "Failed to parse configuration file: %m");
goto finish;
}
r = manager_start(m);
if (r < 0) {
log_error_errno(r, "Failed to start manager: %m");

View file

@ -16,4 +16,5 @@
#FallbackDNS=@DNS_SERVERS@
#Domains=
#LLMNR=yes
#MulticastDNS=no
#DNSSEC=no

41
src/shared/resolve-util.c Normal file
View file

@ -0,0 +1,41 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2016 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "conf-parser.h"
#include "resolve-util.h"
#include "string-table.h"
DEFINE_CONFIG_PARSE_ENUM(config_parse_resolve_support, resolve_support, ResolveSupport, "Failed to parse resolve support setting");
DEFINE_CONFIG_PARSE_ENUM(config_parse_dnssec_mode, dnssec_mode, DnssecMode, "Failed to parse DNSSEC mode setting");
static const char* const resolve_support_table[_RESOLVE_SUPPORT_MAX] = {
[RESOLVE_SUPPORT_NO] = "no",
[RESOLVE_SUPPORT_YES] = "yes",
[RESOLVE_SUPPORT_RESOLVE] = "resolve",
};
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(resolve_support, ResolveSupport, RESOLVE_SUPPORT_YES);
static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = {
[DNSSEC_NO] = "no",
[DNSSEC_ALLOW_DOWNGRADE] = "allow-downgrade",
[DNSSEC_YES] = "yes",
};
DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dnssec_mode, DnssecMode, DNSSEC_YES);

62
src/shared/resolve-util.h Normal file
View file

@ -0,0 +1,62 @@
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
#pragma once
/***
This file is part of systemd.
Copyright 2016 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "macro.h"
typedef enum ResolveSupport ResolveSupport;
typedef enum DnssecMode DnssecMode;
enum ResolveSupport {
RESOLVE_SUPPORT_NO,
RESOLVE_SUPPORT_YES,
RESOLVE_SUPPORT_RESOLVE,
_RESOLVE_SUPPORT_MAX,
_RESOLVE_SUPPORT_INVALID = -1
};
enum DnssecMode {
/* No DNSSEC validation is done */
DNSSEC_NO,
/* Validate locally, if the server knows DO, but if not,
* don't. Don't trust the AD bit. If the server doesn't do
* DNSSEC properly, downgrade to non-DNSSEC operation. Of
* course, we then are vulnerable to a downgrade attack, but
* that's life and what is configured. */
DNSSEC_ALLOW_DOWNGRADE,
/* Insist on DNSSEC server support, and rather fail than downgrading. */
DNSSEC_YES,
_DNSSEC_MODE_MAX,
_DNSSEC_MODE_INVALID = -1
};
int config_parse_resolve_support(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);
int config_parse_dnssec_mode(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);
const char* resolve_support_to_string(ResolveSupport p) _const_;
ResolveSupport resolve_support_from_string(const char *s) _pure_;
const char* dnssec_mode_to_string(DnssecMode p) _const_;
DnssecMode dnssec_mode_from_string(const char *s) _pure_;

View file

@ -111,6 +111,27 @@ int sd_network_link_get_ntp(int ifindex, char ***addr);
*/
int sd_network_link_get_llmnr(int ifindex, char **llmnr);
/* Indicates whether or not MulticastDNS should be enabled for the
* link.
* Possible levels of support: yes, no, resolve
* Possible return codes:
* -ENODATA: networkd is not aware of the link
*/
int sd_network_link_get_mdns(int ifindex, char **mdns);
/* Indicates whether or not DNSSEC should be enabled for the link
* Possible levels of support: yes, no, allow-downgrade
* Possible return codes:
* -ENODATA: networkd is not aware of the link
*/
int sd_network_link_get_dnssec(int ifindex, char **dnssec);
/* Returns the list of per-interface DNSSEC negative trust anchors
* Possible return codes:
* -ENODATA: networkd is not aware of the link, or has no such data
*/
int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char ***nta);
int sd_network_link_get_lldp(int ifindex, char **lldp);
/* Get the DNS domain names for a given link. */