diff options
220 files changed, 7341 insertions, 2290 deletions
diff --git a/.dir-locals.el b/.dir-locals.el index 9d9f8cd178..eee106213b 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -3,5 +3,11 @@ ; Mode can be nil, which gives default values. ((nil . ((indent-tabs-mode . nil) - (tab-width . 8))) -) + (tab-width . 8) + (fill-column . 119))) + (c-mode . ((c-basic-offset . 8) + (eval . (c-set-offset 'substatement-open 0)) + (eval . (c-set-offset 'statement-case-open 0)) + (eval . (c-set-offset 'case-label 0)) + (eval . (c-set-offset 'arglist-intro '++)) + (eval . (c-set-offset 'arglist-close 0))))) @@ -7,3 +7,4 @@ set tabstop=8 set shiftwidth=8 set expandtab set makeprg=GCC_COLORS=\ make +set tw=119 diff --git a/Makefile-man.am b/Makefile-man.am index e91ecfdfdf..98769fbee8 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -1990,16 +1990,21 @@ endif if ENABLE_RESOLVED MANPAGES += \ + man/dnssec-trust-anchors.d.5 \ man/nss-resolve.8 \ man/resolved.conf.5 \ man/systemd-resolved.service.8 MANPAGES_ALIAS += \ man/libnss_resolve.so.2.8 \ man/resolved.conf.d.5 \ - man/systemd-resolved.8 + man/systemd-resolved.8 \ + man/systemd.negative.5 \ + man/systemd.positive.5 man/libnss_resolve.so.2.8: man/nss-resolve.8 man/resolved.conf.d.5: man/resolved.conf.5 man/systemd-resolved.8: man/systemd-resolved.service.8 +man/systemd.negative.5: man/dnssec-trust-anchors.d.5 +man/systemd.positive.5: man/dnssec-trust-anchors.d.5 man/libnss_resolve.so.2.html: man/nss-resolve.html $(html-alias) @@ -2009,6 +2014,12 @@ man/resolved.conf.d.html: man/resolved.conf.html man/systemd-resolved.html: man/systemd-resolved.service.html $(html-alias) +man/systemd.negative.html: man/dnssec-trust-anchors.d.html + $(html-alias) + +man/systemd.positive.html: man/dnssec-trust-anchors.d.html + $(html-alias) + endif if ENABLE_RFKILL @@ -2434,6 +2445,7 @@ EXTRA_DIST += \ man/coredumpctl.xml \ man/crypttab.xml \ man/daemon.xml \ + man/dnssec-trust-anchors.d.xml \ man/file-hierarchy.xml \ man/halt.xml \ man/hostname.xml \ diff --git a/Makefile.am b/Makefile.am index 3516b50385..3eba35f917 100644 --- a/Makefile.am +++ b/Makefile.am @@ -694,29 +694,27 @@ man_MANS = \ noinst_DATA += \ $(HTML_FILES) \ - $(HTML_ALIAS) + $(HTML_ALIAS) \ + docs/html/man +endif CLEANFILES += \ $(man_MANS) \ $(HTML_FILES) \ - $(HTML_ALIAS) + $(HTML_ALIAS) \ + docs/html/man docs/html/man: $(AM_V_at)$(MKDIR_P) $(dir $@) $(AM_V_LN)$(LN_S) -f ../../man $@ -noinst_DATA += \ - docs/html/man - -CLEANFILES += \ - docs/html/man - -if HAVE_PYTHON man/index.html: man/systemd.index.html $(AM_V_LN)$(LN_S) -f systemd.index.html $@ +if HAVE_PYTHON noinst_DATA += \ man/index.html +endif CLEANFILES += \ man/index.html @@ -745,11 +743,6 @@ CLEANFILES += \ man/systemd.index.xml \ man/systemd.directives.xml - -endif - -endif - EXTRA_DIST += \ $(filter-out man/systemd.directives.xml man/systemd.index.xml,$(XML_FILES)) \ tools/make-man-index.py \ @@ -841,6 +834,8 @@ libbasic_la_SOURCES = \ src/basic/mempool.h \ src/basic/hashmap.c \ src/basic/hashmap.h \ + src/basic/hash-funcs.c \ + src/basic/hash-funcs.h \ src/basic/siphash24.c \ src/basic/siphash24.h \ src/basic/set.h \ @@ -1054,7 +1049,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 += \ @@ -5174,6 +5171,8 @@ systemd_resolved_SOURCES = \ src/resolve/resolved-bus.h \ src/resolve/resolved-link.h \ src/resolve/resolved-link.c \ + src/resolve/resolved-link-bus.c \ + src/resolve/resolved-link-bus.h \ src/resolve/resolved-llmnr.h \ src/resolve/resolved-llmnr.c \ src/resolve/resolved-mdns.h \ @@ -5293,6 +5292,9 @@ tests += \ test-dns-domain \ test-dnssec +manual_tests += \ + test-dnssec-complex + test_dnssec_SOURCES = \ src/resolve/test-dnssec.c \ src/resolve/resolved-dns-packet.c \ @@ -5311,6 +5313,14 @@ test_dnssec_SOURCES = \ test_dnssec_LDADD = \ libshared.la +test_dnssec_complex_SOURCES = \ + src/resolve/test-dnssec-complex.c \ + src/resolve/dns-type.c \ + src/resolve/dns-type.h + +test_dnssec_complex_LDADD = \ + libshared.la + endif endif @@ -5978,24 +5988,24 @@ src/%.c: src/%.gperf $(AM_V_at)$(MKDIR_P) $(dir $@) $(AM_V_GPERF)$(GPERF) < $< > $@ -src/%: src/%.m4 +src/%: src/%.m4 $(top_builddir)/config.status $(AM_V_at)$(MKDIR_P) $(dir $@) $(AM_V_M4)$(M4) -P $(M4_DEFINES) < $< > $@ -sysusers.d/%: sysusers.d/%.m4 +sysusers.d/%: sysusers.d/%.m4 $(top_builddir)/config.status $(AM_V_at)$(MKDIR_P) $(dir $@) $(AM_V_M4)$(M4) -P $(M4_DEFINES) < $< > $@ -tmpfiles.d/%: tmpfiles.d/%.m4 +tmpfiles.d/%: tmpfiles.d/%.m4 $(top_builddir)/config.status $(AM_V_at)$(MKDIR_P) $(dir $@) $(AM_V_M4)$(M4) -P $(M4_DEFINES) < $< > $@ -units/%: units/%.m4 +units/%: units/%.m4 $(top_builddir)/config.status $(AM_V_at)$(MKDIR_P) $(dir $@) $(AM_V_M4)$(M4) -P $(M4_DEFINES) -DFOR_SYSTEM=1 < $< > $@ -units/user/%: units/user/%.m4 +units/user/%: units/user/%.m4 $(top_builddir)/config.status $(AM_V_at)$(MKDIR_P) $(dir $@) $(AM_V_M4)$(M4) -P $(M4_DEFINES) -DFOR_USER=1 < $< > $@ @@ -6010,7 +6020,6 @@ EXTRA_DIST += \ $(polkitpolicy_in_in_files) # ------------------------------------------------------------------------------ -if ENABLE_MANPAGES man/custom-entities.ent: configure.ac $(AM_V_GEN)$(MKDIR_P) $(dir $@) $(AM_V_GEN)(echo '<?xml version="1.0" encoding="utf-8" ?>' && \ @@ -6058,8 +6067,6 @@ define html-alias $(AM_V_LN)$(LN_S) -f $(notdir $<) $@ endef -endif - EXTRA_DIST += \ man/custom-html.xsl \ man/custom-man.xsl @@ -6285,10 +6292,10 @@ install-tree: all tree $(abs_srcdir)/install-tree # Let's run all tests of the test suite, but under valgrind. Let's -# exclude the one perl script we have in there +# exclude perl/python/shell scripts we have in there .PHONY: valgrind-tests valgrind-tests: $(TESTS) - $(AM_V_GEN)for f in $(filter-out %.pl, $^); do \ + $(AM_V_GEN)for f in $(filter-out %.pl %.py, $^); do \ if file $$f | grep -q shell; then \ echo -e "$${x}Skipping non-binary $$f"; else \ echo -e "$${x}Running $$f"; \ @@ -237,10 +237,6 @@ SYSV INIT.D SCRIPTS: needs to look like, and provide an implementation at the marked places. WARNINGS: - systemd will freeze execution during boot if /etc/mtab exists - but is not a symlink to /proc/mounts. Please ensure that - /etc/mtab is a proper symlink. - systemd will warn you during boot if /usr is on a different file system than /. While in systemd itself very little will break if /usr is on a separate partition, many of its @@ -33,6 +33,21 @@ Janitorial Clean-ups: Features: +* cache sd_event_now() result from before the first iteration... + +* remove Capabilities=, after all AmbientCapabilities= and CapabilityBoundingSet= should be enough. + +* support for the new copy_file_range() syscall + +* add systemctl stop --job-mode=triggering that follows TRIGGERED_BY deps and adds them to the same transaction + +* coredump logic should use prlimit() to query RLIMIT_CORE of the dumpee and honour it + +* Add a MaxRuntimeSec= setting for service units (or units in general) to terminate units after they ran for a certain + amount of time + +* Maybe add a way how users can "pin" units into memory, so that they are not subject to automatic GC? + * PID1: find a way how we can reload unit file configuration for specific units only, without reloading the whole of systemd diff --git a/coccinelle/xsprintf.cocci b/coccinelle/xsprintf.cocci new file mode 100644 index 0000000000..401216ad72 --- /dev/null +++ b/coccinelle/xsprintf.cocci @@ -0,0 +1,6 @@ +@@ +expression e, fmt; +expression list vaargs; +@@ +- snprintf(e, sizeof(e), fmt, vaargs); ++ xsprintf(e, fmt, vaargs); diff --git a/configure.ac b/configure.ac index 3128ca8672..228d5ee1da 100644 --- a/configure.ac +++ b/configure.ac @@ -1301,9 +1301,9 @@ AM_CONDITIONAL(ENABLE_HWDB, [test x$enable_hwdb = xyes]) # ------------------------------------------------------------------------------ have_manpages=no AC_ARG_ENABLE(manpages, AS_HELP_STRING([--disable-manpages], [disable manpages])) +AC_PATH_PROG([XSLTPROC], [xsltproc]) AS_IF([test "x$enable_manpages" != xno], [ have_manpages=yes - AC_PATH_PROG([XSLTPROC], [xsltproc]) AS_IF([test -z "$XSLTPROC"], AC_MSG_ERROR([*** xsltproc is required for man pages])) ]) diff --git a/hwdb/60-evdev.hwdb b/hwdb/60-evdev.hwdb index f7a82ee26c..577800e075 100644 --- a/hwdb/60-evdev.hwdb +++ b/hwdb/60-evdev.hwdb @@ -25,8 +25,7 @@ # https://github.com/systemd/systemd # or create a bug report on https://github.com/systemd/systemd/issues and # include your new rules, a description of the device, and the output of -# udevadm info /dev/input/eventXX -# (or /dev/input/event*). +# udevadm info /dev/input/eventXX. # # Allowed properties are: # EVDEV_ABS_<axis>=<min>:<max>:<res>:<fuzz>:<flat> @@ -115,6 +114,13 @@ evdev:name:AlpsPS/2 ALPS DualPoint TouchPad:dmi:bvn*:bvr*:bd*:svnDellInc.:pnInsp EVDEV_ABS_35=25:2000:22 EVDEV_ABS_36=0:1351:28 +# Dell Latitude E6220 +evdev:name:AlpsPS/2 ALPS DualPoint TouchPad:dmi:bvn*:bvr*:bd*:svnDellInc.:pnLatitudeE6220* + EVDEV_ABS_00=76:1815:22 + EVDEV_ABS_01=131:1330:30 + EVDEV_ABS_35=76:1815:22 + EVDEV_ABS_36=131:1330:30 + ######################################### # Google ######################################### diff --git a/hwdb/60-keyboard.hwdb b/hwdb/60-keyboard.hwdb index 94906abcbf..01213b6069 100644 --- a/hwdb/60-keyboard.hwdb +++ b/hwdb/60-keyboard.hwdb @@ -56,8 +56,7 @@ # https://github.com/systemd/systemd # or create a bug report on https://github.com/systemd/systemd/issues and # include your new rules, a description of the device, and the output of -# udevadm info /dev/input/eventXX -# (or /dev/input/event*). +# udevadm info /dev/input/eventXX. ########################################## # Acer @@ -499,6 +498,13 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnHewlett-Packard*:pnHPProBook450G0:pvr* evdev:atkbd:dmi:bvn*:bvr*:bd*:svnHewlett-Packard:pnHPProBook6555b:* KEYBOARD_KEY_b2=www # Earth +# HP ProBook 440 G3 +evdev:atkbd:dmi:bvn*:bvr*:svnHP*:pnHP*ProBook*440*G3* + KEYBOARD_KEY_92=brightnessdown + KEYBOARD_KEY_97=brightnessup + KEYBOARD_KEY_ee=switchvideomode + KEYBOARD_KEY_81=f20 # micmute + ########################################################### # IBM ########################################################### @@ -652,6 +658,11 @@ evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*IdeaPad*Z370*:pvr* evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*Lenovo*V480*:pvr* KEYBOARD_KEY_f1=f21 +# Lenovo Thinkcentre M800z AIO machine +# key_scancode 00 is KEY_MICMUTE +keyboard:name:Microphone Mute Button:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn* + KEYBOARD_KEY_00=f20 + # enhanced USB keyboard evdev:input:b0003v04B3p301B* KEYBOARD_KEY_90001=prog1 # ThinkVantage diff --git a/hwdb/70-mouse.hwdb b/hwdb/70-mouse.hwdb index eda1adcfca..54ace7cbc1 100644 --- a/hwdb/70-mouse.hwdb +++ b/hwdb/70-mouse.hwdb @@ -41,8 +41,7 @@ # https://github.com/systemd/systemd # or create a bug report on https://github.com/systemd/systemd/issues and # include your new rules, a description of the device, and the output of -# udevadm info /dev/input/eventXX -# (or /dev/input/event*). +# udevadm info /dev/input/eventXX. # # Allowed properties are: # MOUSE_DPI diff --git a/hwdb/70-pointingstick.hwdb b/hwdb/70-pointingstick.hwdb index 9d288e38fd..6820331784 100644 --- a/hwdb/70-pointingstick.hwdb +++ b/hwdb/70-pointingstick.hwdb @@ -37,8 +37,7 @@ # https://github.com/systemd/systemd # or create a bug report on https://github.com/systemd/systemd/issues and # include your new rules, a description of the device, and the output of -# udevadm info /dev/input/eventXX -# (or /dev/input/event*). +# udevadm info /dev/input/eventXX. # # Allowed properties are: # POINTINGSTICK_CONST_ACCEL diff --git a/man/dnssec-trust-anchors.d.xml b/man/dnssec-trust-anchors.d.xml new file mode 100644 index 0000000000..4bdc167f79 --- /dev/null +++ b/man/dnssec-trust-anchors.d.xml @@ -0,0 +1,200 @@ +<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<!-- + 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/>. +--> + +<refentry id="dnssec-trust-anchors.d" conditional='ENABLE_RESOLVED' + xmlns:xi="http://www.w3.org/2001/XInclude"> + <refentryinfo> + <title>dnssec-trust-anchors.d</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Developer</contrib> + <firstname>Lennart</firstname> + <surname>Poettering</surname> + <email>lennart@poettering.net</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>dnssec-trust-anchors.d</refentrytitle> + <manvolnum>5</manvolnum> + </refmeta> + + <refnamediv> + <refname>dnssec-trust-anchors.d</refname> + <refname>systemd.positive</refname> + <refname>systemd.negative</refname> + <refpurpose>DNSSEC trust anchor configuration files</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <para><filename>/etc/dnssec-trust-anchors.d/*.positive</filename></para> + <para><filename>/run/dnssec-trust-anchors.d/*.positive</filename></para> + <para><filename>/usr/lib/dnssec-trust-anchors.d/*.positive</filename></para> + <para><filename>/etc/dnssec-trust-anchors.d/*.negative</filename></para> + <para><filename>/run/dnssec-trust-anchors.d/*.negative</filename></para> + <para><filename>/usr/lib/dnssec-trust-anchors.d/*.negative</filename></para> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The DNSSEC trust anchor configuration files define positive + and negative trust anchors + <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> + bases DNSSEC integrity proofs on.</para> + </refsect1> + + <refsect1> + <title>Positive Trust Anchors</title> + + <para>Positive trust anchor configuration files contain DNSKEY and + DS resource record definitions to use as base for DNSSEC integrity + proofs. See <ulink + url="https://tools.ietf.org/html/rfc4035#section-4.4">RFC 4035, + Section 4.4</ulink> for more information about DNSSEC trust + anchors.</para> + + <para>Positive trust anchors are read from files with the suffix + <filename>.positive</filename> located in + <filename>/etc/dnssec-trust-anchors.d/</filename>, + <filename>/run/dnssec-trust-anchors.d/</filename> and + <filename>/usr/lib/dnssec-trust-anchors.d/</filename>. These + directories are searched in the specified order, and a trust + anchor file of the same name in an earlier path overrides a trust + anchor files in a later path. To disable a trust anchor file + shipped in <filename>/usr/lib/dnssec-trust-anchors.d/</filename> + it is sufficient to provide an identically-named file in + <filename>/etc/dnssec-trust-anchors.d/</filename> or + <filename>/run/dnssec-trust-anchors.d/</filename> that is either + empty or a symlink to <filename>/dev/null</filename> ("masked").</para> + + <para>Positive trust anchor files are simple text files resembling + DNS zone files, as documented in <ulink + url="https://tools.ietf.org/html/rfc1035#section-5">RFC 1035, Section + 5</ulink>. One DS or DNSKEY resource record may be listed per + line. Empty lines and lines starting with a semicolon + (<literal>;</literal>) are ignored and considered comments. A DS + resource record is specified like in the following example:</para> + + <programlisting>. IN DS 19036 8 2 49aac11d7b6f6446702e54a1607371607a1a41855200fd2ce1cdde32f24e8fb5</programlisting> + + <para>The first word specifies the domain, use + <literal>.</literal> for the root domain. The domain may be + specified with or without trailing dot, which is considered + equivalent. The second word must be <literal>IN</literal> the + third word <literal>DS</literal>. The following words specify the + key tag, signature algorithm, digest algorithm, followed by the + hex-encoded key fingerprint. See <ulink + url="https://tools.ietf.org/html/rfc4034#section-5">RFC 4034, + Section 5</ulink> for details about the precise syntax and meaning + of these fields.</para> + + <para>Alternatively, DNSKEY resource records may be used to define + trust anchors, like in the following example:</para> + + <programlisting>. IN DNSKEY 257 3 8 AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjFFVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoXbfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaDX6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpzW5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relSQageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulqQxA+Uk1ihz0=</programlisting> + + <para>The first word specifies the domain again, the second word + must be <literal>IN</literal>, followed by + <literal>DNSKEY</literal>. The subsequent words encode the DNSKEY + flags, protocol and algorithm fields, followed by the key data + encoded in Base64. See <ulink + url="https://tools.ietf.org/html/rfc4034#section-2">RFC 4034, + Section 2</ulink> for details about the precise syntax and meaning + of these fields.</para> + + <para>If multiple DS or DNSKEY records are defined for the same + domain (possibly even in different trust anchor files), all keys + are used and are considered equivalent as base for DNSSEC + proofs.</para> + + <para>Note that <filename>systemd-resolved</filename> will + automatically use a built-in trust anchor key for the Internet + root domain if no positive trust anchors are defined for the root + domain. In most cases it is hence unnecessary to define an + explicit key with trust anchor files. The built-in key is disabled + as soon as at least one trust anchor key for the root domain is + defined in trust anchor files.</para> + + <para>It is generally recommended to encode trust anchors in DS + resource records, rather than DNSKEY resource records.</para> + + <para>If a trust anchor specified via a DS record is found revoked + it is automatically removed from the trust anchor database for the + runtime. See <ulink url="https://tools.ietf.org/html/rfc5011">RFC + 5011</ulink> for details about revoked trust anchors. Note that + <filename>systemd-resolved</filename> will not update its trust + anchor database from DNS servers automatically. Instead, it is + recommended to update the resolver software or update the new + trust anchor via adding in new trust anchor files.</para> + + <para>The current DNSSEC trust anchor for the Internet's root + domain is available at the <ulink + url="https://data.iana.org/root-anchors/root-anchors.xml">IANA + Trust Anchor and Keys</ulink> page.</para> + </refsect1> + + <refsect1> + <title>Negative Trust Anchors</title> + + <para>Negative trust anchors define domains where DNSSEC + validation shall be turned off. Negative trust anchor files are + found at the same location as positive trust anchor files, and + follow the same overriding rules. They are text files with the + <filename>.negative</filename> suffix. Empty lines and lines whose + first character is <literal>;</literal> are ignored. Each line + specifies one domain name where DNSSEC validation shall be + disabled on.</para> + + <para>Negative trust anchors are useful to support private DNS + subtrees that are not referenced from the Internet DNS hierarchy, + and not signed.</para> + + <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> + <title>See Also</title> + <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>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry> + </para> + </refsect1> + +</refentry> diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 309220632e..42d5e006bb 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -91,6 +91,7 @@ <term><varname>systemd.default_standard_output=</varname></term> <term><varname>systemd.default_standard_error=</varname></term> <term><varname>systemd.setenv=</varname></term> + <term><varname>systemd.machine_id=</varname></term> <listitem> <para>Parameters understood by the system and service manager to control system behavior. For details, see diff --git a/man/machine-id.xml b/man/machine-id.xml index db72c2a01c..d318ec54ec 100644 --- a/man/machine-id.xml +++ b/man/machine-id.xml @@ -84,6 +84,12 @@ at install time. Use <citerefentry><refentrytitle>systemd-firstboot</refentrytitle><manvolnum>1</manvolnum></citerefentry> to initialize it on mounted (but not booted) system images.</para> + + <para>The machine-id may also be set, for example when network + booting, by setting the <varname>systemd.machine_id=</varname> + kernel command line parameter or passing the option + <option>--machine-id=</option> to systemd. A machine-id may not + be set to all zeros.</para> </refsect1> <refsect1> diff --git a/man/resolved.conf.xml b/man/resolved.conf.xml index 4680b6a4e5..3ab7fc4a11 100644 --- a/man/resolved.conf.xml +++ b/man/resolved.conf.xml @@ -124,6 +124,82 @@ global setting is on.</para></listitem> </varlistentry> + <varlistentry> + <term><varname>DNSSEC=</varname></term> + <listitem><para>Takes a boolean argument or + <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 + time penalty.</para> + + <para>DNSSEC requires knowledge of "trust anchors" to prove + data integrity. The trust anchor for the Internet root domain + is built into the resolver, additional trust anchors may be + defined with + <citerefentry><refentrytitle>dnssec-trust-anchors.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>. + Trust anchors may change in regular intervals, and old trust + anchors may be revoked. In such a case DNSSEC validation is + not possible until new trust anchors are configured locally or + the resolver software package is updated with the new root + trust anchor. In effect, when the built-in trust anchor is + revoked and <varname>DNSSEC=</varname> is true, all further + 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>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 + returned data could not be verified (either because the data + was found unsigned in the DNS, or the DNS server did not + support DNSSEC or no appropriate trust anchors were known). In + the latter case it is assumed that client programs employ a + secondary scheme to validate the returned DNS data, should + this be required.</para> + + <para>It is recommended to set <varname>DNSSEC=</varname> to + 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>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> + </variablelist> </refsect1> @@ -133,6 +209,7 @@ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>dnssec-trust-anchors.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>, <citerefentry><refentrytitle>resolv.conf</refentrytitle><manvolnum>4</manvolnum></citerefentry> </para> </refsect1> diff --git a/man/sd_event_add_io.xml b/man/sd_event_add_io.xml index eeb406ba5b..c3749164cd 100644 --- a/man/sd_event_add_io.xml +++ b/man/sd_event_add_io.xml @@ -120,36 +120,35 @@ returned in the <parameter>source</parameter> parameter. The <parameter>fd</parameter> parameter takes the UNIX file descriptor to watch, which may refer to a socket, a FIFO, a message queue, a - serial connection, a character device or any other file descriptor - compatible with Linux <citerefentry - project='man-pages'><refentrytitle>epoll</refentrytitle><manvolnum>7</manvolnum></citerefentry>. The - <parameter>events</parameter> parameter takes a bit mask of I/O - events to watch the file descriptor for, a combination of the - following event flags: <constant>EPOLLIN</constant>, - <constant>EPOLLOUT</constant>, <constant>EPOLLRDHUP</constant>, - <constant>EPOLLPRI</constant> and <constant>EPOLLET</constant>, - see <citerefentry - project='man-pages'><refentrytitle>epoll_ctl</refentrytitle><manvolnum>2</manvolnum></citerefentry> + serial connection, a character device, or any other file descriptor + compatible with Linux + <citerefentry project='man-pages'><refentrytitle>epoll</refentrytitle><manvolnum>7</manvolnum></citerefentry>. The + <parameter>events</parameter> parameter takes a bit mask of events + to watch for, a combination of the following event flags: + <constant>EPOLLIN</constant>, <constant>EPOLLOUT</constant>, + <constant>EPOLLRDHUP</constant>, <constant>EPOLLPRI</constant>, + and <constant>EPOLLET</constant>, see + <citerefentry project='man-pages'><refentrytitle>epoll_ctl</refentrytitle><manvolnum>2</manvolnum></citerefentry> for details. The <parameter>handler</parameter> shall reference a - function to call when the I/O event source is triggered. The - handler function will be passed the - <parameter>userdata</parameter> pointer, which may be chosen - freely by the caller. The handler will also be passed the file - descriptor the event was seen on as well as the actual event flags - seen. It's generally a subset of the events watched, however may - additionally have <constant>EPOLLERR</constant> and - <constant>EPOLLHUP</constant> set.</para> - - <para>By default, the I/O event source will stay enabled + function to call when the event source is triggered. The + <parameter>userdata</parameter> pointer will be passed to the + handler function, and may be chosen freely by the caller. The + handler will also be passed the file descriptor the event was seen + on, as well as the actual event flags. It's generally a subset of + the events watched, however may additionally include + <constant>EPOLLERR</constant> and <constant>EPOLLHUP</constant>. + </para> + + <para>By default, an event source will stay enabled continuously (<constant>SD_EVENT_ON</constant>), but this may be changed with <citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>. If the handler function returns a negative error code, it will be disabled after the invocation, even if the <constant>SD_EVENT_ON</constant> mode was requested before. Note - that an I/O event source set to <constant>SD_EVENT_ON</constant> will - fire continuously unless data is read or written to the file - descriptor in order to reset the mask of events seen. + that an event source set to <constant>SD_EVENT_ON</constant> will + fire continuously unless data is read from or written to the file + descriptor to reset the mask of events seen. </para> <para>Setting the I/O event mask to watch for to 0 does not mean @@ -164,16 +163,17 @@ <citerefentry><refentrytitle>sd_event_source_unref</refentrytitle><manvolnum>3</manvolnum></citerefentry>, but note that the event source is only removed from the event loop when all references to the event source are dropped. To make sure - an event source does not fire anymore, even when there's still a - reference to it kept, consider setting the event source to - <constant>SD_EVENT_OFF</constant> with - <citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para> + an event source does not fire anymore, even if it is still referenced, + disable the event source using + <citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry> + with <constant>SD_EVENT_OFF</constant>.</para> <para>If the second parameter of - <function>sd_event_add_io()</function> is passed as NULL no - reference to the event source object is returned. In this case the - event source is considered "floating", and will be destroyed - implicitly when the event loop itself is destroyed.</para> + <function>sd_event_add_io()</function> is + <constant>NULL</constant> no reference to the event source object + is returned. In this case the event source is considered + "floating", and will be destroyed implicitly when the event loop + itself is destroyed.</para> <para>It is recommended to use <function>sd_event_add_io()</function> only in conjunction with @@ -181,24 +181,24 @@ ensure that all I/O operations from invoked handlers are properly asynchronous and non-blocking. Using file descriptors without <constant>O_NONBLOCK</constant> might result in unexpected - starving of other event sources. See <citerefentry - project='man-pages'><refentrytitle>fcntl</refentrytitle><manvolnum>2</manvolnum></citerefentry> + starvation of other event sources. See + <citerefentry project='man-pages'><refentrytitle>fcntl</refentrytitle><manvolnum>2</manvolnum></citerefentry> for details on enabling <constant>O_NONBLOCK</constant> mode.</para> <para><function>sd_event_source_get_io_events()</function> retrieves - the configured I/O event mask to watch of an I/O event source created + the configured mask of watched I/O events of an event source created previously with <function>sd_event_add_io()</function>. It takes the event source object and a pointer to a variable to store the - event mask in.</para> + mask in.</para> - <para><function>sd_event_source_set_io_events()</function> changes the - configured I/O event mask to watch of an I/O event source created previously - with <function>sd_event_add_io()</function>. It takes the event - source object and the new event mask to set.</para> + <para><function>sd_event_source_set_io_events()</function> + configures the mask of watched I/O events of an event source created + previously with <function>sd_event_add_io()</function>. It takes the + event source object and the new event mask.</para> <para><function>sd_event_source_get_io_revents()</function> retrieves the I/O event mask of currently seen but undispatched - events from an I/O event source created previously with + events from an event source created previously with <function>sd_event_add_io()</function>. It takes the event source object and a pointer to a variable to store the event mask in. When called from a handler function on the handler's event @@ -214,15 +214,15 @@ source types, the latter only to I/O event sources.</para> <para><function>sd_event_source_get_io_fd()</function> retrieves - the UNIX file descriptor of an I/O event source created previously + the UNIX file descriptor of an event source created previously with <function>sd_event_add_io()</function>. It takes the event - source object and returns the positive file descriptor in the return - value, or a negative error number on error (see below).</para> + source object and returns the non-negative file descriptor + or a negative error number on error (see below).</para> <para><function>sd_event_source_set_io_fd()</function> changes the UNIX file descriptor of an I/O event source created previously with <function>sd_event_add_io()</function>. It takes - the event source object and the new file descriptor to set.</para> + the event source object and the new file descriptor.</para> </refsect1> <refsect1> @@ -230,13 +230,13 @@ <para>On success, these functions return 0 or a positive integer. On failure, they return a negative errno-style error - code. </para> + code.</para> </refsect1> <refsect1> <title>Errors</title> - <para>Returned errors may indicate the following problems:</para> + <para>Returned values may indicate the following problems:</para> <variablelist> <varlistentry> diff --git a/man/sd_event_add_signal.xml b/man/sd_event_add_signal.xml index a2aabd3c1a..e98f1d2682 100644 --- a/man/sd_event_add_signal.xml +++ b/man/sd_event_add_signal.xml @@ -123,24 +123,23 @@ <citerefentry><refentrytitle>sd_event_source_unref</refentrytitle><manvolnum>3</manvolnum></citerefentry>, but note that the event source is only removed from the event loop when all references to the event source are dropped. To make sure - an event source does not fire anymore, even when there's still a - reference to it kept, consider setting the event source to - <constant>SD_EVENT_OFF</constant> with - <citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para> + an event source does not fire anymore, even if it is still referenced, + disable the event source using + <citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry> + with <constant>SD_EVENT_OFF</constant>.</para> <para>If the second parameter of - <function>sd_event_add_signal()</function> is passed as NULL no - reference to the event source object is returned. In this case the - event source is considered "floating", and will be destroyed - implicitly when the event loop itself is destroyed.</para> - - <para><function>sd_event_source_get_signal()</function> retrieves - the configured UNIX process signal number of a signal event source - created previously with - <function>sd_event_add_signal()</function>. It takes the event - source object as the <parameter>source</parameter> + <function>sd_event_add_signal()</function> is + <constant>NULL</constant> no reference to the event source object + is returned. In this case the event source is considered + "floating", and will be destroyed implicitly when the event loop + itself is destroyed.</para> + + <para><function>sd_event_source_get_signal()</function> returns + the configured signal number of an event source created previously + with <function>sd_event_add_signal()</function>. It takes the + event source object as the <parameter>source</parameter> parameter.</para> - </refsect1> <refsect1> @@ -148,7 +147,7 @@ <para>On success, these functions return 0 or a positive integer. On failure, they return a negative errno-style error - code. </para> + code.</para> </refsect1> <refsect1> @@ -167,7 +166,6 @@ <term><constant>-EINVAL</constant></term> <listitem><para>An invalid argument has been passed.</para></listitem> - </varlistentry> <varlistentry> @@ -175,21 +173,18 @@ <listitem><para>A handler is already installed for this signal or the signal was not blocked previously.</para></listitem> - </varlistentry> <varlistentry> <term><constant>-ESTALE</constant></term> <listitem><para>The event loop is already terminated.</para></listitem> - </varlistentry> <varlistentry> <term><constant>-ECHILD</constant></term> <listitem><para>The event loop has been created in a different process.</para></listitem> - </varlistentry> <varlistentry> diff --git a/man/sd_event_add_time.xml b/man/sd_event_add_time.xml index b58d740bd8..142fa80f8f 100644 --- a/man/sd_event_add_time.xml +++ b/man/sd_event_add_time.xml @@ -122,31 +122,31 @@ clock identifier, one of <constant>CLOCK_REALTIME</constant>, <constant>CLOCK_MONOTONIC</constant>, <constant>CLOCK_BOOTTIME</constant>, - <constant>CLOCK_REALTIME_ALARM</constant> or + <constant>CLOCK_REALTIME_ALARM</constant>, or <constant>CLOCK_BOOTTIME_ALARM</constant>. See <citerefentry><refentrytitle>timerfd_create</refentrytitle><manvolnum>2</manvolnum></citerefentry> for details regarding the various types of clocks. The - <parameter>usec</parameter> parameter takes a time value in - microseconds (µs), relative to the clock's epoch, specifying when - the timer shall elapse the earliest. If a time that already lies - in the past is specified (including 0), the timer source is - dispatched immediately in the next event loop iterations. The - <parameter>accuracy</parameter> parameter takes an additional - accuracy value in µs specifying a time the timer event may be - delayed. Specify 0 for selecting the default accuracy - (250ms). Specify 1µs for most accurate timers. Consider specifying - 60000000µs or larger (1min) for long-running events that may be + <parameter>usec</parameter> parameter specifies the earliest time, + in microseconds (µs), relative to the clock's epoch, when + the timer shall be triggered. If a time already in the past is + specified (including <constant>0</constant>), this timer source + "fires" immediately and is ready to be dispatched. The + <parameter>accuracy</parameter> parameter specifies an additional + accuracy value in µs specifying how much the timer event may be + delayed. Use <constant>0</constant> to select the default accuracy + (250ms). Use 1µs for maximum accuracy. Consider specifying + 60000000µs (1min) or larger for long-running events that may be delayed substantially. Picking higher accuracy values allows the - system to coalesce timer events more aggressively, thus improving + system to coalesce timer events more aggressively, improving power efficiency. The <parameter>handler</parameter> parameter shall reference a function to call when the timer elapses. The handler function will be passed the <parameter>userdata</parameter> pointer, which may be chosen freely by the caller. The handler is also passed the configured - time it was triggered, however it might actually have been called - at a slightly later time, subject to the specified accuracy value, + trigger time, even if it is actually called + slightly later, subject to the specified accuracy value, the kernel timer slack (see - <citerefentry><refentrytitle>prctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>) + <citerefentry><refentrytitle>prctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>), and additional scheduling latencies. To query the actual time the handler was called use <citerefentry><refentrytitle>sd_event_now</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para> @@ -167,22 +167,24 @@ <citerefentry><refentrytitle>sd_event_source_unref</refentrytitle><manvolnum>3</manvolnum></citerefentry>, but note that the event source is only removed from the event loop when all references to the event source are dropped. To make sure - an event source does not fire anymore, even when there's still a - reference to it kept, consider setting the event source to - <constant>SD_EVENT_OFF</constant> with - <citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para> + an event source does not fire anymore, even if it is still referenced, + disable the event source using + <citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry> + with <constant>SD_EVENT_OFF</constant>.</para> <para>If the second parameter of - <function>sd_event_add_time()</function> is passed as NULL no - reference to the event source object is returned. In this case the - event source is considered "floating", and will be destroyed - implicitly when the event loop itself is destroyed.</para> + <function>sd_event_add_time()</function> is + <constant>NULL</constant> no reference to the event source object + is returned. In this case the event source is considered + "floating", and will be destroyed implicitly when the event loop + itself is destroyed.</para> <para>If the <parameter>handler</parameter> to - <function>sd_event_add_time()</function> is passed as NULL, and - the event source fires, this will be considered a request to exit - the event loop. In this case, the <parameter>userdata</parameter> - parameter, cast to an integer is used for the exit code passed to + <function>sd_event_add_time()</function> is + <constant>NULL</constant>, and the event source fires, this will + be considered a request to exit the event loop. In this case, the + <parameter>userdata</parameter> parameter, cast to an integer, is + used for the exit code passed to <citerefentry><refentrytitle>sd_event_exit</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para> <para>Use <constant>CLOCK_BOOTTIME_ALARM</constant> and @@ -192,7 +194,7 @@ <para>In order to set up relative timers (that is, relative to the current time), retrieve the current time via <citerefentry><refentrytitle>sd_event_now</refentrytitle><manvolnum>3</manvolnum></citerefentry>, - add the desired timespan to sleep to it, and pass the result as + add the desired timespan to it, and use the result as the <parameter>usec</parameter> parameter to <function>sd_event_add_time()</function>.</para> @@ -212,30 +214,30 @@ latency will keep accumulating on the timer.</para> <para><function>sd_event_source_get_time()</function> retrieves - the configured time value of a timer event source created + the configured time value of an event source created previously with <function>sd_event_add_time()</function>. It takes the event source object and a pointer to a variable to store the - time, relative to the selected clock's epoch, in µs in.</para> + time in, relative to the selected clock's epoch, in µs.</para> <para><function>sd_event_source_set_time()</function> changes the - configured time value of a timer event source created previously - with <function>sd_event_add_time()</function>. It takes the event - source object and a time relative to the selected clock's - epoch, in µs.</para> + time of an event source created previously with + <function>sd_event_add_time()</function>. It takes the event + source object and a time relative to the selected clock's epoch, + in µs.</para> <para><function>sd_event_source_get_time_accuracy()</function> - retrieves the configured accuracy value of a timer event source + retrieves the configured accuracy value of a event source created previously with <function>sd_event_add_time()</function>. It takes the event source object and a pointer to a variable to store - the accuracy in µs in.</para> + the accuracy in. The accuracy is specified in µs.</para> <para><function>sd_event_source_set_time_accuracy()</function> changes the configured accuracy of a timer event source created previously with <function>sd_event_add_time()</function>. It takes - the event source object and an accuracy, in µs.</para> + the event source object and accuracy, in µs.</para> <para><function>sd_event_source_get_time_clock()</function> - retrieves the configured clock of a timer event source created + retrieves the configured clock of a event source created previously with <function>sd_event_add_time()</function>. It takes the event source object and a pointer to a variable to store the clock identifier in.</para> @@ -252,7 +254,7 @@ <refsect1> <title>Errors</title> - <para>Returned errors may indicate the following problems:</para> + <para>Returned values may indicate the following problems:</para> <variablelist> <varlistentry> diff --git a/man/sd_event_now.xml b/man/sd_event_now.xml index 58d7375eac..2c83b0bcb5 100644 --- a/man/sd_event_now.xml +++ b/man/sd_event_now.xml @@ -65,45 +65,44 @@ <refsect1> <title>Description</title> - <para><function>sd_event_now()</function> returns the timestamp - the most recent event loop iteration began. This timestamp is - taken right after returning from the event sleep, and before + <para><function>sd_event_now()</function> returns the time when + the most recent event loop iteration began. A timestamp + is taken right after returning from the event sleep, and before dispatching any event sources. The <parameter>event</parameter> - parameter takes the even loop object to retrieve the timestamp + parameter specifies the event loop object to retrieve the timestamp from. The <parameter>clock</parameter> parameter specifies the clock to retrieve the timestamp for, and is one of - <constant>CLOCK_REALTIME</constant> (or its equivalent + <constant>CLOCK_REALTIME</constant> (or equivalently <constant>CLOCK_REALTIME_ALARM</constant>), - <constant>CLOCK_MONOTONIC</constant> or - <constant>CLOCK_BOOTTIME</constant> (or its equivalent - <constant>CLOCK_BOOTTIME_ALARM</constant>), see <citerefentry - project='man-pages'><refentrytitle>clock_gettime</refentrytitle><manvolnum>2</manvolnum></citerefentry> + <constant>CLOCK_MONOTONIC</constant>, or + <constant>CLOCK_BOOTTIME</constant> (or equivalently + <constant>CLOCK_BOOTTIME_ALARM</constant>), see + <citerefentry project='man-pages'><refentrytitle>clock_gettime</refentrytitle><manvolnum>2</manvolnum></citerefentry> for more information on the various clocks. The retrieved timestamp is stored in the <parameter>usec</parameter> parameter, in µs since the clock's epoch. If this function is invoked before - the first event loop iteration the current time is returned, as + the first event loop iteration, the current time is returned, as reported by <function>clock_gettime()</function>. To distinguish this case from a regular invocation the return value will be - positive non-zero in this case, while it is zero when the returned - timestamp refers to the actual event loop iteration.</para> + positive, and zero when the returned timestamp refers to an actual + event loop iteration.</para> </refsect1> <refsect1> <title>Return Value</title> <para>If the first event loop iteration has not run yet - <function>sd_event_now()</function> returns the requested - timestamp in <parameter>usec</parameter> and returns a positive, - non-zero return value. Otherwise, on success it will return the - iteration's timestamp in <parameter>usec</parameter> and 0 as - return value. On failure, the call returns a negative errno-style + <function>sd_event_now()</function> writes current time to + <parameter>usec</parameter> and returns a positive return value. + Otherwise, it will write the requested timestamp to <parameter>usec</parameter> + and return 0. On failure, the call returns a negative errno-style error code.</para> </refsect1> <refsect1> <title>Errors</title> - <para>Returned errors may indicate the following problems:</para> + <para>Returned values may indicate the following problems:</para> <variablelist> <varlistentry> @@ -115,12 +114,18 @@ </varlistentry> <varlistentry> + <term><constant>-EOPNOTSUPP</constant></term> + + <listitem><para>Unsupported clock type. + </para></listitem> + </varlistentry> + + <varlistentry> <term><constant>-ECHILD</constant></term> <listitem><para>The event loop object was created in a different process.</para></listitem> </varlistentry> - </variablelist> </refsect1> diff --git a/man/systemd-journal-gatewayd.service.xml b/man/systemd-journal-gatewayd.service.xml index 6df2248578..e32ac26850 100644 --- a/man/systemd-journal-gatewayd.service.xml +++ b/man/systemd-journal-gatewayd.service.xml @@ -193,7 +193,7 @@ </varlistentry> <varlistentry> - <term><constant>application/event-stream</constant></term> + <term><constant>text/event-stream</constant></term> <listitem><para>Entries are formatted as JSON data structures, wrapped in a format suitable for <ulink diff --git a/man/systemd-resolved.service.xml b/man/systemd-resolved.service.xml index 10198812e1..8e1ca1c092 100644 --- a/man/systemd-resolved.service.xml +++ b/man/systemd-resolved.service.xml @@ -144,7 +144,9 @@ <para> <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, <citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>dnssec-trust-anchors.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>, <citerefentry><refentrytitle>nss-resolve</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>resolv.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> </para> diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 5f98ef163c..f0f77c5091 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -807,6 +807,35 @@ </varlistentry> <varlistentry> + <term><varname>AmbientCapabilities=</varname></term> + + <listitem><para>Controls which capabilities to include in the + ambient capability set for the executed process. Takes a + whitespace-separated list of capability names as read by + <citerefentry project='mankier'><refentrytitle>cap_from_name</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + e.g. <constant>CAP_SYS_ADMIN</constant>, + <constant>CAP_DAC_OVERRIDE</constant>, + <constant>CAP_SYS_PTRACE</constant>. This option may appear more than + once in which case the ambient capability sets are merged. + If the list of capabilities is prefixed with <literal>~</literal>, all + but the listed capabilities will be included, the effect of the + assignment inverted. If the empty string is + assigned to this option, the ambient capability set is reset to + the empty capability set, and all prior settings have no effect. + If set to <literal>~</literal> (without any further argument), the + ambient capability set is reset to the full set of available + capabilities, also undoing any previous settings. Note that adding + capabilities to ambient capability set adds them to the process's + inherited capability set. + </para><para> + Ambient capability sets are useful if you want to execute a process + as a non-privileged user but still want to give it some capabilities. + Note that in this case option <constant>keep-caps</constant> is + automatically added to <varname>SecureBits=</varname> to retain the + capabilities over the user change.</para></listitem> + </varlistentry> + + <varlistentry> <term><varname>SecureBits=</varname></term> <listitem><para>Controls the secure bits set for the executed process. Takes a space-separated combination of options from diff --git a/man/systemd.generator.xml b/man/systemd.generator.xml index b36aab3259..62658fb115 100644 --- a/man/systemd.generator.xml +++ b/man/systemd.generator.xml @@ -315,7 +315,7 @@ </example> <example> - <title>Debuging a generator</title> + <title>Debugging a generator</title> <programlisting>dir=$(mktemp -d) SYSTEMD_LOG_LEVEL=debug &systemgeneratordir;/systemd-fstab-generator \ diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 5ad03f75e6..5a6383cfc2 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -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> diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 126b1b5cb4..9846659134 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -875,7 +875,7 @@ <para><varname>ConditionSecurity=</varname> may be used to check whether the given security module is enabled on the - system. Currently, the recognized values values are + system. Currently, the recognized values are <varname>selinux</varname>, <varname>apparmor</varname>, <varname>ima</varname>, diff --git a/man/systemd.xml b/man/systemd.xml index 6de18f8294..b8d91b8943 100644 --- a/man/systemd.xml +++ b/man/systemd.xml @@ -255,6 +255,14 @@ <option>inherit</option>.</para></listitem> </varlistentry> + <varlistentry> + <term><option>--machine-id=</option></term> + + <listitem><para>Override the machine-id set on the hard drive, + useful for network booting or for containers. May not be set + to all zeros.</para></listitem> + </varlistentry> + <xi:include href="standard-options.xml" xpointer="help" /> <xi:include href="standard-options.xml" xpointer="version" /> </variablelist> @@ -827,6 +835,13 @@ </varlistentry> <varlistentry> + <term><varname>$SYSTEMD_COLORS</varname></term> + + <listitem><para>Controls whether colorized output should be generated. + </para></listitem> + </varlistentry> + + <varlistentry> <term><varname>$LISTEN_PID</varname></term> <term><varname>$LISTEN_FDS</varname></term> <term><varname>$LISTEN_FDNAMES</varname></term> @@ -977,6 +992,15 @@ </varlistentry> <varlistentry> + <term><varname>systemd.machine_id=</varname></term> + + <listitem><para>Takes a 32 character hex value to be + used for setting the machine-id. Intended mostly for + network booting where the same machine-id is desired + for every boot.</para></listitem> + </varlistentry> + + <varlistentry> <term><varname>quiet</varname></term> <listitem><para>Turn off status output at boot, much like diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml index 3c847d74a9..3b6b1e3f11 100644 --- a/man/tmpfiles.d.xml +++ b/man/tmpfiles.d.xml @@ -264,7 +264,8 @@ be removed and be replaced by the symlink. If the argument is omitted, symlinks to files with the same name residing in the directory <filename>/usr/share/factory/</filename> are - created.</para></listitem> + created. Note that permissions and ownership on symlinks + are ignored.</para></listitem> </varlistentry> <varlistentry> @@ -2,21 +2,21 @@ # Copyright (C) 2014 systemd's COPYRIGHT HOLDER # This file is distributed under the same license as the systemd package. # Eugene Melnik <jeka7js@gmail.com>, 2014. -# Daniel Korostil <ted.korostiled@gmail.com>, 2014. +# Daniel Korostil <ted.korostiled@gmail.com>, 2014, 2016. msgid "" msgstr "" "Project-Id-Version: systemd master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-11-22 16:37+0100\n" -"PO-Revision-Date: 2014-07-16 19:13+0300\n" +"POT-Creation-Date: 2016-01-11 09:21+0200\n" +"PO-Revision-Date: 2016-01-11 11:00+0300\n" "Last-Translator: Daniel Korostil <ted.korostiled@gmail.com>\n" "Language-Team: linux.org.ua\n" "Language: uk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Virtaal 0.7.1\n" #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:1 @@ -30,43 +30,42 @@ msgstr "Засвідчення потрібно, щоб надіслати вв #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:3 msgid "Manage system services or other units" -msgstr "" +msgstr "Керувати системними службами й іншими одиницями" #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:4 -#, fuzzy msgid "Authentication is required to manage system services or other units." -msgstr "Засвідчення потрібно, щоб доступитись до менеджера системи і служб." +msgstr "" +"Засвідчення потрібно, щоб керувати системними службами й іншими одиницями." #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:5 msgid "Manage system service or unit files" -msgstr "" +msgstr "Керувати системними службами й файлами одиниць" #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:6 -#, fuzzy msgid "Authentication is required to manage system service or unit files." -msgstr "Засвідчення потрібно, щоб доступитись до менеджера системи і служб." +msgstr "" +"Засвідчення потрібно, щоб керувати системними службами й файлами одиниць." #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:7 -#, fuzzy msgid "Set or unset system and service manager environment variables" -msgstr "Привілейований доступ до менеджера системи і служб" +msgstr "" +"Встановити або забрати змінну середовища з керування службами і системою" #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:8 -#, fuzzy msgid "" "Authentication is required to set or unset system and service manager " "environment variables." -msgstr "Засвідчення потрібно, щоб доступитись до менеджера системи і служб." +msgstr "" +"Засвідчення потрібно, щоб установити або забрати змінні середовища з " +"керування службами і системою." #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:9 -#, fuzzy msgid "Reload the systemd state" -msgstr "Перезавантажити систему" +msgstr "Перезапустити стан системи" #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:10 -#, fuzzy msgid "Authentication is required to reload the systemd state." -msgstr "Засвідчення потрібно, щоб вказати системний час." +msgstr "Засвідчення потрібно, щоб перезапустити стан системи." #: ../src/hostname/org.freedesktop.hostname1.policy.in.h:1 msgid "Set host name" @@ -98,30 +97,31 @@ msgstr "Засвідчення потрібно, щоб вказати лока #: ../src/import/org.freedesktop.import1.policy.in.h:1 msgid "Import a VM or container image" -msgstr "" +msgstr "Імпортувати образ контейнера або віртуальної машини" #: ../src/import/org.freedesktop.import1.policy.in.h:2 -#, fuzzy msgid "Authentication is required to import a VM or container image" -msgstr "Засвідчення потрібно, щоб вказати системний час." +msgstr "" +"Засвідчення потрібно, щоб імпортувати образ контейнера або віртуальної машини" #: ../src/import/org.freedesktop.import1.policy.in.h:3 msgid "Export a VM or container image" -msgstr "" +msgstr "Експортувати образ контейнера або віртуальної машини" #: ../src/import/org.freedesktop.import1.policy.in.h:4 -#, fuzzy msgid "Authentication is required to export a VM or container image" -msgstr "Засвідчення потрібно, щоб вказати системний час." +msgstr "" +"Засвідчення потрібно, щоб експортувати образ контейнера або віртуальної " +"машини" #: ../src/import/org.freedesktop.import1.policy.in.h:5 msgid "Download a VM or container image" -msgstr "" +msgstr "Звантажити образ контейнера або віртуальної машини" #: ../src/import/org.freedesktop.import1.policy.in.h:6 -#, fuzzy msgid "Authentication is required to download a VM or container image" -msgstr "Засвідчення потрібно, щоб вказати локальну інформацію про машини." +msgstr "" +"Засвідчення потрібно, щоб звантажити образ контейнера або віртуальної машини" #: ../src/locale/org.freedesktop.locale1.policy.in.h:1 msgid "Set system locale" @@ -277,7 +277,7 @@ msgstr "Засвідчення потрібно, щоб вимкнути сис #: ../src/login/org.freedesktop.login1.policy.in.h:27 msgid "Power off the system while other users are logged in" -msgstr "Вимикнути систему, коли інші користувачі ще в ній" +msgstr "Вимкнути систему, коли інші користувачі ще в ній" #: ../src/login/org.freedesktop.login1.policy.in.h:28 msgid "" @@ -288,7 +288,7 @@ msgstr "" #: ../src/login/org.freedesktop.login1.policy.in.h:29 msgid "Power off the system while an application asked to inhibit it" -msgstr "Вимкнути систему, коли програми намагаються першкодити цьому" +msgstr "Вимкнути систему, коли програми намагаються перешкодити цьому" #: ../src/login/org.freedesktop.login1.policy.in.h:30 msgid "" @@ -296,7 +296,7 @@ msgid "" "asked to inhibit it." msgstr "" "Засвідчення потрібно, щоб вимкнути систему, коли програми намагаються " -"першкодити цьому." +"перешкодити цьому." #: ../src/login/org.freedesktop.login1.policy.in.h:31 msgid "Reboot the system" @@ -308,7 +308,7 @@ msgstr "Для перезавантаження системи необхідн #: ../src/login/org.freedesktop.login1.policy.in.h:33 msgid "Reboot the system while other users are logged in" -msgstr "Перезавантажити, якщо інщі користувачі в системі" +msgstr "Перезавантажити, якщо інші користувачі в системі" #: ../src/login/org.freedesktop.login1.policy.in.h:34 msgid "" @@ -319,7 +319,7 @@ msgstr "" #: ../src/login/org.freedesktop.login1.policy.in.h:35 msgid "Reboot the system while an application asked to inhibit it" -msgstr "Перезапустити систему, коли програми намагаються першкодити цьому" +msgstr "Перезапустити систему, коли програми намагаються перешкодити цьому" #: ../src/login/org.freedesktop.login1.policy.in.h:36 msgid "" @@ -327,7 +327,7 @@ msgid "" "asked to inhibit it." msgstr "" "Засвідчення потрібно, щоб перезапустити систему, коли програми намагаються " -"першкодити цьому." +"перешкодити цьому." #: ../src/login/org.freedesktop.login1.policy.in.h:37 msgid "Suspend the system" @@ -350,15 +350,15 @@ msgstr "" #: ../src/login/org.freedesktop.login1.policy.in.h:41 msgid "Suspend the system while an application asked to inhibit it" -msgstr "Призупинити систему, коли програми намагаються першкодити цьому" +msgstr "Призупинити систему, коли програми намагаються перешкодити цьому" #: ../src/login/org.freedesktop.login1.policy.in.h:42 msgid "" "Authentication is required for suspending the system while an application " "asked to inhibit it." msgstr "" -"Засвідчення потрібно, щоб призупнити систему, коли програми намагаються " -"першкодити цьому." +"Засвідчення потрібно, щоб призупинити систему, коли програми намагаються " +"перешкодити цьому." #: ../src/login/org.freedesktop.login1.policy.in.h:43 msgid "Hibernate the system" @@ -381,7 +381,7 @@ msgstr "" #: ../src/login/org.freedesktop.login1.policy.in.h:47 msgid "Hibernate the system while an application asked to inhibit it" -msgstr "Приспати систему, коли програми намагаються першкодити цьому" +msgstr "Приспати систему, коли програми намагаються перешкодити цьому" #: ../src/login/org.freedesktop.login1.policy.in.h:48 msgid "" @@ -389,124 +389,118 @@ msgid "" "asked to inhibit it." msgstr "" "Засвідчення потрібно, щоб приспати систему, коли програми намагаються " -"першкодити цьому." +"перешкодити цьому." #: ../src/login/org.freedesktop.login1.policy.in.h:49 msgid "Manage active sessions, users and seats" -msgstr "" +msgstr "Керувати сеансами, користувачами і робочими місцями" #: ../src/login/org.freedesktop.login1.policy.in.h:50 -#, fuzzy msgid "" "Authentication is required for managing active sessions, users and seats." -msgstr "Засвідчення потрібно, щоб під'єднувати пристрої до місць." +msgstr "" +"Засвідчення потрібно, щоб керувати сеансами, користувачами і робочими " +"місцями." #: ../src/login/org.freedesktop.login1.policy.in.h:51 msgid "Lock or unlock active sessions" -msgstr "" +msgstr "Заблокувати або розблокувати сеанси" #: ../src/login/org.freedesktop.login1.policy.in.h:52 -#, fuzzy msgid "Authentication is required to lock or unlock active sessions." -msgstr "Засвідчення потрібно, щоб вказати локальну інформацію про машини." +msgstr "Засвідчення потрібно, щоб заблокувати або розблокувати сеанси." #: ../src/login/org.freedesktop.login1.policy.in.h:53 msgid "Allow indication to the firmware to boot to setup interface" -msgstr "" +msgstr "Дозволити мікрокоду визначати, чи завантажувати інтерфейс встановлення" #: ../src/login/org.freedesktop.login1.policy.in.h:54 -#, fuzzy msgid "" "Authentication is required to indicate to the firmware to boot to setup " "interface." -msgstr "Засвідчення потрібне, щоб встановити назву локального вузла." +msgstr "" +"Засвідчення потрібне, щоб дозволити мікрокоду визначати, чи завантажувати " +"інтерфейс встановлення." #: ../src/login/org.freedesktop.login1.policy.in.h:55 msgid "Set a wall message" -msgstr "" +msgstr "Вказати повідомлення на стіні" #: ../src/login/org.freedesktop.login1.policy.in.h:56 -#, fuzzy msgid "Authentication is required to set a wall message" -msgstr "Засвідчення потрібне, щоб встановити назву локального вузла." +msgstr "Засвідчення потрібне, щоб вказати повідомлення на стіні" #: ../src/machine/org.freedesktop.machine1.policy.in.h:1 msgid "Log into a local container" -msgstr "" +msgstr "Увійти в локальний контейнер" #: ../src/machine/org.freedesktop.machine1.policy.in.h:2 -#, fuzzy msgid "Authentication is required to log into a local container." -msgstr "Засвідчення потрібне, щоб встановити назву локального вузла." +msgstr "Засвідчення потрібне, щоб увійти в локальний контейнер." #: ../src/machine/org.freedesktop.machine1.policy.in.h:3 msgid "Log into the local host" -msgstr "" +msgstr "Увійти в локальний вузол" #: ../src/machine/org.freedesktop.machine1.policy.in.h:4 -#, fuzzy msgid "Authentication is required to log into the local host." -msgstr "Засвідчення потрібне, щоб встановити назву локального вузла." +msgstr "Засвідчення потрібне, щоб увійти в локальний вузол." #: ../src/machine/org.freedesktop.machine1.policy.in.h:5 -#, fuzzy msgid "Acquire a shell in a local container" -msgstr "Засвідчення потрібне, щоб встановити назву локального вузла." +msgstr "Перейняти оболонку в локальному контейнері" #: ../src/machine/org.freedesktop.machine1.policy.in.h:6 -#, fuzzy msgid "Authentication is required to acquire a shell in a local container." -msgstr "Засвідчення потрібне, щоб встановити назву локального вузла." +msgstr "Засвідчення потрібне, щоб перейняти оболонку в локальному контейнері." #: ../src/machine/org.freedesktop.machine1.policy.in.h:7 msgid "Acquire a shell on the local host" -msgstr "" +msgstr "Перейняти оболонку на локальному вузлі" #: ../src/machine/org.freedesktop.machine1.policy.in.h:8 -#, fuzzy msgid "Authentication is required to acquire a shell on the local host." -msgstr "Засвідчення потрібне, щоб встановити назву локального вузла." +msgstr "Засвідчення потрібне, щоб перейняти оболонку на локальному вузлі." #: ../src/machine/org.freedesktop.machine1.policy.in.h:9 -#, fuzzy msgid "Acquire a pseudo TTY in a local container" -msgstr "Засвідчення потрібне, щоб встановити назву локального вузла." +msgstr "Перейняти псевдо TTY в локальному контейнері" #: ../src/machine/org.freedesktop.machine1.policy.in.h:10 -#, fuzzy msgid "" "Authentication is required to acquire a pseudo TTY in a local container." -msgstr "Засвідчення потрібне, щоб встановити назву локального вузла." +msgstr "Засвідчення потрібне, щоб перейняти псевдо TTY в локальному контейнері." #: ../src/machine/org.freedesktop.machine1.policy.in.h:11 msgid "Acquire a pseudo TTY on the local host" -msgstr "" +msgstr "Перейняти псевдо TTY на локальному вузлі" #: ../src/machine/org.freedesktop.machine1.policy.in.h:12 -#, fuzzy msgid "Authentication is required to acquire a pseudo TTY on the local host." -msgstr "Засвідчення потрібне, щоб встановити назву локального вузла." +msgstr "Засвідчення потрібне, щоб перейняти псевдо TTY на локальному вузлі." #: ../src/machine/org.freedesktop.machine1.policy.in.h:13 msgid "Manage local virtual machines and containers" -msgstr "" +msgstr "Керувати локальними віртуальними машинами і контейнерами" #: ../src/machine/org.freedesktop.machine1.policy.in.h:14 -#, fuzzy msgid "" "Authentication is required to manage local virtual machines and containers." -msgstr "Засвідчення потрібно, щоб вказати локальну інформацію про машини." +msgstr "" +"Засвідчення потрібно, щоб керувати локальними віртуальними машинами і " +"контейнерами." #: ../src/machine/org.freedesktop.machine1.policy.in.h:15 msgid "Manage local virtual machine and container images" -msgstr "" +msgstr "Керувати локальними образами віртуальних машин і контейнерів" #: ../src/machine/org.freedesktop.machine1.policy.in.h:16 -#, fuzzy msgid "" "Authentication is required to manage local virtual machine and container " "images." -msgstr "Засвідчення потрібно, щоб вказати локальну інформацію про машини." +msgstr "" +"Засвідчення потрібно, щоб керувати локальними образами віртуальних машин і " +"контейнерів." #: ../src/timedate/org.freedesktop.timedate1.policy.in.h:1 msgid "Set system time" @@ -546,37 +540,30 @@ msgstr "" "Засвідчення потрібно, щоб контролювати, чи синхронізування часу через мережу " "запущено." -#: ../src/core/dbus-unit.c:428 -#, fuzzy +#: ../src/core/dbus-unit.c:449 msgid "Authentication is required to start '$(unit)'." -msgstr "Засвідчення потрібно, щоб вказати системний час." +msgstr "Засвідчення потрібно, щоб запустити «$(unit)»." -#: ../src/core/dbus-unit.c:429 -#, fuzzy +#: ../src/core/dbus-unit.c:450 msgid "Authentication is required to stop '$(unit)'." -msgstr "Засвідчення потрібно, щоб вказати системний час." +msgstr "Засвідчення потрібно, щоб зупинити «$(unit)»." -#: ../src/core/dbus-unit.c:430 -#, fuzzy +#: ../src/core/dbus-unit.c:451 msgid "Authentication is required to reload '$(unit)'." -msgstr "Засвідчення потрібно, щоб вказати системний час." +msgstr "Засвідчення потрібно, щоб перезавантажити «$(unit)»." -#: ../src/core/dbus-unit.c:431 ../src/core/dbus-unit.c:432 -#, fuzzy +#: ../src/core/dbus-unit.c:452 ../src/core/dbus-unit.c:453 msgid "Authentication is required to restart '$(unit)'." -msgstr "Засвідчення потрібно, щоб вказати системний час." +msgstr "Засвідчення потрібно, щоб перезапустити «$(unit)»." -#: ../src/core/dbus-unit.c:535 -#, fuzzy +#: ../src/core/dbus-unit.c:556 msgid "Authentication is required to kill '$(unit)'." -msgstr "Засвідчення потрібне, щоб встановити назву локального вузла." +msgstr "Засвідчення потрібне, щоб вбити «$(unit)»." -#: ../src/core/dbus-unit.c:565 -#, fuzzy +#: ../src/core/dbus-unit.c:586 msgid "Authentication is required to reset the \"failed\" state of '$(unit)'." -msgstr "Засвідчення потрібне, щоб встановити назву локального вузла." +msgstr "Засвідчення потрібне, щоб скинути «пошкоджений» стан з «$(unit)»." -#: ../src/core/dbus-unit.c:597 -#, fuzzy +#: ../src/core/dbus-unit.c:618 msgid "Authentication is required to set properties on '$(unit)'." -msgstr "Засвідчення потрібно, щоб вказати системний час." +msgstr "Засвідчення потрібно, щоб вказати властивості на «$(unit)»." diff --git a/src/backlight/backlight.c b/src/backlight/backlight.c index b0fa079fec..a59459bc26 100644 --- a/src/backlight/backlight.c +++ b/src/backlight/backlight.c @@ -323,7 +323,7 @@ int main(int argc, char *argv[]) { errno = 0; device = udev_device_new_from_subsystem_sysname(udev, ss, sysname); if (!device) { - if (errno != 0) + if (errno > 0) log_error_errno(errno, "Failed to get backlight or LED device '%s:%s': %m", ss, sysname); else log_oom(); diff --git a/src/basic/btrfs-util.c b/src/basic/btrfs-util.c index acd48f6954..d07d1df5a8 100644 --- a/src/basic/btrfs-util.c +++ b/src/basic/btrfs-util.c @@ -2051,7 +2051,7 @@ int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret) { args.key.nr_items = 256; if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) - return -errno; + return negative_errno(); if (args.key.nr_items <= 0) break; diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index fef722b6f2..49c2d61afe 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -96,7 +96,62 @@ unsigned long cap_last_cap(void) { return p; } -int capability_bounding_set_drop(uint64_t drop, bool right_now) { +int capability_update_inherited_set(cap_t caps, uint64_t set) { + unsigned long i; + + /* Add capabilities in the set to the inherited caps. Do not apply + * them yet. */ + + for (i = 0; i < cap_last_cap(); i++) { + + if (set & (UINT64_C(1) << i)) { + cap_value_t v; + + v = (cap_value_t) i; + + /* Make the capability inheritable. */ + if (cap_set_flag(caps, CAP_INHERITABLE, 1, &v, CAP_SET) < 0) + return -errno; + } + } + + return 0; +} + +int capability_ambient_set_apply(uint64_t set, bool also_inherit) { + unsigned long i; + _cleanup_cap_free_ cap_t caps = NULL; + + /* Add the capabilities to the ambient set. */ + + if (also_inherit) { + int r; + caps = cap_get_proc(); + if (!caps) + return -errno; + + r = capability_update_inherited_set(caps, set); + if (r < 0) + return -errno; + + if (cap_set_proc(caps) < 0) + return -errno; + } + + for (i = 0; i < cap_last_cap(); i++) { + + if (set & (UINT64_C(1) << i)) { + + /* Add the capability to the ambient set. */ + if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, i, 0, 0) < 0) + return -errno; + } + } + + return 0; +} + +int capability_bounding_set_drop(uint64_t keep, bool right_now) { _cleanup_cap_free_ cap_t after_cap = NULL; cap_flag_value_t fv; unsigned long i; @@ -137,7 +192,7 @@ int capability_bounding_set_drop(uint64_t drop, bool right_now) { for (i = 0; i <= cap_last_cap(); i++) { - if (drop & ((uint64_t) 1ULL << (uint64_t) i)) { + if (!(keep & (UINT64_C(1) << i))) { cap_value_t v; /* Drop it from the bounding set */ @@ -176,7 +231,7 @@ finish: return r; } -static int drop_from_file(const char *fn, uint64_t drop) { +static int drop_from_file(const char *fn, uint64_t keep) { int r, k; uint32_t hi, lo; uint64_t current, after; @@ -196,7 +251,7 @@ static int drop_from_file(const char *fn, uint64_t drop) { return -EIO; current = (uint64_t) lo | ((uint64_t) hi << 32ULL); - after = current & ~drop; + after = current & keep; if (current == after) return 0; @@ -213,14 +268,14 @@ static int drop_from_file(const char *fn, uint64_t drop) { return r; } -int capability_bounding_set_drop_usermode(uint64_t drop) { +int capability_bounding_set_drop_usermode(uint64_t keep) { int r; - r = drop_from_file("/proc/sys/kernel/usermodehelper/inheritable", drop); + r = drop_from_file("/proc/sys/kernel/usermodehelper/inheritable", keep); if (r < 0) return r; - r = drop_from_file("/proc/sys/kernel/usermodehelper/bset", drop); + r = drop_from_file("/proc/sys/kernel/usermodehelper/bset", keep); if (r < 0) return r; @@ -257,7 +312,7 @@ int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities) { return log_error_errno(errno, "Failed to disable keep capabilities flag: %m"); /* Drop all caps from the bounding set, except the ones we want */ - r = capability_bounding_set_drop(~keep_capabilities, true); + r = capability_bounding_set_drop(keep_capabilities, true); if (r < 0) return log_error_errno(r, "Failed to drop capabilities: %m"); diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index 6bbf7318fd..be41475441 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -29,10 +29,15 @@ #include "macro.h" #include "util.h" +#define CAP_ALL (uint64_t) -1 + unsigned long cap_last_cap(void); int have_effective_cap(int value); -int capability_bounding_set_drop(uint64_t drop, bool right_now); -int capability_bounding_set_drop_usermode(uint64_t drop); +int capability_bounding_set_drop(uint64_t keep, bool right_now); +int capability_bounding_set_drop_usermode(uint64_t keep); + +int capability_ambient_set_apply(uint64_t set, bool also_inherit); +int capability_update_inherited_set(cap_t caps, uint64_t ambient_set); int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities); @@ -46,3 +51,9 @@ static inline void cap_free_charpp(char **p) { cap_free(*p); } #define _cleanup_cap_free_charp_ _cleanup_(cap_free_charpp) + +static inline bool cap_test_all(uint64_t caps) { + uint64_t m; + m = (UINT64_C(1) << (cap_last_cap() + 1)) - 1; + return (caps & m) == m; +} diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index 639f9f3db1..f873fb89d3 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -53,6 +53,7 @@ #include "set.h" #include "special.h" #include "stat-util.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "unit-name.h" @@ -92,7 +93,7 @@ int cg_read_pid(FILE *f, pid_t *_pid) { if (feof(f)) return 0; - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; } if (ul <= 0) @@ -647,7 +648,7 @@ int cg_trim(const char *controller, const char *path, bool delete_root) { if (nftw(fs, trim_cb, 64, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) != 0) { if (errno == ENOENT) r = 0; - else if (errno != 0) + else if (errno > 0) r = -errno; else r = -EIO; @@ -716,7 +717,7 @@ int cg_attach(const char *controller, const char *path, pid_t pid) { if (pid == 0) pid = getpid(); - snprintf(c, sizeof(c), PID_FMT"\n", pid); + xsprintf(c, PID_FMT "\n", pid); return write_string_file(fs, c, 0); } @@ -2090,7 +2091,7 @@ int cg_kernel_controllers(Set *controllers) { if (feof(f)) break; - if (ferror(f) && errno != 0) + if (ferror(f) && errno > 0) return -errno; return -EBADMSG; diff --git a/src/basic/clock-util.c b/src/basic/clock-util.c index 00f549c023..05788a360e 100644 --- a/src/basic/clock-util.c +++ b/src/basic/clock-util.c @@ -33,6 +33,7 @@ #include "fd-util.h" #include "macro.h" #include "string-util.h" +#include "util.h" int clock_get_hwclock(struct tm *tm) { _cleanup_close_ int fd = -1; @@ -121,7 +122,8 @@ int clock_set_timezone(int *min) { * have read from the RTC. */ if (settimeofday(tv_null, &tz) < 0) - return -errno; + return negative_errno(); + if (min) *min = minutesdelta; return 0; diff --git a/src/basic/errno-list.c b/src/basic/errno-list.c index 0a66902ac9..b4d080103b 100644 --- a/src/basic/errno-list.c +++ b/src/basic/errno-list.c @@ -25,7 +25,7 @@ #include "macro.h" static const struct errno_name* lookup_errno(register const char *str, - register unsigned int len); + register unsigned int len); #include "errno-from-name.h" #include "errno-to-name.h" @@ -48,8 +48,9 @@ int errno_from_name(const char *name) { sc = lookup_errno(name, strlen(name)); if (!sc) - return 0; + return -EINVAL; + assert(sc->id > 0); return sc->id; } diff --git a/src/basic/escape.c b/src/basic/escape.c index ab282efa3c..5661f36813 100644 --- a/src/basic/escape.c +++ b/src/basic/escape.c @@ -119,16 +119,18 @@ char *cescape(const char *s) { return cescape_length(s, strlen(s)); } -int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode) { +int cunescape_one(const char *p, size_t length, uint32_t *ret, bool *eight_bit) { int r = 1; assert(p); assert(*p); assert(ret); - /* Unescapes C style. Returns the unescaped character in ret, - * unless we encountered a \u sequence in which case the full - * unicode character is returned in ret_unicode, instead. */ + /* Unescapes C style. Returns the unescaped character in ret. + * Sets *eight_bit to true if the escaped sequence either fits in + * one byte in UTF-8 or is a non-unicode literal byte and should + * instead be copied directly. + */ if (length != (size_t) -1 && length < 1) return -EINVAL; @@ -190,7 +192,8 @@ int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode if (a == 0 && b == 0) return -EINVAL; - *ret = (char) ((a << 4U) | b); + *ret = (a << 4U) | b; + *eight_bit = true; r = 3; break; } @@ -217,16 +220,7 @@ int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode if (c == 0) return -EINVAL; - if (c < 128) - *ret = c; - else { - if (!ret_unicode) - return -EINVAL; - - *ret = 0; - *ret_unicode = c; - } - + *ret = c; r = 5; break; } @@ -258,16 +252,7 @@ int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode if (!unichar_is_valid(c)) return -EINVAL; - if (c < 128) - *ret = c; - else { - if (!ret_unicode) - return -EINVAL; - - *ret = 0; - *ret_unicode = c; - } - + *ret = c; r = 9; break; } @@ -309,6 +294,7 @@ int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode return -EINVAL; *ret = m; + *eight_bit = true; r = 3; break; } @@ -342,7 +328,7 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi for (f = s, t = r + pl; f < s + length; f++) { size_t remaining; uint32_t u; - char c; + bool eight_bit = false; int k; remaining = s + length - f; @@ -365,7 +351,7 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi return -EINVAL; } - k = cunescape_one(f + 1, remaining - 1, &c, &u); + k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit); if (k < 0) { if (flags & UNESCAPE_RELAX) { /* Invalid escape code, let's take it literal then */ @@ -377,14 +363,13 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi return k; } - if (c != 0) - /* Non-Unicode? Let's encode this directly */ - *(t++) = c; + f += k; + if (eight_bit) + /* One byte? Set directly as specified */ + *(t++) = u; else - /* Unicode? Then let's encode this in UTF-8 */ + /* Otherwise encode as multi-byte UTF-8 */ t += utf8_encode_unichar(t, u); - - f += k; } *t = 0; diff --git a/src/basic/escape.h b/src/basic/escape.h index c710f01743..d943aa71f5 100644 --- a/src/basic/escape.h +++ b/src/basic/escape.h @@ -45,7 +45,7 @@ size_t cescape_char(char c, char *buf); int cunescape(const char *s, UnescapeFlags flags, char **ret); int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret); int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret); -int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode); +int cunescape_one(const char *p, size_t length, uint32_t *ret, bool *eight_bit); char *xescape(const char *s, const char *bad); diff --git a/src/basic/extract-word.c b/src/basic/extract-word.c index 7cc2a1de13..090d2a7884 100644 --- a/src/basic/extract-word.c +++ b/src/basic/extract-word.c @@ -108,8 +108,9 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra if (flags & EXTRACT_CUNESCAPE) { uint32_t u; + bool eight_bit = false; - r = cunescape_one(*p, (size_t) -1, &c, &u); + r = cunescape_one(*p, (size_t) -1, &u, &eight_bit); if (r < 0) { if (flags & EXTRACT_CUNESCAPE_RELAX) { s[sz++] = '\\'; @@ -119,10 +120,10 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra } else { (*p) += r - 1; - if (c != 0) - s[sz++] = c; /* normal explicit char */ + if (eight_bit) + s[sz++] = u; else - sz += utf8_encode_unichar(s + sz, u); /* unicode chars we'll encode as utf8 */ + sz += utf8_encode_unichar(s + sz, u); } } else s[sz++] = c; diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index 5ce1592eeb..973413ff42 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -73,3 +73,6 @@ int same_fd(int a, int b); void cmsg_close_all(struct msghdr *mh); bool fdname_is_valid(const char *s); + +#define ERRNO_IS_DISCONNECT(r) \ + IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE) diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 3a237252b5..5ed5460904 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -165,7 +165,7 @@ int read_one_line_file(const char *fn, char **line) { if (!fgets(t, sizeof(t), f)) { if (ferror(f)) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; t[0] = 0; } @@ -1064,7 +1064,7 @@ int fflush_and_check(FILE *f) { fflush(f); if (ferror(f)) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; return 0; } diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index fb760abe18..d31bd6e273 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -481,7 +481,7 @@ int get_files_in_directory(const char *path, char ***list) { errno = 0; de = readdir(d); - if (!de && errno != 0) + if (!de && errno > 0) return -errno; if (!de) break; diff --git a/src/basic/glob-util.c b/src/basic/glob-util.c index a0be0efd40..811ab6ec36 100644 --- a/src/basic/glob-util.c +++ b/src/basic/glob-util.c @@ -40,7 +40,7 @@ int glob_exists(const char *path) { if (k == GLOB_NOSPACE) return -ENOMEM; if (k != 0) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; return !strv_isempty(g.gl_pathv); } @@ -58,7 +58,7 @@ int glob_extend(char ***strv, const char *path) { if (k == GLOB_NOSPACE) return -ENOMEM; if (k != 0) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; if (strv_isempty(g.gl_pathv)) return -ENOENT; diff --git a/src/basic/hash-funcs.c b/src/basic/hash-funcs.c new file mode 100644 index 0000000000..d4affaffee --- /dev/null +++ b/src/basic/hash-funcs.c @@ -0,0 +1,83 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2014 Michal Schmidt + + 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 "hash-funcs.h" + +void string_hash_func(const void *p, struct siphash *state) { + siphash24_compress(p, strlen(p) + 1, state); +} + +int string_compare_func(const void *a, const void *b) { + return strcmp(a, b); +} + +const struct hash_ops string_hash_ops = { + .hash = string_hash_func, + .compare = string_compare_func +}; + +void trivial_hash_func(const void *p, struct siphash *state) { + siphash24_compress(&p, sizeof(p), state); +} + +int trivial_compare_func(const void *a, const void *b) { + return a < b ? -1 : (a > b ? 1 : 0); +} + +const struct hash_ops trivial_hash_ops = { + .hash = trivial_hash_func, + .compare = trivial_compare_func +}; + +void uint64_hash_func(const void *p, struct siphash *state) { + siphash24_compress(p, sizeof(uint64_t), state); +} + +int uint64_compare_func(const void *_a, const void *_b) { + uint64_t a, b; + a = *(const uint64_t*) _a; + b = *(const uint64_t*) _b; + return a < b ? -1 : (a > b ? 1 : 0); +} + +const struct hash_ops uint64_hash_ops = { + .hash = uint64_hash_func, + .compare = uint64_compare_func +}; + +#if SIZEOF_DEV_T != 8 +void devt_hash_func(const void *p, struct siphash *state) { + siphash24_compress(p, sizeof(dev_t), state); +} + +int devt_compare_func(const void *_a, const void *_b) { + dev_t a, b; + a = *(const dev_t*) _a; + b = *(const dev_t*) _b; + return a < b ? -1 : (a > b ? 1 : 0); +} + +const struct hash_ops devt_hash_ops = { + .hash = devt_hash_func, + .compare = devt_compare_func +}; +#endif diff --git a/src/basic/hash-funcs.h b/src/basic/hash-funcs.h new file mode 100644 index 0000000000..c640eaf4d1 --- /dev/null +++ b/src/basic/hash-funcs.h @@ -0,0 +1,67 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2014 Michal Schmidt + + 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" +#include "siphash24.h" + +typedef void (*hash_func_t)(const void *p, struct siphash *state); +typedef int (*compare_func_t)(const void *a, const void *b); + +struct hash_ops { + hash_func_t hash; + compare_func_t compare; +}; + +void string_hash_func(const void *p, struct siphash *state); +int string_compare_func(const void *a, const void *b) _pure_; +extern const struct hash_ops string_hash_ops; + +/* This will compare the passed pointers directly, and will not + * dereference them. This is hence not useful for strings or + * suchlike. */ +void trivial_hash_func(const void *p, struct siphash *state); +int trivial_compare_func(const void *a, const void *b) _const_; +extern const struct hash_ops trivial_hash_ops; + +/* 32bit values we can always just embed in the pointer itself, but + * in order to support 32bit archs we need store 64bit values + * indirectly, since they don't fit in a pointer. */ +void uint64_hash_func(const void *p, struct siphash *state); +int uint64_compare_func(const void *a, const void *b) _pure_; +extern const struct hash_ops uint64_hash_ops; + +/* On some archs dev_t is 32bit, and on others 64bit. And sometimes + * it's 64bit on 32bit archs, and sometimes 32bit on 64bit archs. Yuck! */ +#if SIZEOF_DEV_T != 8 +void devt_hash_func(const void *p, struct siphash *state) _pure_; +int devt_compare_func(const void *a, const void *b) _pure_; +extern const struct hash_ops devt_hash_ops = { + .hash = devt_hash_func, + .compare = devt_compare_func +}; +#else +#define devt_hash_func uint64_hash_func +#define devt_compare_func uint64_compare_func +#define devt_hash_ops uint64_hash_ops +#endif diff --git a/src/basic/hashmap.c b/src/basic/hashmap.c index 286ddfef5b..dcd8ae412d 100644 --- a/src/basic/hashmap.c +++ b/src/basic/hashmap.c @@ -280,66 +280,6 @@ static const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX] = { }, }; -void string_hash_func(const void *p, struct siphash *state) { - siphash24_compress(p, strlen(p) + 1, state); -} - -int string_compare_func(const void *a, const void *b) { - return strcmp(a, b); -} - -const struct hash_ops string_hash_ops = { - .hash = string_hash_func, - .compare = string_compare_func -}; - -void trivial_hash_func(const void *p, struct siphash *state) { - siphash24_compress(&p, sizeof(p), state); -} - -int trivial_compare_func(const void *a, const void *b) { - return a < b ? -1 : (a > b ? 1 : 0); -} - -const struct hash_ops trivial_hash_ops = { - .hash = trivial_hash_func, - .compare = trivial_compare_func -}; - -void uint64_hash_func(const void *p, struct siphash *state) { - siphash24_compress(p, sizeof(uint64_t), state); -} - -int uint64_compare_func(const void *_a, const void *_b) { - uint64_t a, b; - a = *(const uint64_t*) _a; - b = *(const uint64_t*) _b; - return a < b ? -1 : (a > b ? 1 : 0); -} - -const struct hash_ops uint64_hash_ops = { - .hash = uint64_hash_func, - .compare = uint64_compare_func -}; - -#if SIZEOF_DEV_T != 8 -void devt_hash_func(const void *p, struct siphash *state) { - siphash24_compress(p, sizeof(dev_t), state); -} - -int devt_compare_func(const void *_a, const void *_b) { - dev_t a, b; - a = *(const dev_t*) _a; - b = *(const dev_t*) _b; - return a < b ? -1 : (a > b ? 1 : 0); -} - -const struct hash_ops devt_hash_ops = { - .hash = devt_hash_func, - .compare = devt_compare_func -}; -#endif - static unsigned n_buckets(HashmapBase *h) { return h->has_indirect ? h->indirect.n_buckets : hashmap_type_info[h->type].n_direct_buckets; diff --git a/src/basic/hashmap.h b/src/basic/hashmap.h index 708811124b..fdba9c61ff 100644 --- a/src/basic/hashmap.h +++ b/src/basic/hashmap.h @@ -26,8 +26,8 @@ #include <stdbool.h> #include <stddef.h> +#include "hash-funcs.h" #include "macro.h" -#include "siphash24.h" #include "util.h" /* @@ -70,47 +70,6 @@ typedef struct { #define _IDX_ITERATOR_FIRST (UINT_MAX - 1) #define ITERATOR_FIRST ((Iterator) { .idx = _IDX_ITERATOR_FIRST, .next_key = NULL }) -typedef void (*hash_func_t)(const void *p, struct siphash *state); -typedef int (*compare_func_t)(const void *a, const void *b); - -struct hash_ops { - hash_func_t hash; - compare_func_t compare; -}; - -void string_hash_func(const void *p, struct siphash *state); -int string_compare_func(const void *a, const void *b) _pure_; -extern const struct hash_ops string_hash_ops; - -/* This will compare the passed pointers directly, and will not - * dereference them. This is hence not useful for strings or - * suchlike. */ -void trivial_hash_func(const void *p, struct siphash *state); -int trivial_compare_func(const void *a, const void *b) _const_; -extern const struct hash_ops trivial_hash_ops; - -/* 32bit values we can always just embedd in the pointer itself, but - * in order to support 32bit archs we need store 64bit values - * indirectly, since they don't fit in a pointer. */ -void uint64_hash_func(const void *p, struct siphash *state); -int uint64_compare_func(const void *a, const void *b) _pure_; -extern const struct hash_ops uint64_hash_ops; - -/* On some archs dev_t is 32bit, and on others 64bit. And sometimes - * it's 64bit on 32bit archs, and sometimes 32bit on 64bit archs. Yuck! */ -#if SIZEOF_DEV_T != 8 -void devt_hash_func(const void *p, struct siphash *state) _pure_; -int devt_compare_func(const void *a, const void *b) _pure_; -extern const struct hash_ops devt_hash_ops = { - .hash = devt_hash_func, - .compare = devt_compare_func -}; -#else -#define devt_hash_func uint64_hash_func -#define devt_compare_func uint64_compare_func -#define devt_hash_ops uint64_hash_ops -#endif - /* Macros for type checking */ #define PTR_COMPATIBLE_WITH_HASHMAP_BASE(h) \ (__builtin_types_compatible_p(typeof(h), HashmapBase*) || \ diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c index 5143dddf8f..8609ffb3c9 100644 --- a/src/basic/in-addr-util.c +++ b/src/basic/in-addr-util.c @@ -219,7 +219,7 @@ int in_addr_to_string(int family, const union in_addr_union *u, char **ret) { errno = 0; if (!inet_ntop(family, u, x, l)) { free(x); - return errno ? -errno : -EINVAL; + return errno > 0 ? -errno : -EINVAL; } *ret = x; @@ -236,7 +236,7 @@ int in_addr_from_string(int family, const char *s, union in_addr_union *ret) { errno = 0; if (inet_pton(family, s, ret) <= 0) - return errno ? -errno : -EINVAL; + return errno > 0 ? -errno : -EINVAL; return 0; } diff --git a/src/basic/in-addr-util.h b/src/basic/in-addr-util.h index bcc116c783..f2b8865df5 100644 --- a/src/basic/in-addr-util.h +++ b/src/basic/in-addr-util.h @@ -33,6 +33,11 @@ union in_addr_union { struct in6_addr in6; }; +struct in_addr_data { + int family; + union in_addr_union address; +}; + int in_addr_is_null(int family, const union in_addr_union *u); int in_addr_is_link_local(int family, const union in_addr_union *u); int in_addr_is_localhost(int family, const union in_addr_union *u); diff --git a/src/basic/log.c b/src/basic/log.c index 1a9e6bdb91..a2bc0d5be2 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -352,7 +352,7 @@ static int write_to_console( highlight = LOG_PRI(level) <= LOG_ERR && show_color; if (show_location) { - snprintf(location, sizeof(location), "(%s:%i) ", file, line); + xsprintf(location, "(%s:%i) ", file, line); IOVEC_SET_STRING(iovec[n++], location); } @@ -777,7 +777,7 @@ static void log_assert( return; DISABLE_WARNING_FORMAT_NONLITERAL; - snprintf(buffer, sizeof(buffer), format, text, file, line, func); + xsprintf(buffer, format, text, file, line, func); REENABLE_WARNING; log_abort_msg = buffer; diff --git a/src/basic/macro.h b/src/basic/macro.h index 5088e6720d..c529c6ecad 100644 --- a/src/basic/macro.h +++ b/src/basic/macro.h @@ -320,18 +320,47 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) { #define SET_FLAG(v, flag, b) \ (v) = (b) ? ((v) | (flag)) : ((v) & ~(flag)) -#define IN_SET(x, y, ...) \ - ({ \ - static const typeof(y) _array[] = { (y), __VA_ARGS__ }; \ - const typeof(y) _x = (x); \ - unsigned _i; \ - bool _found = false; \ - for (_i = 0; _i < ELEMENTSOF(_array); _i++) \ - if (_array[_i] == _x) { \ - _found = true; \ - break; \ - } \ - _found; \ +#define CASE_F(X) case X: +#define CASE_F_1(CASE, X) CASE_F(X) +#define CASE_F_2(CASE, X, ...) CASE(X) CASE_F_1(CASE, __VA_ARGS__) +#define CASE_F_3(CASE, X, ...) CASE(X) CASE_F_2(CASE, __VA_ARGS__) +#define CASE_F_4(CASE, X, ...) CASE(X) CASE_F_3(CASE, __VA_ARGS__) +#define CASE_F_5(CASE, X, ...) CASE(X) CASE_F_4(CASE, __VA_ARGS__) +#define CASE_F_6(CASE, X, ...) CASE(X) CASE_F_5(CASE, __VA_ARGS__) +#define CASE_F_7(CASE, X, ...) CASE(X) CASE_F_6(CASE, __VA_ARGS__) +#define CASE_F_8(CASE, X, ...) CASE(X) CASE_F_7(CASE, __VA_ARGS__) +#define CASE_F_9(CASE, X, ...) CASE(X) CASE_F_8(CASE, __VA_ARGS__) +#define CASE_F_10(CASE, X, ...) CASE(X) CASE_F_9(CASE, __VA_ARGS__) +#define CASE_F_11(CASE, X, ...) CASE(X) CASE_F_10(CASE, __VA_ARGS__) +#define CASE_F_12(CASE, X, ...) CASE(X) CASE_F_11(CASE, __VA_ARGS__) +#define CASE_F_13(CASE, X, ...) CASE(X) CASE_F_12(CASE, __VA_ARGS__) +#define CASE_F_14(CASE, X, ...) CASE(X) CASE_F_13(CASE, __VA_ARGS__) +#define CASE_F_15(CASE, X, ...) CASE(X) CASE_F_14(CASE, __VA_ARGS__) +#define CASE_F_16(CASE, X, ...) CASE(X) CASE_F_15(CASE, __VA_ARGS__) +#define CASE_F_17(CASE, X, ...) CASE(X) CASE_F_16(CASE, __VA_ARGS__) +#define CASE_F_18(CASE, X, ...) CASE(X) CASE_F_17(CASE, __VA_ARGS__) +#define CASE_F_19(CASE, X, ...) CASE(X) CASE_F_18(CASE, __VA_ARGS__) +#define CASE_F_20(CASE, X, ...) CASE(X) CASE_F_19(CASE, __VA_ARGS__) + +#define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,NAME,...) NAME +#define FOR_EACH_MAKE_CASE(...) \ + GET_CASE_F(__VA_ARGS__,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12,CASE_F_11, \ + CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \ + (CASE_F,__VA_ARGS__) + +#define IN_SET(x, ...) \ + ({ \ + bool _found = false; \ + /* If the build breaks in the line below, you need to extend the case macros */ \ + static _unused_ char _static_assert__macros_need_to_be_extended[20 - sizeof((int[]){__VA_ARGS__})/sizeof(int)]; \ + switch(x) { \ + FOR_EACH_MAKE_CASE(__VA_ARGS__) \ + _found = true; \ + break; \ + default: \ + break; \ + } \ + _found; \ }) /* Define C11 thread_local attribute even on older gcc compiler diff --git a/src/basic/missing.h b/src/basic/missing.h index 880e724cb4..2d2785bead 100644 --- a/src/basic/missing.h +++ b/src/basic/missing.h @@ -1129,3 +1129,19 @@ static inline key_serial_t request_key(const char *type, const char *description #ifndef KEY_SPEC_USER_KEYRING #define KEY_SPEC_USER_KEYRING -4 #endif + +#ifndef PR_CAP_AMBIENT +#define PR_CAP_AMBIENT 47 +#endif + +#ifndef PR_CAP_AMBIENT_IS_SET +#define PR_CAP_AMBIENT_IS_SET 1 +#endif + +#ifndef PR_CAP_AMBIENT_RAISE +#define PR_CAP_AMBIENT_RAISE 2 +#endif + +#ifndef PR_CAP_AMBIENT_CLEAR_ALL +#define PR_CAP_AMBIENT_CLEAR_ALL 4 +#endif diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c index 618ef5d564..d8de6f90ea 100644 --- a/src/basic/parse-util.c +++ b/src/basic/parse-util.c @@ -81,7 +81,7 @@ int parse_mode(const char *s, mode_t *ret) { errno = 0; l = strtol(s, &x, 8); - if (errno != 0) + if (errno > 0) return -errno; if (!x || x == s || *x) return -EINVAL; @@ -176,7 +176,7 @@ int parse_size(const char *t, uint64_t base, uint64_t *size) { errno = 0; l = strtoull(p, &e, 10); - if (errno != 0) + if (errno > 0) return -errno; if (e == p) return -EINVAL; @@ -192,7 +192,7 @@ int parse_size(const char *t, uint64_t base, uint64_t *size) { char *e2; l2 = strtoull(e, &e2, 10); - if (errno != 0) + if (errno > 0) return -errno; /* Ignore failure. E.g. 10.M is valid */ @@ -330,7 +330,7 @@ int safe_atou(const char *s, unsigned *ret_u) { errno = 0; l = strtoul(s, &x, 0); - if (errno != 0) + if (errno > 0) return -errno; if (!x || x == s || *x) return -EINVAL; @@ -352,7 +352,7 @@ int safe_atoi(const char *s, int *ret_i) { errno = 0; l = strtol(s, &x, 0); - if (errno != 0) + if (errno > 0) return -errno; if (!x || x == s || *x) return -EINVAL; @@ -374,7 +374,7 @@ int safe_atollu(const char *s, long long unsigned *ret_llu) { errno = 0; l = strtoull(s, &x, 0); - if (errno != 0) + if (errno > 0) return -errno; if (!x || x == s || *x) return -EINVAL; @@ -394,7 +394,7 @@ int safe_atolli(const char *s, long long int *ret_lli) { errno = 0; l = strtoll(s, &x, 0); - if (errno != 0) + if (errno > 0) return -errno; if (!x || x == s || *x) return -EINVAL; @@ -414,7 +414,7 @@ int safe_atou8(const char *s, uint8_t *ret) { errno = 0; l = strtoul(s, &x, 0); - if (errno != 0) + if (errno > 0) return -errno; if (!x || x == s || *x) return -EINVAL; @@ -438,7 +438,7 @@ int safe_atou16(const char *s, uint16_t *ret) { errno = 0; l = strtoul(s, &x, 0); - if (errno != 0) + if (errno > 0) return -errno; if (!x || x == s || *x) return -EINVAL; @@ -460,7 +460,7 @@ int safe_atoi16(const char *s, int16_t *ret) { errno = 0; l = strtol(s, &x, 0); - if (errno != 0) + if (errno > 0) return -errno; if (!x || x == s || *x) return -EINVAL; @@ -485,7 +485,7 @@ int safe_atod(const char *s, double *ret_d) { errno = 0; d = strtod_l(s, &x, loc); - if (errno != 0) { + if (errno > 0) { freelocale(loc); return -errno; } diff --git a/src/basic/path-util.c b/src/basic/path-util.c index 61fab0e087..4837bb2d7d 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -102,7 +102,7 @@ int path_make_absolute_cwd(const char *p, char **ret) { cwd = get_current_dir_name(); if (!cwd) - return -errno; + return negative_errno(); c = strjoin(cwd, "/", p, NULL); } diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c index 14f8474da0..4807561723 100644 --- a/src/basic/rm-rf.c +++ b/src/basic/rm-rf.c @@ -82,7 +82,7 @@ int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { errno = 0; de = readdir(d); if (!de) { - if (errno != 0 && ret == 0) + if (errno > 0 && ret == 0) ret = -errno; return ret; } diff --git a/src/basic/signal-util.c b/src/basic/signal-util.c index 7637fccb2f..315efadd93 100644 --- a/src/basic/signal-util.c +++ b/src/basic/signal-util.c @@ -26,6 +26,7 @@ #include "macro.h" #include "parse-util.h" #include "signal-util.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" @@ -234,9 +235,9 @@ const char *signal_to_string(int signo) { return name; if (signo >= SIGRTMIN && signo <= SIGRTMAX) - snprintf(buf, sizeof(buf), "RTMIN+%d", signo - SIGRTMIN); + xsprintf(buf, "RTMIN+%d", signo - SIGRTMIN); else - snprintf(buf, sizeof(buf), "%d", signo); + xsprintf(buf, "%d", signo); return buf; } diff --git a/src/basic/siphash24.h b/src/basic/siphash24.h index 3f7e20362b..54e2420cc6 100644 --- a/src/basic/siphash24.h +++ b/src/basic/siphash24.h @@ -16,6 +16,8 @@ struct siphash { void siphash24_init(struct siphash *state, const uint8_t k[16]); void siphash24_compress(const void *in, size_t inlen, struct siphash *state); +#define siphash24_compress_byte(byte, state) siphash24_compress((const uint8_t[]) { (byte) }, 1, (state)) + uint64_t siphash24_finalize(struct siphash *state); uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]); diff --git a/src/basic/string-table.h b/src/basic/string-table.h index 2181a3a767..588404ab5a 100644 --- a/src/basic/string-table.h +++ b/src/basic/string-table.h @@ -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) { \ diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 8178c7093f..1f95a9abba 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -317,18 +317,67 @@ char *truncate_nl(char *s) { return s; } +char ascii_tolower(char x) { + + if (x >= 'A' && x <= 'Z') + return x - 'A' + 'a'; + + return x; +} + char *ascii_strlower(char *t) { char *p; assert(t); for (p = t; *p; p++) - if (*p >= 'A' && *p <= 'Z') - *p = *p - 'A' + 'a'; + *p = ascii_tolower(*p); return t; } +char *ascii_strlower_n(char *t, size_t n) { + size_t i; + + if (n <= 0) + return t; + + for (i = 0; i < n; i++) + t[i] = ascii_tolower(t[i]); + + return t; +} + +int ascii_strcasecmp_n(const char *a, const char *b, size_t n) { + + for (; n > 0; a++, b++, n--) { + int x, y; + + x = (int) (uint8_t) ascii_tolower(*a); + y = (int) (uint8_t) ascii_tolower(*b); + + if (x != y) + return x - y; + } + + return 0; +} + +int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m) { + int r; + + r = ascii_strcasecmp_n(a, b, MIN(n, m)); + if (r != 0) + return r; + + if (n < m) + return -1; + else if (n > m) + return 1; + else + return 0; +} + bool chars_intersect(const char *a, const char *b) { const char *p; diff --git a/src/basic/string-util.h b/src/basic/string-util.h index b59b9b5a71..8ea18f45aa 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -130,7 +130,12 @@ char *strstrip(char *s); char *delete_chars(char *s, const char *bad); char *truncate_nl(char *s); -char *ascii_strlower(char *path); +char ascii_tolower(char x); +char *ascii_strlower(char *s); +char *ascii_strlower_n(char *s, size_t n); + +int ascii_strcasecmp_n(const char *a, const char *b, size_t n); +int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m); bool chars_intersect(const char *a, const char *b) _pure_; diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index a39764472b..fedfc8a5df 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -128,7 +128,7 @@ int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) { errno = 0; if (!fgets(line, sizeof(line), f)) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; truncate_nl(line); @@ -212,7 +212,7 @@ int ask_string(char **ret, const char *text, ...) { errno = 0; if (!fgets(line, sizeof(line), stdin)) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; if (!endswith(line, "\n")) putchar('\n'); @@ -1135,3 +1135,16 @@ int open_terminal_in_namespace(pid_t pid, const char *name, int mode) { return receive_one_fd(pair[0], 0); } + +bool colors_enabled(void) { + const char *colors; + + colors = getenv("SYSTEMD_COLORS"); + if (!colors) { + if (streq_ptr(getenv("TERM"), "dumb")) + return false; + return on_tty(); + } + + return parse_boolean(colors) != 0; +} diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 597a0060ad..a7c96a77cb 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -79,37 +79,38 @@ unsigned lines(void); void columns_lines_cache_reset(int _unused_ signum); bool on_tty(void); +bool colors_enabled(void); static inline const char *ansi_underline(void) { - return on_tty() ? ANSI_UNDERLINE : ""; + return colors_enabled() ? ANSI_UNDERLINE : ""; } static inline const char *ansi_highlight(void) { - return on_tty() ? ANSI_HIGHLIGHT : ""; + return colors_enabled() ? ANSI_HIGHLIGHT : ""; } static inline const char *ansi_highlight_underline(void) { - return on_tty() ? ANSI_HIGHLIGHT_UNDERLINE : ""; + return colors_enabled() ? ANSI_HIGHLIGHT_UNDERLINE : ""; } static inline const char *ansi_highlight_red(void) { - return on_tty() ? ANSI_HIGHLIGHT_RED : ""; + return colors_enabled() ? ANSI_HIGHLIGHT_RED : ""; } static inline const char *ansi_highlight_green(void) { - return on_tty() ? ANSI_HIGHLIGHT_GREEN : ""; + return colors_enabled() ? ANSI_HIGHLIGHT_GREEN : ""; } static inline const char *ansi_highlight_yellow(void) { - return on_tty() ? ANSI_HIGHLIGHT_YELLOW : ""; + return colors_enabled() ? ANSI_HIGHLIGHT_YELLOW : ""; } static inline const char *ansi_highlight_blue(void) { - return on_tty() ? ANSI_HIGHLIGHT_BLUE : ""; + return colors_enabled() ? ANSI_HIGHLIGHT_BLUE : ""; } static inline const char *ansi_normal(void) { - return on_tty() ? ANSI_NORMAL : ""; + return colors_enabled() ? ANSI_NORMAL : ""; } int get_ctty_devnr(pid_t pid, dev_t *d); diff --git a/src/basic/user-util.c b/src/basic/user-util.c index 56e1a3be48..70a6e1f5e4 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -68,7 +68,7 @@ int parse_uid(const char *s, uid_t *ret) { if (!uid_is_valid(uid)) return -ENXIO; /* we return ENXIO instead of EINVAL * here, to make it easy to distuingish - * invalid numeric uids invalid + * invalid numeric uids from invalid * strings. */ if (ret) diff --git a/src/basic/util.c b/src/basic/util.c index 9e0b576283..4434ecfdf6 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -513,7 +513,7 @@ int on_ac_power(void) { errno = 0; de = readdir(d); - if (!de && errno != 0) + if (!de && errno > 0) return -errno; if (!de) diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 4cf42d17f3..77eea6aada 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -270,9 +270,9 @@ static int enumerate_binaries(const char *esp_path, const char *path, const char if (r < 0) return r; if (r > 0) - printf(" File: └─/%s/%s (%s)\n", path, de->d_name, v); + printf(" File: %s/%s/%s (%s)\n", draw_special_char(DRAW_TREE_RIGHT), path, de->d_name, v); else - printf(" File: └─/%s/%s\n", path, de->d_name); + printf(" File: %s/%s/%s\n", draw_special_char(DRAW_TREE_RIGHT), path, de->d_name); c++; } @@ -324,7 +324,7 @@ static int print_efi_option(uint16_t id, bool in_order) { printf(" ID: 0x%04X\n", id); printf(" Status: %sactive%s\n", active ? "" : "in", in_order ? ", boot-order" : ""); printf(" Partition: /dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", SD_ID128_FORMAT_VAL(partition)); - printf(" File: └─%s\n", path); + printf(" File: %s%s\n", draw_special_char(DRAW_TREE_RIGHT), path); printf("\n"); return 0; diff --git a/src/bootchart/svg.c b/src/bootchart/svg.c index 2bf473ffc1..79e261abe5 100644 --- a/src/bootchart/svg.c +++ b/src/bootchart/svg.c @@ -37,6 +37,7 @@ #include "fileio.h" #include "list.h" #include "macro.h" +#include "stdio-util.h" #include "store.h" #include "svg.h" #include "utf8.h" @@ -171,7 +172,7 @@ static int svg_title(FILE *of, const char *build, int pscount, double log_start, strncpy(rootbdev, &c[10], sizeof(rootbdev) - 1); rootbdev[3] = '\0'; - snprintf(filename, sizeof(filename), "/sys/block/%s/device/model", rootbdev); + xsprintf(filename, "/sys/block/%s/device/model", rootbdev); r = read_one_line_file(filename, &model); if (r < 0) diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index 0a5c11ad0c..4894296554 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -40,6 +40,7 @@ #include "parse-util.h" #include "path-util.h" #include "process-util.h" +#include "stdio-util.h" #include "terminal-util.h" #include "unit-name.h" #include "util.h" @@ -565,9 +566,9 @@ static void display(Hashmap *a) { } if (arg_cpu_type == CPU_PERCENT) - snprintf(buffer, sizeof(buffer), "%6s", "%CPU"); + xsprintf(buffer, "%6s", "%CPU"); else - snprintf(buffer, sizeof(buffer), "%*s", maxtcpu, "CPU Time"); + xsprintf(buffer, "%*s", maxtcpu, "CPU Time"); rows = lines(); if (rows <= 10) diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 1f736b2686..eae0808f9e 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -141,7 +141,7 @@ static int property_get_nice( else { errno = 0; n = getpriority(PRIO_PROCESS, 0); - if (errno != 0) + if (errno > 0) n = 0; } @@ -293,9 +293,25 @@ static int property_get_capability_bounding_set( assert(reply); assert(c); - /* We store this negated internally, to match the kernel, but - * we expose it normalized. */ - return sd_bus_message_append(reply, "t", ~c->capability_bounding_set_drop); + return sd_bus_message_append(reply, "t", c->capability_bounding_set); +} + +static int property_get_ambient_capabilities( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "t", c->capability_ambient_set); } static int property_get_capabilities( @@ -689,6 +705,7 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("Capabilities", "s", property_get_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SecureBits", "i", bus_property_get_int, offsetof(ExecContext, secure_bits), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("CapabilityBoundingSet", "t", property_get_capability_bounding_set, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("AmbientCapabilities", "t", property_get_ambient_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("User", "s", NULL, offsetof(ExecContext, user), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST), @@ -1365,7 +1382,7 @@ int bus_exec_context_set_transient_property( dirs = &c->read_write_dirs; else if (streq(name, "ReadOnlyDirectories")) dirs = &c->read_only_dirs; - else if (streq(name, "InaccessibleDirectories")) + else /* "InaccessibleDirectories" */ dirs = &c->inaccessible_dirs; if (strv_length(l) == 0) { diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 8a523cc8ac..c5c672a0a2 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -1607,6 +1607,7 @@ static int reply_unit_file_changes_and_free( if (r < 0) goto fail; + unit_file_changes_free(changes, n_changes); return sd_bus_send(NULL, reply, NULL); fail: @@ -1843,8 +1844,10 @@ static int method_preset_all_unit_files(sd_bus_message *message, void *userdata, scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; r = unit_file_preset_all(scope, runtime, NULL, mm, force, &changes, &n_changes); - if (r < 0) + if (r < 0) { + unit_file_changes_free(changes, n_changes); return r; + } return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); } diff --git a/src/core/device.c b/src/core/device.c index bcd4d1146b..56ed947089 100644 --- a/src/core/device.c +++ b/src/core/device.c @@ -267,7 +267,7 @@ static int device_add_udev_wants(Unit *u, struct udev_device *dev) { assert(u); assert(dev); - property = u->manager->running_as == MANAGER_USER ? "MANAGER_USER_WANTS" : "SYSTEMD_WANTS"; + property = u->manager->running_as == MANAGER_USER ? "SYSTEMD_USER_WANTS" : "SYSTEMD_WANTS"; wants = udev_device_get_property_value(dev, property); if (!wants) return 0; diff --git a/src/core/execute.c b/src/core/execute.c index 9b76861919..0028730889 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -737,12 +737,7 @@ static int enforce_user(const ExecContext *context, uid_t uid) { /* Sets (but doesn't lookup) the uid and make sure we keep the * capabilities while doing so. */ - if (context->capabilities) { - _cleanup_cap_free_ cap_t d = NULL; - static const cap_value_t bits[] = { - CAP_SETUID, /* Necessary so that we can run setresuid() below */ - CAP_SETPCAP /* Necessary so that we can set PR_SET_SECUREBITS later on */ - }; + if (context->capabilities || context->capability_ambient_set != 0) { /* First step: If we need to keep capabilities but * drop privileges we need to make sure we keep our @@ -758,16 +753,24 @@ static int enforce_user(const ExecContext *context, uid_t uid) { /* Second step: set the capabilities. This will reduce * the capabilities to the minimum we need. */ - d = cap_dup(context->capabilities); - if (!d) - return -errno; + if (context->capabilities) { + _cleanup_cap_free_ cap_t d = NULL; + static const cap_value_t bits[] = { + CAP_SETUID, /* Necessary so that we can run setresuid() below */ + CAP_SETPCAP /* Necessary so that we can set PR_SET_SECUREBITS later on */ + }; - if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 || - cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0) - return -errno; + d = cap_dup(context->capabilities); + if (!d) + return -errno; - if (cap_set_proc(d) < 0) - return -errno; + if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 || + cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0) + return -errno; + + if (cap_set_proc(d) < 0) + return -errno; + } } /* Third step: actually set the uids */ @@ -1856,6 +1859,8 @@ static int exec_child( if (params->apply_permissions) { + int secure_bits = context->secure_bits; + for (i = 0; i < _RLIMIT_MAX; i++) { if (!context->rlimit[i]) continue; @@ -1866,28 +1871,71 @@ static int exec_child( } } - if (context->capability_bounding_set_drop) { - r = capability_bounding_set_drop(context->capability_bounding_set_drop, false); + if (!cap_test_all(context->capability_bounding_set)) { + r = capability_bounding_set_drop(context->capability_bounding_set, false); if (r < 0) { *exit_status = EXIT_CAPABILITIES; return r; } } + /* This is done before enforce_user, but ambient set + * does not survive over setresuid() if keep_caps is not set. */ + if (context->capability_ambient_set != 0) { + r = capability_ambient_set_apply(context->capability_ambient_set, true); + if (r < 0) { + *exit_status = EXIT_CAPABILITIES; + return r; + } + + if (context->capabilities) { + + /* The capabilities in ambient set need to be also in the inherited + * set. If they aren't, trying to get them will fail. Add the ambient + * set inherited capabilities to the capability set in the context. + * This is needed because if capabilities are set (using "Capabilities=" + * keyword), they will override whatever we set now. */ + + r = capability_update_inherited_set(context->capabilities, context->capability_ambient_set); + if (r < 0) { + *exit_status = EXIT_CAPABILITIES; + return r; + } + } + } + if (context->user) { r = enforce_user(context, uid); if (r < 0) { *exit_status = EXIT_USER; return r; } + if (context->capability_ambient_set != 0) { + + /* Fix the ambient capabilities after user change. */ + r = capability_ambient_set_apply(context->capability_ambient_set, false); + if (r < 0) { + *exit_status = EXIT_CAPABILITIES; + return r; + } + + /* If we were asked to change user and ambient capabilities + * were requested, we had to add keep-caps to the securebits + * so that we would maintain the inherited capability set + * through the setresuid(). Make sure that the bit is added + * also to the context secure_bits so that we don't try to + * drop the bit away next. */ + + secure_bits |= 1<<SECURE_KEEP_CAPS; + } } /* PR_GET_SECUREBITS is not privileged, while * PR_SET_SECUREBITS is. So to suppress * potential EPERMs we'll try not to call * PR_SET_SECUREBITS unless necessary. */ - if (prctl(PR_GET_SECUREBITS) != context->secure_bits) - if (prctl(PR_SET_SECUREBITS, context->secure_bits) < 0) { + if (prctl(PR_GET_SECUREBITS) != secure_bits) + if (prctl(PR_SET_SECUREBITS, secure_bits) < 0) { *exit_status = EXIT_SECUREBITS; return -errno; } @@ -2114,6 +2162,7 @@ void exec_context_init(ExecContext *c) { c->timer_slack_nsec = NSEC_INFINITY; c->personality = PERSONALITY_INVALID; c->runtime_directory_mode = 0755; + c->capability_bounding_set = CAP_ALL; } void exec_context_done(ExecContext *c) { @@ -2270,7 +2319,7 @@ int exec_context_load_environment(Unit *unit, const ExecContext *c, char ***l) { continue; strv_free(r); - return errno ? -errno : -EINVAL; + return errno > 0 ? -errno : -EINVAL; } count = pglob.gl_pathc; if (count == 0) { @@ -2517,12 +2566,23 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { (c->secure_bits & 1<<SECURE_NOROOT) ? " noroot" : "", (c->secure_bits & 1<<SECURE_NOROOT_LOCKED) ? "noroot-locked" : ""); - if (c->capability_bounding_set_drop) { + if (c->capability_bounding_set != CAP_ALL) { unsigned long l; fprintf(f, "%sCapabilityBoundingSet:", prefix); for (l = 0; l <= cap_last_cap(); l++) - if (!(c->capability_bounding_set_drop & ((uint64_t) 1ULL << (uint64_t) l))) + if (c->capability_bounding_set & (UINT64_C(1) << l)) + fprintf(f, " %s", strna(capability_to_name(l))); + + fputs("\n", f); + } + + if (c->capability_ambient_set != 0) { + unsigned long l; + fprintf(f, "%sAmbientCapabilities:", prefix); + + for (l = 0; l <= cap_last_cap(); l++) + if (c->capability_ambient_set & (UINT64_C(1) << l)) fprintf(f, " %s", strna(capability_to_name(l))); fputs("\n", f); @@ -2623,7 +2683,7 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { fputc('\n', f); } - if (c->syscall_errno != 0) + if (c->syscall_errno > 0) fprintf(f, "%sSystemCallErrorNumber: %s\n", prefix, strna(errno_to_name(c->syscall_errno))); diff --git a/src/core/execute.h b/src/core/execute.h index be5be9f531..8649620830 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -155,7 +155,9 @@ struct ExecContext { char **read_write_dirs, **read_only_dirs, **inaccessible_dirs; unsigned long mount_flags; - uint64_t capability_bounding_set_drop; + uint64_t capability_bounding_set; + + uint64_t capability_ambient_set; cap_t capabilities; int secure_bits; diff --git a/src/core/job.c b/src/core/job.c index 9654590635..274c554da9 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -35,6 +35,7 @@ #include "parse-util.h" #include "set.h" #include "special.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -754,7 +755,7 @@ static void job_log_status_message(Unit *u, JobType t, JobResult result) { return; DISABLE_WARNING_FORMAT_NONLITERAL; - snprintf(buf, sizeof(buf), format, unit_description(u)); + xsprintf(buf, format, unit_description(u)); REENABLE_WARNING; switch (t) { diff --git a/src/core/load-dropin.c b/src/core/load-dropin.c index 3fa66f91aa..569632e13b 100644 --- a/src/core/load-dropin.c +++ b/src/core/load-dropin.c @@ -65,6 +65,7 @@ int unit_load_dropin(Unit *u) { } } + u->dropin_paths = strv_free(u->dropin_paths); r = unit_find_dropin_paths(u, &u->dropin_paths); if (r <= 0) return 0; diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 0408b9a829..29ab1b6b9e 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -47,7 +47,8 @@ $1.SyslogLevel, config_parse_log_level, 0, $1.SyslogLevelPrefix, config_parse_bool, 0, offsetof($1, exec_context.syslog_level_prefix) $1.Capabilities, config_parse_exec_capabilities, 0, offsetof($1, exec_context) $1.SecureBits, config_parse_exec_secure_bits, 0, offsetof($1, exec_context) -$1.CapabilityBoundingSet, config_parse_bounding_set, 0, offsetof($1, exec_context.capability_bounding_set_drop) +$1.CapabilityBoundingSet, config_parse_capability_set, 0, offsetof($1, exec_context.capability_bounding_set) +$1.AmbientCapabilities, config_parse_capability_set, 0, offsetof($1, exec_context.capability_ambient_set) $1.TimerSlackNSec, config_parse_nsec, 0, offsetof($1, exec_context.timer_slack_nsec) $1.NoNewPrivileges, config_parse_no_new_privileges, 0, offsetof($1, exec_context) m4_ifdef(`HAVE_SECCOMP', diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index cb553e1252..d3880b4e3c 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -38,6 +38,7 @@ #include "bus-internal.h" #include "bus-util.h" #include "cap-list.h" +#include "capability-util.h" #include "cgroup.h" #include "conf-parser.h" #include "cpu-set-util.h" @@ -1024,7 +1025,7 @@ int config_parse_exec_secure_bits(const char *unit, return 0; } -int config_parse_bounding_set( +int config_parse_capability_set( const char *unit, const char *filename, unsigned line, @@ -1036,8 +1037,8 @@ int config_parse_bounding_set( void *data, void *userdata) { - uint64_t *capability_bounding_set_drop = data; - uint64_t capability_bounding_set, sum = 0; + uint64_t *capability_set = data; + uint64_t sum = 0, initial = 0; bool invert = false; const char *p; @@ -1051,10 +1052,9 @@ int config_parse_bounding_set( rvalue++; } - /* Note that we store this inverted internally, since the - * kernel wants it like this. But we actually expose it - * non-inverted everywhere to have a fully normalized - * interface. */ + if (strcmp(lvalue, "CapabilityBoundingSet") == 0) + initial = CAP_ALL; /* initialized to all bits on */ + /* else "AmbientCapabilities" initialized to all bits off */ p = rvalue; for (;;) { @@ -1073,18 +1073,21 @@ int config_parse_bounding_set( cap = capability_from_name(word); if (cap < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse capability in bounding set, ignoring: %s", word); + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse capability in bounding/ambient set, ignoring: %s", word); continue; } sum |= ((uint64_t) UINT64_C(1)) << (uint64_t) cap; } - capability_bounding_set = invert ? ~sum : sum; - if (*capability_bounding_set_drop != 0 && capability_bounding_set != 0) - *capability_bounding_set_drop = ~(~*capability_bounding_set_drop | capability_bounding_set); + sum = invert ? ~sum : sum; + + if (sum == 0 || *capability_set == initial) + /* "" or uninitialized data -> replace */ + *capability_set = sum; else - *capability_bounding_set_drop = ~capability_bounding_set; + /* previous data -> merge */ + *capability_set |= sum; return 0; } @@ -4002,7 +4005,7 @@ void unit_dump_config_items(FILE *f) { { config_parse_log_level, "LEVEL" }, { config_parse_exec_capabilities, "CAPABILITIES" }, { config_parse_exec_secure_bits, "SECUREBITS" }, - { config_parse_bounding_set, "BOUNDINGSET" }, + { config_parse_capability_set, "BOUNDINGSET" }, { config_parse_limit, "LIMIT" }, { config_parse_unit_deps, "UNIT [...]" }, { config_parse_exec, "PATH [ARGUMENT [...]]" }, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index a451fc164a..f0027a6b43 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -56,7 +56,7 @@ int config_parse_exec_cpu_sched_prio(const char *unit, const char *filename, uns int config_parse_exec_cpu_affinity(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_exec_capabilities(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_exec_secure_bits(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_bounding_set(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_capability_set(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_limit(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_bytes_limit(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_sec_limit(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); diff --git a/src/core/machine-id-setup.c b/src/core/machine-id-setup.c index 145ba2a28d..09b0449c80 100644 --- a/src/core/machine-id-setup.c +++ b/src/core/machine-id-setup.c @@ -198,7 +198,7 @@ static int generate_machine_id(char id[34], const char *root) { return 0; } -int machine_id_setup(const char *root) { +int machine_id_setup(const char *root, sd_id128_t machine_id) { const char *etc_machine_id, *run_machine_id; _cleanup_close_ int fd = -1; bool writable = true; @@ -248,15 +248,22 @@ int machine_id_setup(const char *root) { } } - if (read_machine_id(fd, id) >= 0) - return 0; + /* A machine id argument overrides all other machined-ids */ + if (!sd_id128_is_null(machine_id)) { + sd_id128_to_string(machine_id, id); + id[32] = '\n'; + id[33] = 0; + } else { + if (read_machine_id(fd, id) >= 0) + return 0; - /* Hmm, so, the id currently stored is not useful, then let's - * generate one */ + /* Hmm, so, the id currently stored is not useful, then let's + * generate one */ - r = generate_machine_id(id, root); - if (r < 0) - return r; + r = generate_machine_id(id, root); + if (r < 0) + return r; + } if (writable) if (write_machine_id(fd, id) >= 0) diff --git a/src/core/machine-id-setup.h b/src/core/machine-id-setup.h index f7707c3bf9..a2168a8d4a 100644 --- a/src/core/machine-id-setup.h +++ b/src/core/machine-id-setup.h @@ -22,4 +22,4 @@ ***/ int machine_id_commit(const char *root); -int machine_id_setup(const char *root); +int machine_id_setup(const char *root, sd_id128_t machine_id); diff --git a/src/core/main.c b/src/core/main.c index f9de54028e..2f9094f03a 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -117,7 +117,7 @@ static usec_t arg_runtime_watchdog = 0; static usec_t arg_shutdown_watchdog = 10 * USEC_PER_MINUTE; static char **arg_default_environment = NULL; static struct rlimit *arg_default_rlimit[_RLIMIT_MAX] = {}; -static uint64_t arg_capability_bounding_set_drop = 0; +static uint64_t arg_capability_bounding_set = CAP_ALL; static nsec_t arg_timer_slack_nsec = NSEC_INFINITY; static usec_t arg_default_timer_accuracy_usec = 1 * USEC_PER_MINUTE; static Set* arg_syscall_archs = NULL; @@ -127,6 +127,7 @@ static bool arg_default_blockio_accounting = false; static bool arg_default_memory_accounting = false; static bool arg_default_tasks_accounting = true; static uint64_t arg_default_tasks_max = UINT64_C(512); +static sd_id128_t arg_machine_id = {}; static void pager_open_if_enabled(void) { @@ -300,6 +301,17 @@ static int parse_crash_chvt(const char *value) { return 0; } +static int set_machine_id(const char *m) { + + if (sd_id128_from_string(m, &arg_machine_id) < 0) + return -EINVAL; + + if (sd_id128_is_null(arg_machine_id)) + return -EINVAL; + + return 0; +} + static int parse_proc_cmdline_item(const char *key, const char *value) { int r; @@ -388,6 +400,12 @@ static int parse_proc_cmdline_item(const char *key, const char *value) { } else log_warning("Environment variable name '%s' is not valid. Ignoring.", value); + } else if (streq(key, "systemd.machine_id") && value) { + + r = set_machine_id(value); + if (r < 0) + log_warning("MachineID '%s' is not valid. Ignoring.", value); + } else if (streq(key, "quiet") && !value) { if (arg_show_status == _SHOW_STATUS_UNSET) @@ -644,7 +662,7 @@ static int parse_config_file(void) { { "Manager", "JoinControllers", config_parse_join_controllers, 0, &arg_join_controllers }, { "Manager", "RuntimeWatchdogSec", config_parse_sec, 0, &arg_runtime_watchdog }, { "Manager", "ShutdownWatchdogSec", config_parse_sec, 0, &arg_shutdown_watchdog }, - { "Manager", "CapabilityBoundingSet", config_parse_bounding_set, 0, &arg_capability_bounding_set_drop }, + { "Manager", "CapabilityBoundingSet", config_parse_capability_set, 0, &arg_capability_bounding_set }, #ifdef HAVE_SECCOMP { "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs }, #endif @@ -743,7 +761,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_DESERIALIZE, ARG_SWITCHED_ROOT, ARG_DEFAULT_STD_OUTPUT, - ARG_DEFAULT_STD_ERROR + ARG_DEFAULT_STD_ERROR, + ARG_MACHINE_ID }; static const struct option options[] = { @@ -769,6 +788,7 @@ static int parse_argv(int argc, char *argv[]) { { "switched-root", no_argument, NULL, ARG_SWITCHED_ROOT }, { "default-standard-output", required_argument, NULL, ARG_DEFAULT_STD_OUTPUT, }, { "default-standard-error", required_argument, NULL, ARG_DEFAULT_STD_ERROR, }, + { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, {} }; @@ -964,6 +984,14 @@ static int parse_argv(int argc, char *argv[]) { arg_switched_root = true; break; + case ARG_MACHINE_ID: + r = set_machine_id(optarg); + if (r < 0) { + log_error("MachineID '%s' is not valid.", optarg); + return r; + } + break; + case 'h': arg_action = ACTION_HELP; if (arg_no_pager < 0) @@ -1617,7 +1645,7 @@ int main(int argc, char *argv[]) { status_welcome(); hostname_setup(); - machine_id_setup(NULL); + machine_id_setup(NULL, arg_machine_id); loopback_setup(); bump_unix_max_dgram_qlen(); @@ -1631,14 +1659,14 @@ int main(int argc, char *argv[]) { if (prctl(PR_SET_TIMERSLACK, arg_timer_slack_nsec) < 0) log_error_errno(errno, "Failed to adjust timer slack: %m"); - if (arg_capability_bounding_set_drop) { - r = capability_bounding_set_drop_usermode(arg_capability_bounding_set_drop); + if (!cap_test_all(arg_capability_bounding_set)) { + r = capability_bounding_set_drop_usermode(arg_capability_bounding_set); if (r < 0) { log_emergency_errno(r, "Failed to drop capability bounding set of usermode helpers: %m"); error_message = "Failed to drop capability bounding set of usermode helpers"; goto finish; } - r = capability_bounding_set_drop(arg_capability_bounding_set_drop, true); + r = capability_bounding_set_drop(arg_capability_bounding_set, true); if (r < 0) { log_emergency_errno(r, "Failed to drop capability bounding set: %m"); error_message = "Failed to drop capability bounding set"; diff --git a/src/core/manager.c b/src/core/manager.c index 711b0cdcee..a83a8b013a 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -233,7 +233,7 @@ static int have_ask_password(void) { errno = 0; de = readdir(dir); - if (!de && errno != 0) + if (!de && errno > 0) return -errno; if (!de) return false; @@ -989,7 +989,7 @@ Manager* manager_free(Manager *m) { free(m->switch_root_init); for (i = 0; i < _RLIMIT_MAX; i++) - free(m->rlimit[i]); + m->rlimit[i] = mfree(m->rlimit[i]); assert(hashmap_isempty(m->units_requiring_mounts_for)); hashmap_free(m->units_requiring_mounts_for); @@ -2923,6 +2923,8 @@ int manager_set_default_rlimits(Manager *m, struct rlimit **default_rlimit) { assert(m); for (i = 0; i < _RLIMIT_MAX; i++) { + m->rlimit[i] = mfree(m->rlimit[i]); + if (!default_rlimit[i]) continue; diff --git a/src/core/mount-setup.c b/src/core/mount-setup.c index 2b8d590ed1..d73b319c5d 100644 --- a/src/core/mount-setup.c +++ b/src/core/mount-setup.c @@ -304,13 +304,18 @@ int mount_cgroup_controllers(char ***join_controllers) { return log_oom(); r = symlink(options, t); - if (r < 0 && errno != EEXIST) - return log_error_errno(errno, "Failed to create symlink %s: %m", t); + if (r >= 0) { #ifdef SMACK_RUN_LABEL - r = mac_smack_copy(t, options); - if (r < 0 && r != -EOPNOTSUPP) - return log_error_errno(r, "Failed to copy smack label from %s to %s: %m", options, t); + _cleanup_free_ char *src; + src = strappend("/sys/fs/cgroup/", options); + if (!src) + return log_oom(); + r = mac_smack_copy(t, src); + if (r < 0 && r != -EOPNOTSUPP) + return log_error_errno(r, "Failed to copy smack label from %s to %s: %m", src, t); #endif + } else if (errno != EEXIST) + return log_error_errno(errno, "Failed to create symlink %s: %m", t); } } } diff --git a/src/core/smack-setup.c b/src/core/smack-setup.c index 0661ff9ecd..c9374ca0e8 100644 --- a/src/core/smack-setup.c +++ b/src/core/smack-setup.c @@ -197,6 +197,75 @@ static int write_cipso2_rules(const char* srcdir) { return r; } +static int write_netlabel_rules(const char* srcdir) { + _cleanup_fclose_ FILE *dst = NULL; + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *entry; + char buf[NAME_MAX]; + int dfd = -1; + int r = 0; + + dst = fopen("/sys/fs/smackfs/netlabel", "we"); + if (!dst) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to open /sys/fs/smackfs/netlabel: %m"); + return -errno; /* negative error */ + } + + /* write rules to dst from every file in the directory */ + dir = opendir(srcdir); + if (!dir) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to opendir %s: %m", srcdir); + return errno; /* positive on purpose */ + } + + dfd = dirfd(dir); + assert(dfd >= 0); + + FOREACH_DIRENT(entry, dir, return 0) { + int fd; + _cleanup_fclose_ FILE *policy = NULL; + + fd = openat(dfd, entry->d_name, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + if (r == 0) + r = -errno; + log_warning_errno(errno, "Failed to open %s: %m", entry->d_name); + continue; + } + + policy = fdopen(fd, "re"); + if (!policy) { + if (r == 0) + r = -errno; + safe_close(fd); + log_error_errno(errno, "Failed to open %s: %m", entry->d_name); + continue; + } + + /* load2 write rules in the kernel require a line buffered stream */ + FOREACH_LINE(buf, policy, + log_error_errno(errno, "Failed to read line from %s: %m", + entry->d_name)) { + if (!fputs(buf, dst)) { + if (r == 0) + r = -EINVAL; + log_error_errno(errno, "Failed to write line to /sys/fs/smackfs/netlabel"); + break; + } + if (fflush(dst)) { + if (r == 0) + r = -errno; + log_error_errno(errno, "Failed to flush writes to /sys/fs/smackfs/netlabel: %m"); + break; + } + } + } + + return r; +} + #endif int mac_smack_setup(bool *loaded_policy) { @@ -225,8 +294,18 @@ int mac_smack_setup(bool *loaded_policy) { #ifdef SMACK_RUN_LABEL r = write_string_file("/proc/self/attr/current", SMACK_RUN_LABEL, 0); - if (r) - log_warning_errno(r, "Failed to set SMACK label \"%s\" on self: %m", SMACK_RUN_LABEL); + if (r < 0) + log_warning_errno(r, "Failed to set SMACK label \"" SMACK_RUN_LABEL "\" on self: %m"); + r = write_string_file("/sys/fs/smackfs/ambient", SMACK_RUN_LABEL, 0); + if (r < 0) + log_warning_errno(r, "Failed to set SMACK ambient label \"" SMACK_RUN_LABEL "\": %m"); + r = write_string_file("/sys/fs/smackfs/netlabel", + "0.0.0.0/0 " SMACK_RUN_LABEL, 0); + if (r < 0) + log_warning_errno(r, "Failed to set SMACK netlabel rule \"0.0.0.0/0 " SMACK_RUN_LABEL "\": %m"); + r = write_string_file("/sys/fs/smackfs/netlabel", "127.0.0.1 -CIPSO", 0); + if (r < 0) + log_warning_errno(r, "Failed to set SMACK netlabel rule \"127.0.0.1 -CIPSO\": %m"); #endif r = write_cipso2_rules("/etc/smack/cipso.d/"); @@ -236,13 +315,29 @@ int mac_smack_setup(bool *loaded_policy) { return 0; case ENOENT: log_debug("Smack/CIPSO access rules directory '/etc/smack/cipso.d/' not found"); - return 0; + break; case 0: log_info("Successfully loaded Smack/CIPSO policies."); break; default: log_warning_errno(r, "Failed to load Smack/CIPSO access rules, ignoring: %m"); + break; + } + + r = write_netlabel_rules("/etc/smack/netlabel.d/"); + switch(r) { + case -ENOENT: + log_debug("Smack/CIPSO is not enabled in the kernel."); return 0; + case ENOENT: + log_debug("Smack network host rules directory '/etc/smack/netlabel.d/' not found"); + break; + case 0: + log_info("Successfully loaded Smack network host rules."); + break; + default: + log_warning_errno(r, "Failed to load Smack network host rules: %m, ignoring."); + break; } *loaded_policy = true; diff --git a/src/core/transaction.c b/src/core/transaction.c index 2f163190e9..8b0ed74643 100644 --- a/src/core/transaction.c +++ b/src/core/transaction.c @@ -27,6 +27,7 @@ #include "bus-error.h" #include "terminal-util.h" #include "transaction.h" +#include "dbus-unit.h" static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies); @@ -860,30 +861,12 @@ int transaction_add_job_and_dependencies( if (!IN_SET(unit->load_state, UNIT_LOADED, UNIT_ERROR, UNIT_NOT_FOUND, UNIT_MASKED)) return sd_bus_error_setf(e, BUS_ERROR_LOAD_FAILED, "Unit %s is not loaded properly.", unit->id); - if (type != JOB_STOP && unit->load_state == UNIT_ERROR) { - if (unit->load_error == -ENOENT || unit->manager->test_run) - return sd_bus_error_setf(e, BUS_ERROR_LOAD_FAILED, - "Unit %s failed to load: %s.", - unit->id, - strerror(-unit->load_error)); - else - return sd_bus_error_setf(e, BUS_ERROR_LOAD_FAILED, - "Unit %s failed to load: %s. " - "See system logs and 'systemctl status %s' for details.", - unit->id, - strerror(-unit->load_error), - unit->id); + if (type != JOB_STOP) { + r = bus_unit_check_load_state(unit, e); + if (r < 0) + return r; } - if (type != JOB_STOP && unit->load_state == UNIT_NOT_FOUND) - return sd_bus_error_setf(e, BUS_ERROR_LOAD_FAILED, - "Unit %s failed to load: %s.", - unit->id, strerror(-unit->load_error)); - - if (type != JOB_STOP && unit->load_state == UNIT_MASKED) - return sd_bus_error_setf(e, BUS_ERROR_UNIT_MASKED, - "Unit %s is masked.", unit->id); - if (!unit_job_is_applicable(unit, type)) return sd_bus_error_setf(e, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, "Job type %s is not applicable for unit %s.", @@ -949,9 +932,10 @@ int transaction_add_job_and_dependencies( SET_FOREACH(dep, ret->unit->dependencies[UNIT_WANTS], i) { r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, false, false, false, ignore_order, e); if (r < 0) { + /* unit masked and unit not found are not considered as errors. */ log_unit_full(dep, - r == -EBADR /* unit masked */ ? LOG_DEBUG : LOG_WARNING, r, - "Cannot add dependency job, ignoring: %s", + r == -EBADR || r == -ENOENT ? LOG_DEBUG : LOG_WARNING, + r, "Cannot add dependency job, ignoring: %s", bus_error_message(e, r)); sd_bus_error_free(e); } diff --git a/src/core/unit.c b/src/core/unit.c index f935b6a601..32267d95f5 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -51,6 +51,7 @@ #include "set.h" #include "special.h" #include "stat-util.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "unit-name.h" @@ -1412,7 +1413,7 @@ static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { format = unit_get_status_message_format(u, t); DISABLE_WARNING_FORMAT_NONLITERAL; - snprintf(buf, sizeof(buf), format, unit_description(u)); + xsprintf(buf, format, unit_description(u)); REENABLE_WARNING; mid = t == JOB_START ? SD_MESSAGE_UNIT_STARTING : @@ -3119,7 +3120,7 @@ int unit_kill_common( killed = true; } - if (r == 0 && !killed && IN_SET(who, KILL_ALL_FAIL, KILL_CONTROL_FAIL, KILL_ALL_FAIL)) + if (r == 0 && !killed && IN_SET(who, KILL_ALL_FAIL, KILL_CONTROL_FAIL)) return -ESRCH; return r; @@ -3231,7 +3232,7 @@ int unit_patch_contexts(Unit *u) { ec->no_new_privileges = true; if (ec->private_devices) - ec->capability_bounding_set_drop |= (uint64_t) 1ULL << (uint64_t) CAP_MKNOD; + ec->capability_bounding_set &= ~(UINT64_C(1) << CAP_MKNOD); } cc = unit_get_cgroup_context(u); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 469ee7af68..cc5e9741fe 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -502,7 +502,7 @@ static int write_root_shadow(const char *path, const struct spwd *p) { errno = 0; if (putspent(p, f) != 0) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; return fflush_and_check(f); } diff --git a/src/getty-generator/getty-generator.c b/src/getty-generator/getty-generator.c index 03df7365b5..bddc0c441a 100644 --- a/src/getty-generator/getty-generator.c +++ b/src/getty-generator/getty-generator.c @@ -112,7 +112,7 @@ static int verify_tty(const char *name) { errno = 0; if (isatty(fd) <= 0) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; return 0; } diff --git a/src/import/aufs-util.c b/src/import/aufs-util.c index 82f519958c..b44dbb14ea 100644 --- a/src/import/aufs-util.c +++ b/src/import/aufs-util.c @@ -69,7 +69,7 @@ int aufs_resolve(const char *path) { errno = 0; r = nftw(path, nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL); if (r == FTW_STOP) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; return 0; } diff --git a/src/import/import-common.c b/src/import/import-common.c index a8551ca9e8..8a48bd7bf9 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -134,7 +134,7 @@ int import_fork_tar_x(const char *path, pid_t *ret) { if (unshare(CLONE_NEWNET) < 0) log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); - r = capability_bounding_set_drop(~retain, true); + r = capability_bounding_set_drop(retain, true); if (r < 0) log_error_errno(r, "Failed to drop capabilities, ignoring: %m"); @@ -208,7 +208,7 @@ int import_fork_tar_c(const char *path, pid_t *ret) { if (unshare(CLONE_NEWNET) < 0) log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); - r = capability_bounding_set_drop(~retain, true); + r = capability_bounding_set_drop(retain, true); if (r < 0) log_error_errno(r, "Failed to drop capabilities, ignoring: %m"); diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index 006791a542..f5fe165fa3 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -45,6 +45,8 @@ #include "sigbus.h" #include "util.h" +#define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC) + static char *arg_key_pem = NULL; static char *arg_cert_pem = NULL; static char *arg_trust_pem = NULL; @@ -181,11 +183,13 @@ static ssize_t request_reader_entries( } else if (r == 0) { if (m->follow) { - r = sd_journal_wait(m->journal, (uint64_t) -1); + r = sd_journal_wait(m->journal, (uint64_t) JOURNAL_WAIT_TIMEOUT); if (r < 0) { log_error_errno(r, "Couldn't wait for journal event: %m"); return MHD_CONTENT_READER_END_WITH_ERROR; } + if (r == SD_JOURNAL_NOP) + break; continue; } @@ -241,6 +245,8 @@ static ssize_t request_reader_entries( } n = m->size - pos; + if (n < 1) + return 0; if (n > max) n = max; @@ -694,7 +700,7 @@ static int request_handler_file( if (fstat(fd, &st) < 0) return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n"); - response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0); + response = MHD_create_response_from_fd_at_offset64(st.st_size, fd, 0); if (!response) return respond_oom(connection); @@ -834,7 +840,7 @@ static int request_handler( assert(method); if (!streq(method, "GET")) - return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE, + return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, "Unsupported method.\n"); diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c index 3f93e85232..2126606661 100644 --- a/src/journal-remote/journal-remote.c +++ b/src/journal-remote/journal-remote.c @@ -587,7 +587,7 @@ static int request_handler( *connection_cls); if (!streq(method, "POST")) - return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE, + return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, "Unsupported method.\n"); if (!streq(url, "/upload")) diff --git a/src/journal-remote/microhttpd-util.h b/src/journal-remote/microhttpd-util.h index 3e8c4fa6d1..cba57403a3 100644 --- a/src/journal-remote/microhttpd-util.h +++ b/src/journal-remote/microhttpd-util.h @@ -26,6 +26,15 @@ #include "macro.h" +/* Compatiblity with libmicrohttpd < 0.9.38 */ +#ifndef MHD_HTTP_NOT_ACCEPTABLE +#define MHD_HTTP_NOT_ACCEPTABLE MHD_HTTP_METHOD_NOT_ACCEPTABLE +#endif + +#if MHD_VERSION < 0x00094203 +#define MHD_create_response_from_fd_at_offset64 MHD_create_response_from_fd_at_offset +#endif + void microhttpd_logger(void *arg, const char *fmt, va_list ap) _printf_(2, 0); /* respond_oom() must be usable with return, hence this form. */ diff --git a/src/journal/compress.c b/src/journal/compress.c index 1828165894..78935fee74 100644 --- a/src/journal/compress.c +++ b/src/journal/compress.c @@ -612,80 +612,8 @@ int decompress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { #endif } +int decompress_stream_lz4(int in, int out, uint64_t max_bytes) { #ifdef HAVE_LZ4 -static int decompress_stream_lz4_v1(int fdf, int fdt, uint64_t max_bytes) { - - _cleanup_free_ char *buf = NULL, *out = NULL; - size_t buf_size = 0; - LZ4_streamDecode_t lz4_data = {}; - le32_t header; - size_t total_in = sizeof(header), total_out = 0; - - assert(fdf >= 0); - assert(fdt >= 0); - - out = malloc(4*LZ4_BUFSIZE); - if (!out) - return -ENOMEM; - - for (;;) { - ssize_t m; - int r; - - r = loop_read_exact(fdf, &header, sizeof(header), false); - if (r < 0) - return r; - - m = le32toh(header); - if (m == 0) - break; - - /* We refuse to use a bigger decompression buffer than - * the one used for compression by 4 times. This means - * that compression buffer size can be enlarged 4 - * times. This can be changed, but old binaries might - * not accept buffers compressed by newer binaries then. - */ - if (m > LZ4_COMPRESSBOUND(LZ4_BUFSIZE * 4)) { - log_debug("Compressed stream block too big: %zd bytes", m); - return -ENOBUFS; - } - - total_in += sizeof(header) + m; - - if (!GREEDY_REALLOC(buf, buf_size, m)) - return -ENOMEM; - - r = loop_read_exact(fdf, buf, m, false); - if (r < 0) - return r; - - r = LZ4_decompress_safe_continue(&lz4_data, buf, out, m, 4*LZ4_BUFSIZE); - if (r <= 0) { - log_debug("LZ4 decompression failed (legacy format)."); - return -EBADMSG; - } - - total_out += r; - - if (max_bytes != (uint64_t) -1 && (uint64_t) total_out > max_bytes) { - log_debug("Decompressed stream longer than %" PRIu64 " bytes", max_bytes); - return -EFBIG; - } - - r = loop_write(fdt, out, r, false); - if (r < 0) - return r; - } - - log_debug("LZ4 decompression finished (legacy format, %zu -> %zu bytes, %.1f%%)", - total_in, total_out, - (double) total_out / total_in * 100); - - return 0; -} - -static int decompress_stream_lz4_v2(int in, int out, uint64_t max_bytes) { size_t c; _cleanup_(LZ4F_freeDecompressionContextp) LZ4F_decompressionContext_t ctx = NULL; _cleanup_free_ char *buf = NULL; @@ -739,17 +667,6 @@ static int decompress_stream_lz4_v2(int in, int out, uint64_t max_bytes) { cleanup: munmap(src, st.st_size); return r; -} -#endif - -int decompress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) { -#ifdef HAVE_LZ4 - int r; - - r = decompress_stream_lz4_v2(fdf, fdt, max_bytes); - if (r == -EBADMSG) - r = decompress_stream_lz4_v1(fdf, fdt, max_bytes); - return r; #else log_debug("Cannot decompress file. Compiled without LZ4 support."); return -EPROTONOSUPPORT; diff --git a/src/journal/coredump.c b/src/journal/coredump.c index f750ddfcbd..869c8fea03 100644 --- a/src/journal/coredump.c +++ b/src/journal/coredump.c @@ -527,7 +527,7 @@ static int compose_open_fds(pid_t pid, char **open_fds) { errno = 0; stream = safe_fclose(stream); - if (errno != 0) + if (errno > 0) return -errno; *open_fds = buffer; diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index d009b2e93b..db11421e7a 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -2336,7 +2336,7 @@ int main(int argc, char *argv[]) { flags = arg_all * OUTPUT_SHOW_ALL | arg_full * OUTPUT_FULL_WIDTH | - on_tty() * OUTPUT_COLOR | + colors_enabled() * OUTPUT_COLOR | arg_catalog * OUTPUT_CATALOG | arg_utc * OUTPUT_UTC; diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c index 371df5b37f..f80a6ebfe5 100644 --- a/src/journal/journald-native.c +++ b/src/journal/journald-native.c @@ -495,5 +495,9 @@ int server_open_native_socket(Server*s) { if (r < 0) return log_error_errno(r, "Failed to add native server fd to event loop: %m"); + r = sd_event_source_set_priority(s->native_event_source, SD_EVENT_PRIORITY_NORMAL+5); + if (r < 0) + return log_error_errno(r, "Failed to adjust native event source priority: %m"); + return 0; } diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index a8a9b72080..cfcc2c4302 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -1330,7 +1330,7 @@ static int server_parse_proc_cmdline(Server *s) { p = line; for(;;) { - _cleanup_free_ char *word; + _cleanup_free_ char *word = NULL; r = extract_first_word(&p, &word, NULL, 0); if (r < 0) diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c index 131fcdac42..90884b6929 100644 --- a/src/journal/journald-stream.c +++ b/src/journal/journald-stream.c @@ -733,7 +733,7 @@ int server_open_stdout_socket(Server *s) { if (r < 0) return log_error_errno(r, "Failed to add stdout server fd to event source: %m"); - r = sd_event_source_set_priority(s->stdout_event_source, SD_EVENT_PRIORITY_NORMAL+10); + r = sd_event_source_set_priority(s->stdout_event_source, SD_EVENT_PRIORITY_NORMAL+5); if (r < 0) return log_error_errno(r, "Failed to adjust priority of stdout server event source: %m"); diff --git a/src/journal/journald-syslog.c b/src/journal/journald-syslog.c index cfc50d889b..0be73088e2 100644 --- a/src/journal/journald-syslog.c +++ b/src/journal/journald-syslog.c @@ -326,7 +326,7 @@ void server_process_syslog_message( size_t label_len) { char syslog_priority[sizeof("PRIORITY=") + DECIMAL_STR_MAX(int)], - syslog_facility[sizeof("SYSLOG_FACILITY") + DECIMAL_STR_MAX(int)]; + syslog_facility[sizeof("SYSLOG_FACILITY=") + DECIMAL_STR_MAX(int)]; const char *message = NULL, *syslog_identifier = NULL, *syslog_pid = NULL; struct iovec iovec[N_IOVEC_META_FIELDS + 6]; unsigned n = 0; @@ -357,11 +357,11 @@ void server_process_syslog_message( IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=syslog"); - sprintf(syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK); + xsprintf(syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK); IOVEC_SET_STRING(iovec[n++], syslog_priority); if (priority & LOG_FACMASK) { - sprintf(syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)); + xsprintf(syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)); IOVEC_SET_STRING(iovec[n++], syslog_facility); } @@ -430,6 +430,10 @@ int server_open_syslog_socket(Server *s) { if (r < 0) return log_error_errno(r, "Failed to add syslog server fd to event loop: %m"); + r = sd_event_source_set_priority(s->syslog_event_source, SD_EVENT_PRIORITY_NORMAL+5); + if (r < 0) + return log_error_errno(r, "Failed to adjust syslog event source priority: %m"); + return 0; } diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index e875ba4986..6fb80dda7a 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -37,6 +37,7 @@ #include "in-addr-util.h" #include "network-internal.h" #include "parse-util.h" +#include "stdio-util.h" #include "string-util.h" #include "unaligned.h" @@ -839,7 +840,7 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { LIST_FOREACH(options, option, lease->private_options) { char key[strlen("OPTION_000")+1]; - snprintf(key, sizeof(key), "OPTION_%"PRIu8, option->tag); + xsprintf(key, "OPTION_%" PRIu8, option->tag); r = serialize_dhcp_option(f, key, option->data, option->length); if (r < 0) goto fail; diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index d8154f0587..0ee466b32a 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -1,3 +1,5 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + /*** This file is part of systemd. @@ -112,7 +114,7 @@ static NDiscPrefix *ndisc_prefix_unref(NDiscPrefix *prefix) { } static int ndisc_prefix_new(sd_ndisc *nd, NDiscPrefix **ret) { - _cleanup_free_ NDiscPrefix *prefix = NULL; + NDiscPrefix *prefix; assert(ret); @@ -125,8 +127,6 @@ static int ndisc_prefix_new(sd_ndisc *nd, NDiscPrefix **ret) { prefix->nd = nd; *ret = prefix; - prefix = NULL; - return 0; } @@ -314,7 +314,6 @@ static int ndisc_prefix_match(sd_ndisc *nd, const struct in6_addr *addr, LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) { if (prefix->valid_until < time_now) { prefix = ndisc_prefix_unref(prefix); - continue; } @@ -355,14 +354,13 @@ static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len, r = ndisc_prefix_match(nd, &prefix_opt->nd_opt_pi_prefix, prefix_opt->nd_opt_pi_prefix_len, &prefix); + if (r < 0) { + if (r != -EADDRNOTAVAIL) + return r; - if (r < 0 && r != -EADDRNOTAVAIL) - return r; - - /* if router advertisment prefix valid timeout is zero, the timeout - callback will be called immediately to clean up the prefix */ + /* if router advertisment prefix valid timeout is zero, the timeout + callback will be called immediately to clean up the prefix */ - if (r == -EADDRNOTAVAIL) { r = ndisc_prefix_new(nd, &prefix); if (r < 0) return r; @@ -373,9 +371,9 @@ static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len, sizeof(prefix->addr)); log_ndisc(nd, "New prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s", - SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr), - prefix->len, lifetime_valid, - format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC)); + SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr), + prefix->len, lifetime_valid, + format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC)); LIST_PREPEND(prefixes, nd->prefixes, prefix); @@ -386,17 +384,17 @@ static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len, prefixlen = MIN(prefix->len, prefix_opt->nd_opt_pi_prefix_len); log_ndisc(nd, "Prefix length mismatch %d/%d using %d", - prefix->len, - prefix_opt->nd_opt_pi_prefix_len, - prefixlen); + prefix->len, + prefix_opt->nd_opt_pi_prefix_len, + prefixlen); prefix->len = prefixlen; } log_ndisc(nd, "Update prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s", - SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr), - prefix->len, lifetime_valid, - format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC)); + SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr), + prefix->len, lifetime_valid, + format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC)); } r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now); @@ -450,7 +448,7 @@ static int ndisc_ra_parse(sd_ndisc *nd, struct nd_router_advert *ra, ssize_t len nd->mtu = MAX(mtu, IP6_MIN_MTU); log_ndisc(nd, "Router Advertisement link MTU %d using %d", - mtu, nd->mtu); + mtu, nd->mtu); } break; diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index 6e2594d001..9ddc9b5aaf 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -75,6 +75,11 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_ABORTED, ECANCELED), SD_BUS_ERROR_MAP(BUS_ERROR_CONNECTION_FAILURE, ECONNREFUSED), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SERVICE, EUNATCH), + SD_BUS_ERROR_MAP(BUS_ERROR_DNSSEC_FAILED, EHOSTUNREACH), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_TRUST_ANCHOR, EHOSTUNREACH), + SD_BUS_ERROR_MAP(BUS_ERROR_RR_TYPE_UNSUPPORTED, EOPNOTSUPP), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_LINK, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_LINK_BUSY, EBUSY), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_TRANSFER, ENXIO), SD_BUS_ERROR_MAP(BUS_ERROR_TRANSFER_IN_PROGRESS, EBUSY), diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index 9076993f01..e93b6ac448 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -74,6 +74,11 @@ #define BUS_ERROR_ABORTED "org.freedesktop.resolve1.Aborted" #define BUS_ERROR_CONNECTION_FAILURE "org.freedesktop.resolve1.ConnectionFailure" #define BUS_ERROR_NO_SUCH_SERVICE "org.freedesktop.resolve1.NoSuchService" +#define BUS_ERROR_DNSSEC_FAILED "org.freedesktop.resolve1.DnssecFailed" +#define BUS_ERROR_NO_TRUST_ANCHOR "org.freedesktop.resolve1.NoTrustAnchor" +#define BUS_ERROR_RR_TYPE_UNSUPPORTED "org.freedesktop.resolve1.ResourceRecordTypeUnsupported" +#define BUS_ERROR_NO_SUCH_LINK "org.freedesktop.resolve1.NoSuchLink" +#define BUS_ERROR_LINK_BUSY "org.freedesktop.resolve1.LinkBusy" #define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError." #define BUS_ERROR_NO_SUCH_TRANSFER "org.freedesktop.import1.NoSuchTransfer" diff --git a/src/libsystemd/sd-bus/bus-error.c b/src/libsystemd/sd-bus/bus-error.c index 404eaa3c89..c77eb5fd03 100644 --- a/src/libsystemd/sd-bus/bus-error.c +++ b/src/libsystemd/sd-bus/bus-error.c @@ -93,14 +93,14 @@ static int bus_error_name_to_errno(const char *name) { p = startswith(name, "System.Error."); if (p) { r = errno_from_name(p); - if (r <= 0) + if (r < 0) return EIO; return r; } - if (additional_error_maps) { - for (map = additional_error_maps; *map; map++) { + if (additional_error_maps) + for (map = additional_error_maps; *map; map++) for (m = *map;; m++) { /* For additional error maps the end marker is actually the end marker */ if (m->code == BUS_ERROR_MAP_END_MARKER) @@ -109,15 +109,13 @@ static int bus_error_name_to_errno(const char *name) { if (streq(m->name, name)) return m->code; } - } - } m = __start_BUS_ERROR_MAP; while (m < __stop_BUS_ERROR_MAP) { /* For magic ELF error maps, the end marker might * appear in the middle of things, since multiple maps * might appear in the same section. Hence, let's skip - * over it, but realign the pointer to the netx 8byte + * over it, but realign the pointer to the next 8 byte * boundary, which is the selected alignment for the * arrays. */ if (m->code == BUS_ERROR_MAP_END_MARKER) { @@ -258,25 +256,24 @@ int bus_error_setfv(sd_bus_error *e, const char *name, const char *format, va_li if (!name) return 0; - if (!e) - goto finish; - assert_return(!bus_error_is_dirty(e), -EINVAL); + if (e) { + assert_return(!bus_error_is_dirty(e), -EINVAL); - e->name = strdup(name); - if (!e->name) { - *e = BUS_ERROR_OOM; - return -ENOMEM; - } + e->name = strdup(name); + if (!e->name) { + *e = BUS_ERROR_OOM; + return -ENOMEM; + } - /* If we hit OOM on formatting the pretty message, we ignore - * this, since we at least managed to write the error name */ - if (format) - (void) vasprintf((char**) &e->message, format, ap); + /* If we hit OOM on formatting the pretty message, we ignore + * this, since we at least managed to write the error name */ + if (format) + (void) vasprintf((char**) &e->message, format, ap); - e->_need_free = 1; + e->_need_free = 1; + } -finish: return -bus_error_name_to_errno(name); } @@ -582,27 +579,29 @@ const char *bus_error_message(const sd_bus_error *e, int error) { return strerror(error); } +static bool map_ok(const sd_bus_error_map *map) { + for (; map->code != BUS_ERROR_MAP_END_MARKER; map++) + if (!map->name || map->code <=0) + return false; + return true; +} + _public_ int sd_bus_error_add_map(const sd_bus_error_map *map) { const sd_bus_error_map **maps = NULL; unsigned n = 0; assert_return(map, -EINVAL); + assert_return(map_ok(map), -EINVAL); - if (additional_error_maps) { - for (;; n++) { - if (additional_error_maps[n] == NULL) - break; - + if (additional_error_maps) + for (; additional_error_maps[n] != NULL; n++) if (additional_error_maps[n] == map) return 0; - } - } maps = realloc_multiply(additional_error_maps, sizeof(struct sd_bus_error_map*), n + 2); if (!maps) return -ENOMEM; - maps[n] = map; maps[n+1] = NULL; diff --git a/src/libsystemd/sd-bus/bus-kernel.c b/src/libsystemd/sd-bus/bus-kernel.c index 6c05444e9a..e7d6170eec 100644 --- a/src/libsystemd/sd-bus/bus-kernel.c +++ b/src/libsystemd/sd-bus/bus-kernel.c @@ -47,6 +47,7 @@ #include "formats-util.h" #include "memfd-util.h" #include "parse-util.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "user-util.h" @@ -269,8 +270,8 @@ static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) { struct bus_body_part *part; struct kdbus_item *d; const char *destination; - bool well_known; - uint64_t unique; + bool well_known = false; + uint64_t dst_id; size_t sz, dl; unsigned i; int r; @@ -287,13 +288,21 @@ static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) { destination = m->destination ?: m->destination_ptr; if (destination) { - r = bus_kernel_parse_unique_name(destination, &unique); + r = bus_kernel_parse_unique_name(destination, &dst_id); if (r < 0) return r; - - well_known = r == 0; + if (r == 0) { + well_known = true; + + /* verify_destination_id will usually be 0, which makes the kernel + * driver only look at the provided well-known name. Otherwise, + * the kernel will make sure the provided destination id matches + * the owner of the provided well-known-name, and fail if they + * differ. Currently, this is only needed for bus-proxyd. */ + dst_id = m->verify_destination_id; + } } else - well_known = false; + dst_id = KDBUS_DST_ID_BROADCAST; sz = offsetof(struct kdbus_msg, items); @@ -331,15 +340,7 @@ static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) { ((m->header->flags & BUS_MESSAGE_NO_AUTO_START) ? KDBUS_MSG_NO_AUTO_START : 0) | ((m->header->type == SD_BUS_MESSAGE_SIGNAL) ? KDBUS_MSG_SIGNAL : 0); - if (well_known) - /* verify_destination_id will usually be 0, which makes the kernel driver only look - * at the provided well-known name. Otherwise, the kernel will make sure the provided - * destination id matches the owner of the provided weel-known-name, and fail if they - * differ. Currently, this is only needed for bus-proxyd. */ - m->kdbus->dst_id = m->verify_destination_id; - else - m->kdbus->dst_id = destination ? unique : KDBUS_DST_ID_BROADCAST; - + m->kdbus->dst_id = dst_id; m->kdbus->payload_type = KDBUS_PAYLOAD_DBUS; m->kdbus->cookie = m->header->dbus2.cookie; m->kdbus->priority = m->priority; @@ -849,7 +850,8 @@ static int bus_kernel_make_message(sd_bus *bus, struct kdbus_msg *k) { if (k->src_id == KDBUS_SRC_ID_KERNEL) bus_message_set_sender_driver(bus, m); else { - snprintf(m->sender_buffer, sizeof(m->sender_buffer), ":1.%llu", (unsigned long long) k->src_id); + xsprintf(m->sender_buffer, ":1.%llu", + (unsigned long long)k->src_id); m->sender = m->creds.unique_name = m->sender_buffer; } @@ -860,7 +862,8 @@ static int bus_kernel_make_message(sd_bus *bus, struct kdbus_msg *k) { else if (k->dst_id == KDBUS_DST_ID_NAME) m->destination = bus->unique_name; /* fill in unique name if the well-known name is missing */ else { - snprintf(m->destination_buffer, sizeof(m->destination_buffer), ":1.%llu", (unsigned long long) k->dst_id); + xsprintf(m->destination_buffer, ":1.%llu", + (unsigned long long)k->dst_id); m->destination = m->destination_buffer; } diff --git a/src/libsystemd/sd-bus/test-bus-cleanup.c b/src/libsystemd/sd-bus/test-bus-cleanup.c index 1c3ccda364..cbc450fdb2 100644 --- a/src/libsystemd/sd-bus/test-bus-cleanup.c +++ b/src/libsystemd/sd-bus/test-bus-cleanup.c @@ -36,7 +36,7 @@ static void test_bus_new(void) { } static int test_bus_open(void) { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; r = sd_bus_open_system(&bus); @@ -59,8 +59,8 @@ static void test_bus_new_method_call(void) { printf("after message_new_method_call: refcount %u\n", REFCNT_GET(bus->n_ref)); - sd_bus_unref(bus); - printf("after bus_unref: refcount %u\n", m->n_ref); + sd_bus_flush_close_unref(bus); + printf("after bus_flush_close_unref: refcount %u\n", m->n_ref); } static void test_bus_new_signal(void) { @@ -73,8 +73,8 @@ static void test_bus_new_signal(void) { printf("after message_new_signal: refcount %u\n", REFCNT_GET(bus->n_ref)); - sd_bus_unref(bus); - printf("after bus_unref: refcount %u\n", m->n_ref); + sd_bus_flush_close_unref(bus); + printf("after bus_flush_close_unref: refcount %u\n", m->n_ref); } int main(int argc, char **argv) { diff --git a/src/libsystemd/sd-bus/test-bus-error.c b/src/libsystemd/sd-bus/test-bus-error.c index c52405463e..407fd14555 100644 --- a/src/libsystemd/sd-bus/test-bus-error.c +++ b/src/libsystemd/sd-bus/test-bus-error.c @@ -44,7 +44,15 @@ static void test_error(void) { assert_se(sd_bus_error_is_set(&error)); sd_bus_error_free(&error); + /* Check with no error */ assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_setf(&error, NULL, "yyy %i", -1) == 0); + assert_se(error.name == NULL); + assert_se(error.message == NULL); + assert_se(!sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND)); + assert_se(sd_bus_error_get_errno(&error) == 0); + assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_setf(&error, SD_BUS_ERROR_FILE_NOT_FOUND, "yyy %i", -1) == -ENOENT); assert_se(streq(error.name, SD_BUS_ERROR_FILE_NOT_FOUND)); assert_se(streq(error.message, "yyy -1")); @@ -112,6 +120,16 @@ static void test_error(void) { assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_IO_ERROR)); assert_se(sd_bus_error_get_errno(&error) == EIO); assert_se(sd_bus_error_is_set(&error)); + sd_bus_error_free(&error); + + /* Check with no error */ + assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_set_errnof(&error, 0, "Waldi %c", 'X') == 0); + assert_se(error.name == NULL); + assert_se(error.message == NULL); + assert_se(!sd_bus_error_has_name(&error, SD_BUS_ERROR_IO_ERROR)); + assert_se(sd_bus_error_get_errno(&error) == 0); + assert_se(!sd_bus_error_is_set(&error)); } extern const sd_bus_error_map __start_BUS_ERROR_MAP[]; @@ -167,6 +185,16 @@ static const sd_bus_error_map test_errors4[] = { SD_BUS_ERROR_MAP_END }; +static const sd_bus_error_map test_errors_bad1[] = { + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-1", 0), + SD_BUS_ERROR_MAP_END +}; + +static const sd_bus_error_map test_errors_bad2[] = { + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-1", -1), + SD_BUS_ERROR_MAP_END +}; + static void test_errno_mapping_custom(void) { assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error", NULL) == -5); assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-2", NULL) == -52); @@ -190,6 +218,9 @@ static void test_errno_mapping_custom(void) { assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-y", NULL) == -EIO); assert_se(sd_bus_error_set(NULL, BUS_ERROR_NO_SUCH_UNIT, NULL) == -ENOENT); + + assert_se(sd_bus_error_add_map(test_errors_bad1) == -EINVAL); + assert_se(sd_bus_error_add_map(test_errors_bad2) == -EINVAL); } int main(int argc, char *argv[]) { diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index f44054a7b5..9633e46ce0 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -494,7 +494,7 @@ static int handle_uevent_line(sd_device *device, const char *key, const char *va int device_read_uevent_file(sd_device *device) { _cleanup_free_ char *uevent = NULL; - const char *syspath, *key, *value, *major = NULL, *minor = NULL; + const char *syspath, *key = NULL, *value = NULL, *major = NULL, *minor = NULL; char *path; size_t uevent_len; unsigned i; diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 7fccbd1a71..11c7330b9b 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -225,6 +225,7 @@ struct sd_event { bool exit_requested:1; bool need_process_child:1; bool watchdog:1; + bool profile_delays:1; int exit_code; @@ -236,6 +237,9 @@ struct sd_event { unsigned n_sources; LIST_HEAD(sd_event_source, sources); + + usec_t last_run, last_log; + unsigned delays[sizeof(usec_t) * 8]; }; static void source_disconnect(sd_event_source *s); @@ -444,6 +448,11 @@ _public_ int sd_event_new(sd_event** ret) { goto fail; } + if (secure_getenv("SD_EVENT_PROFILE_DELAYS")) { + log_debug("Event loop profiling enabled. Logarithmic histogram of event loop iterations in the range 2^0 ... 2^63 us will be logged every 5s."); + e->profile_delays = true; + } + *ret = e; return 0; @@ -652,8 +661,10 @@ static int event_make_signal_data( d->priority = priority; r = hashmap_put(e->signal_data, &d->priority, d); - if (r < 0) + if (r < 0) { + free(d); return r; + } added = true; } @@ -2622,6 +2633,18 @@ _public_ int sd_event_dispatch(sd_event *e) { return 1; } +static void event_log_delays(sd_event *e) { + char b[ELEMENTSOF(e->delays) * DECIMAL_STR_MAX(unsigned) + 1]; + unsigned i; + int o; + + for (i = o = 0; i < ELEMENTSOF(e->delays); i++) { + o += snprintf(&b[o], sizeof(b) - o, "%u ", e->delays[i]); + e->delays[i] = 0; + } + log_debug("Event loop iterations: %.*s", o, b); +} + _public_ int sd_event_run(sd_event *e, uint64_t timeout) { int r; @@ -2630,11 +2653,30 @@ _public_ int sd_event_run(sd_event *e, uint64_t timeout) { assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); + if (e->profile_delays && e->last_run) { + usec_t this_run; + unsigned l; + + this_run = now(CLOCK_MONOTONIC); + + l = u64log2(this_run - e->last_run); + assert(l < sizeof(e->delays)); + e->delays[l]++; + + if (this_run - e->last_log >= 5*USEC_PER_SEC) { + event_log_delays(e); + e->last_log = this_run; + } + } + r = sd_event_prepare(e); if (r == 0) /* There was nothing? Then wait... */ r = sd_event_wait(e, timeout); + if (e->profile_delays) + e->last_run = now(CLOCK_MONOTONIC); + if (r > 0) { /* There's something now, then let's dispatch it */ r = sd_event_dispatch(e); @@ -2711,6 +2753,12 @@ _public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) { assert_return(e, -EINVAL); assert_return(usec, -EINVAL); assert_return(!event_pid_changed(e), -ECHILD); + assert_return(IN_SET(clock, + CLOCK_REALTIME, + CLOCK_REALTIME_ALARM, + CLOCK_MONOTONIC, + CLOCK_BOOTTIME, + CLOCK_BOOTTIME_ALARM), -EOPNOTSUPP); if (!dual_timestamp_is_set(&e->timestamp)) { /* Implicitly fall back to now() if we never ran @@ -2730,8 +2778,7 @@ _public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) { *usec = e->timestamp.monotonic; break; - case CLOCK_BOOTTIME: - case CLOCK_BOOTTIME_ALARM: + default: *usec = e->timestamp_boottime; break; } diff --git a/src/libsystemd/sd-event/test-event.c b/src/libsystemd/sd-event/test-event.c index 9417a8d1d1..c605b18ae9 100644 --- a/src/libsystemd/sd-event/test-event.c +++ b/src/libsystemd/sd-event/test-event.c @@ -264,6 +264,30 @@ static void test_basic(void) { safe_close_pair(k); } +static void test_sd_event_now(void) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + uint64_t event_now; + + assert_se(sd_event_new(&e) >= 0); + assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) > 0); + assert_se(sd_event_now(e, CLOCK_REALTIME, &event_now) > 0); + assert_se(sd_event_now(e, CLOCK_REALTIME_ALARM, &event_now) > 0); + assert_se(sd_event_now(e, CLOCK_BOOTTIME, &event_now) > 0); + assert_se(sd_event_now(e, CLOCK_BOOTTIME_ALARM, &event_now) > 0); + assert_se(sd_event_now(e, -1, &event_now) == -EOPNOTSUPP); + assert_se(sd_event_now(e, 900 /* arbitrary big number */, &event_now) == -EOPNOTSUPP); + + assert_se(sd_event_run(e, 0) == 0); + + assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) == 0); + assert_se(sd_event_now(e, CLOCK_REALTIME, &event_now) == 0); + assert_se(sd_event_now(e, CLOCK_REALTIME_ALARM, &event_now) == 0); + assert_se(sd_event_now(e, CLOCK_BOOTTIME, &event_now) == 0); + assert_se(sd_event_now(e, CLOCK_BOOTTIME_ALARM, &event_now) == 0); + assert_se(sd_event_now(e, -1, &event_now) == -EOPNOTSUPP); + assert_se(sd_event_now(e, 900 /* arbitrary big number */, &event_now) == -EOPNOTSUPP); +} + static int last_rtqueue_sigval = 0; static int n_rtqueue = 0; @@ -324,7 +348,11 @@ static void test_rtqueue(void) { int main(int argc, char *argv[]) { + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + test_basic(); + test_sd_event_now(); test_rtqueue(); return 0; diff --git a/src/libsystemd/sd-login/sd-login.c b/src/libsystemd/sd-login/sd-login.c index 4b46eeb533..ef240c3531 100644 --- a/src/libsystemd/sd-login/sd-login.c +++ b/src/libsystemd/sd-login/sd-login.c @@ -810,7 +810,7 @@ _public_ int sd_get_uids(uid_t **users) { errno = 0; de = readdir(d); - if (!de && errno != 0) + if (!de && errno > 0) return -errno; if (!de) diff --git a/src/libsystemd/sd-netlink/netlink-socket.c b/src/libsystemd/sd-netlink/netlink-socket.c index 2181201017..e95c99af0d 100644 --- a/src/libsystemd/sd-netlink/netlink-socket.c +++ b/src/libsystemd/sd-netlink/netlink-socket.c @@ -52,7 +52,7 @@ static int broadcast_groups_get(sd_netlink *nl) { int r; assert(nl); - assert(nl->fd > 0); + assert(nl->fd >= 0); r = getsockopt(nl->fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, NULL, &len); if (r < 0) { diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c index 135354e5f3..be4ab1373d 100644 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -96,15 +96,6 @@ static const NLType rtnl_link_info_data_macvlan_types[] = { [IFLA_MACVLAN_FLAGS] = { .type = NETLINK_TYPE_U16 }, }; -static const NLType rtnl_link_bridge_management_types[] = { - [IFLA_BRIDGE_FLAGS] = { .type = NETLINK_TYPE_U16 }, - [IFLA_BRIDGE_MODE] = { .type = NETLINK_TYPE_U16 }, -/* - [IFLA_BRIDGE_VLAN_INFO] = { .type = NETLINK_TYPE_BINARY, - .len = sizeof(struct bridge_vlan_info), }, -*/ -}; - static const NLType rtnl_link_info_data_bridge_types[] = { [IFLA_BR_FORWARD_DELAY] = { .type = NETLINK_TYPE_U32 }, [IFLA_BR_HELLO_TIME] = { .type = NETLINK_TYPE_U32 }, diff --git a/src/libsystemd/sd-network/sd-network.c b/src/libsystemd/sd-network/sd-network.c index efbceba83d..c1f5867ee4 100644 --- a/src/libsystemd/sd-network/sd-network.c +++ b/src/libsystemd/sd-network/sd-network.c @@ -99,17 +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) { +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(state, -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, "ADMIN_STATE", &s, NULL); + r = parse_env_file(p, NEWLINE, field, &s, NULL); if (r == -ENOENT) return -ENODATA; if (r < 0) @@ -117,82 +117,72 @@ _public_ int sd_network_link_get_setup_state(int ifindex, char **state) { if (isempty(s)) return -ENODATA; - *state = s; + *ret = s; s = NULL; return 0; } -_public_ int sd_network_link_get_network_file(int ifindex, char **filename) { - _cleanup_free_ char *s = NULL, *p = NULL; +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; assert_return(ifindex > 0, -EINVAL); - assert_return(filename, -EINVAL); + assert_return(ret, -EINVAL); if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) return -ENOMEM; - r = parse_env_file(p, NEWLINE, "NETWORK_FILE", &s, NULL); + r = parse_env_file(p, NEWLINE, key, &s, NULL); if (r == -ENOENT) return -ENODATA; if (r < 0) return r; - if (isempty(s)) - return -ENODATA; - - *filename = s; - s = NULL; + if (isempty(s)) { + *ret = NULL; + return 0; + } - return 0; -} + a = strv_split(s, " "); + if (!a) + return -ENOMEM; -_public_ int sd_network_link_get_operational_state(int ifindex, char **state) { - _cleanup_free_ char *s = NULL, *p = NULL; - int r; + strv_uniq(a); + r = strv_length(a); - assert_return(ifindex > 0, -EINVAL); - assert_return(state, -EINVAL); + *ret = a; + a = NULL; - if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) - return -ENOMEM; + return r; +} - 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; +_public_ int sd_network_link_get_setup_state(int ifindex, char **state) { + return network_link_get_string(ifindex, "ADMIN_STATE", state); +} - *state = s; - s = NULL; +_public_ int sd_network_link_get_network_file(int ifindex, char **filename) { + return network_link_get_string(ifindex, "NETWORK_FILE", filename); +} - return 0; +_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) { - _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; + return network_link_get_string(ifindex, "LLMNR", llmnr); +} - r = parse_env_file(p, NEWLINE, "LLMNR", &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; +_public_ int sd_network_link_get_mdns(int ifindex, char **mdns) { + return network_link_get_string(ifindex, "MDNS", mdns); +} - *llmnr = s; - s = NULL; +_public_ int sd_network_link_get_dnssec(int ifindex, char **dnssec) { + return network_link_get_string(ifindex, "DNSSEC", dnssec); +} - return 0; +_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) { @@ -221,85 +211,32 @@ _public_ int sd_network_link_get_lldp(int ifindex, char **lldp) { } int sd_network_link_get_timezone(int ifindex, 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) - return -ENOMEM; - - r = parse_env_file(p, NEWLINE, "TIMEZONE", &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; - - *ret = s; - s = NULL; - return 0; -} - -static int network_get_link_strv(const char *key, int ifindex, char ***ret) { - _cleanup_free_ char *p = NULL, *s = NULL; - _cleanup_strv_free_ char **a = NULL; - int r; - - assert_return(ifindex > 0, -EINVAL); - assert_return(ret, -EINVAL); - - if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) - return -ENOMEM; - - r = parse_env_file(p, NEWLINE, key, &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) { - *ret = NULL; - return 0; - } - - a = strv_split(s, " "); - if (!a) - return -ENOMEM; - - strv_uniq(a); - r = strv_length(a); - - *ret = a; - a = NULL; - - return r; + 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); diff --git a/src/libsystemd/sd-resolve/test-resolve.c b/src/libsystemd/sd-resolve/test-resolve.c index e78a75c9ea..ce97e81ed6 100644 --- a/src/libsystemd/sd-resolve/test-resolve.c +++ b/src/libsystemd/sd-resolve/test-resolve.c @@ -101,11 +101,11 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "sd_resolve_getnameinfo(): %m"); - /* Wait until the two queries are completed */ - while (sd_resolve_query_is_done(q1) == 0 || - sd_resolve_query_is_done(q2) == 0) { - + /* Wait until all queries are completed */ + for (;;) { r = sd_resolve_wait(resolve, (uint64_t) -1); + if (r == 0) + break; if (r < 0) { log_error_errno(r, "sd_resolve_wait(): %m"); assert_not_reached("sd_resolve_wait() failed"); diff --git a/src/locale/localed.c b/src/locale/localed.c index 5ca41331bd..8ab845eb80 100644 --- a/src/locale/localed.c +++ b/src/locale/localed.c @@ -539,7 +539,7 @@ static int read_next_mapping(const char* filename, if (!fgets(line, sizeof(line), f)) { if (ferror(f)) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; return 0; } diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 816349c559..931b96fe51 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -88,7 +88,7 @@ static OutputFlags get_output_flags(void) { arg_all * OUTPUT_SHOW_ALL | arg_full * OUTPUT_FULL_WIDTH | (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | - on_tty() * OUTPUT_COLOR; + colors_enabled() * OUTPUT_COLOR; } static int list_sessions(int argc, char *argv[], void *userdata) { diff --git a/src/login/logind-core.c b/src/login/logind-core.c index d51330fb85..2e14aa2d95 100644 --- a/src/login/logind-core.c +++ b/src/login/logind-core.c @@ -139,7 +139,7 @@ int manager_add_user_by_uid(Manager *m, uid_t uid, User **_user) { errno = 0; p = getpwuid(uid); if (!p) - return errno ? -errno : -ENOENT; + return errno > 0 ? -errno : -ENOENT; return manager_add_user(m, uid, p->pw_gid, p->pw_name, _user); } diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 4631f5fc90..9eda4638e5 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -124,7 +124,6 @@ int manager_get_seat_from_creds(Manager *m, sd_bus_message *message, const char return r; seat = session->seat; - if (!seat) return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_SEAT, "Session has no seat."); } else { @@ -1111,7 +1110,7 @@ static int method_set_user_linger(sd_bus_message *message, void *userdata, sd_bu errno = 0; pw = getpwuid(uid); if (!pw) - return errno ? -errno : -ENOENT; + return errno > 0 ? -errno : -ENOENT; r = bus_verify_polkit_async( message, @@ -1995,7 +1994,7 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_AUGMENT|SD_BUS_CREDS_TTY|SD_BUS_CREDS_UID, &creds); if (r >= 0) { - const char *tty; + const char *tty = NULL; (void) sd_bus_creds_get_uid(creds, &m->scheduled_shutdown_uid); (void) sd_bus_creds_get_tty(creds, &tty); @@ -2752,6 +2751,23 @@ int manager_send_changed(Manager *manager, const char *property, ...) { l); } +static int strdup_job(sd_bus_message *reply, char **job) { + const char *j; + char *copy; + int r; + + r = sd_bus_message_read(reply, "o", &j); + if (r < 0) + return r; + + copy = strdup(j); + if (!copy) + return -ENOMEM; + + *job = copy; + return 1; +} + int manager_start_slice( Manager *manager, const char *slice, @@ -2767,6 +2783,7 @@ int manager_start_slice( assert(manager); assert(slice); + assert(job); r = sd_bus_message_new_method_call( manager->bus, @@ -2820,22 +2837,7 @@ int manager_start_slice( if (r < 0) return r; - if (job) { - const char *j; - char *copy; - - r = sd_bus_message_read(reply, "o", &j); - if (r < 0) - return r; - - copy = strdup(j); - if (!copy) - return -ENOMEM; - - *job = copy; - } - - return 1; + return strdup_job(reply, job); } int manager_start_scope( @@ -2856,6 +2858,7 @@ int manager_start_scope( assert(manager); assert(scope); assert(pid > 1); + assert(job); r = sd_bus_message_new_method_call( manager->bus, @@ -2930,22 +2933,7 @@ int manager_start_scope( if (r < 0) return r; - if (job) { - const char *j; - char *copy; - - r = sd_bus_message_read(reply, "o", &j); - if (r < 0) - return r; - - copy = strdup(j); - if (!copy) - return -ENOMEM; - - *job = copy; - } - - return 1; + return strdup_job(reply, job); } int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job) { @@ -2954,6 +2942,7 @@ int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error, assert(manager); assert(unit); + assert(job); r = sd_bus_call_method( manager->bus, @@ -2967,22 +2956,7 @@ int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error, if (r < 0) return r; - if (job) { - const char *j; - char *copy; - - r = sd_bus_message_read(reply, "o", &j); - if (r < 0) - return r; - - copy = strdup(j); - if (!copy) - return -ENOMEM; - - *job = copy; - } - - return 1; + return strdup_job(reply, job); } int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job) { @@ -2991,6 +2965,7 @@ int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, c assert(manager); assert(unit); + assert(job); r = sd_bus_call_method( manager->bus, @@ -3005,9 +2980,7 @@ int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, c if (sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) || sd_bus_error_has_name(error, BUS_ERROR_LOAD_FAILED)) { - if (job) - *job = NULL; - + *job = NULL; sd_bus_error_free(error); return 0; } @@ -3015,22 +2988,7 @@ int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, c return r; } - if (job) { - const char *j; - char *copy; - - r = sd_bus_message_read(reply, "o", &j); - if (r < 0) - return r; - - copy = strdup(j); - if (!copy) - return -ENOMEM; - - *job = copy; - } - - return 1; + return strdup_job(reply, job); } int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *error) { diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c index 1f4936cebe..9d111f737c 100644 --- a/src/login/logind-seat.c +++ b/src/login/logind-seat.c @@ -34,6 +34,7 @@ #include "logind-seat.h" #include "mkdir.h" #include "parse-util.h" +#include "stdio-util.h" #include "string-util.h" #include "terminal-util.h" #include "util.h" @@ -181,7 +182,7 @@ static int vt_allocate(unsigned int vtnr) { assert(vtnr >= 1); - snprintf(p, sizeof(p), "/dev/tty%u", vtnr); + xsprintf(p, "/dev/tty%u", vtnr); fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC); if (fd < 0) return -errno; diff --git a/src/login/logind-user.c b/src/login/logind-user.c index 4ad9740e5e..98f8ea3c78 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -412,13 +412,12 @@ static int user_start_slice(User *u) { u->manager->user_tasks_max, &error, &job); - if (r < 0) { - /* we don't fail due to this, let's try to continue */ - if (!sd_bus_error_has_name(&error, BUS_ERROR_UNIT_EXISTS)) - log_error_errno(r, "Failed to start user slice %s, ignoring: %s (%s)", u->slice, bus_error_message(&error, r), error.name); - } else { + if (r >= 0) u->slice_job = job; - } + else if (!sd_bus_error_has_name(&error, BUS_ERROR_UNIT_EXISTS)) + /* we don't fail due to this, let's try to continue */ + log_error_errno(r, "Failed to start user slice %s, ignoring: %s (%s)", + u->slice, bus_error_message(&error, r), error.name); return 0; } @@ -868,7 +867,7 @@ int config_parse_tmpfs_size( errno = 0; ul = strtoul(rvalue, &f, 10); - if (errno != 0 || f != e) { + if (errno > 0 || f != e) { log_syntax(unit, LOG_ERR, filename, line, errno, "Failed to parse percentage value, ignoring: %s", rvalue); return 0; } diff --git a/src/machine-id-setup/machine-id-setup-main.c b/src/machine-id-setup/machine-id-setup-main.c index d805bcfdca..9d19307236 100644 --- a/src/machine-id-setup/machine-id-setup-main.c +++ b/src/machine-id-setup/machine-id-setup-main.c @@ -112,7 +112,7 @@ int main(int argc, char *argv[]) { if (arg_commit) r = machine_id_commit(arg_root); else - r = machine_id_setup(arg_root); + r = machine_id_setup(arg_root, SD_ID128_NULL); finish: free(arg_root); diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index 9f7c9952d3..03d32c6ed7 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -922,7 +922,7 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu First, we start by creating a private playground in /tmp, that we can mount MS_SLAVE. (Which is necessary, since - MS_MOUNT cannot be applied to mounts with MS_SHARED parent + MS_MOVE cannot be applied to mounts with MS_SHARED parent mounts.) */ if (!mkdtemp(mount_slave)) diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 685bbafdf1..fd454310ae 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -108,7 +108,7 @@ static OutputFlags get_output_flags(void) { arg_all * OUTPUT_SHOW_ALL | arg_full * OUTPUT_FULL_WIDTH | (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | - on_tty() * OUTPUT_COLOR | + colors_enabled() * OUTPUT_COLOR | !arg_quiet * OUTPUT_WARN_CUTOFF; } @@ -1571,7 +1571,7 @@ static int start_machine(int argc, char *argv[], void *userdata) { return log_oom(); } - r = bus_wait_for_jobs(w, arg_quiet); + r = bus_wait_for_jobs(w, arg_quiet, NULL); if (r < 0) return r; diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index c6b5b1ec44..e448dd2035 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -910,7 +910,7 @@ static int method_map_from_machine_user(sd_bus_message *message, void *userdata, if (k < 0 && feof(f)) break; if (k != 3) { - if (ferror(f) && errno != 0) + if (ferror(f) && errno > 0) return -errno; return -EIO; @@ -968,7 +968,7 @@ static int method_map_to_machine_user(sd_bus_message *message, void *userdata, s if (k < 0 && feof(f)) break; if (k != 3) { - if (ferror(f) && errno != 0) + if (ferror(f) && errno > 0) return -errno; return -EIO; @@ -1028,7 +1028,7 @@ static int method_map_from_machine_group(sd_bus_message *message, void *groupdat if (k < 0 && feof(f)) break; if (k != 3) { - if (ferror(f) && errno != 0) + if (ferror(f) && errno > 0) return -errno; return -EIO; @@ -1086,7 +1086,7 @@ static int method_map_to_machine_group(sd_bus_message *message, void *groupdata, if (k < 0 && feof(f)) break; if (k != 3) { - if (ferror(f) && errno != 0) + if (ferror(f) && errno > 0) return -errno; return -EIO; diff --git a/src/network/networkctl.c b/src/network/networkctl.c index 0234825adb..4a8fa4d8f3 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -40,6 +40,7 @@ #include "pager.h" #include "parse-util.h" #include "socket-util.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -275,7 +276,8 @@ static int ieee_oui(sd_hwdb *hwdb, struct ether_addr *mac, char **ret) { if (memcmp(mac, "\0\0\0", 3) == 0) return -EINVAL; - snprintf(modalias, sizeof(modalias), "OUI:" ETHER_ADDR_FORMAT_STR, ETHER_ADDR_FORMAT_VAL(*mac)); + xsprintf(modalias, "OUI:" ETHER_ADDR_FORMAT_STR, + ETHER_ADDR_FORMAT_VAL(*mac)); r = sd_hwdb_get(hwdb, modalias, "ID_OUI_FROM_DATABASE", &description); if (r < 0) diff --git a/src/network/networkd-link-bus.c b/src/network/networkd-link-bus.c index d09a3c2d07..4d6ac747fd 100644 --- a/src/network/networkd-link-bus.c +++ b/src/network/networkd-link-bus.c @@ -59,15 +59,19 @@ static char *link_bus_path(Link *link) { int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { _cleanup_strv_free_ char **l = NULL; Manager *m = userdata; + unsigned c = 0; Link *link; Iterator i; - int r; assert(bus); assert(path); assert(m); assert(nodes); + l = new0(char*, hashmap_size(m->links) + 1); + if (!l) + return -ENOMEM; + HASHMAP_FOREACH(link, m->links, i) { char *p; @@ -75,11 +79,10 @@ int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char *** if (!p) return -ENOMEM; - r = strv_consume(&l, p); - if (r < 0) - return r; + l[c++] = p; } + l[c] = NULL; *nodes = l; l = NULL; @@ -99,7 +102,7 @@ int link_object_find(sd_bus *bus, const char *path, const char *interface, void assert(found); r = sd_bus_path_decode(path, "/org/freedesktop/network1/link", &identifier); - if (r < 0) + if (r <= 0) return 0; r = parse_ifindex(identifier, &ifindex); diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index fb09dbded9..bbda691c08 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -2495,7 +2495,7 @@ int link_ipv6ll_gained(Link *link, const struct in6_addr *address) { link->ipv6ll_address = *address; link_check_ready(link); - if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_PENDING, LINK_STATE_UNMANAGED, LINK_STATE_FAILED)) { + if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_UNMANAGED, LINK_STATE_FAILED)) { r = link_acquire_ipv6_conf(link); if (r < 0) { link_enter_failed(link); @@ -2511,7 +2511,7 @@ static int link_carrier_gained(Link *link) { assert(link); - if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_PENDING, LINK_STATE_UNMANAGED, LINK_STATE_FAILED)) { + if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_UNMANAGED, LINK_STATE_FAILED)) { r = link_acquire_conf(link); if (r < 0) { link_enter_failed(link); @@ -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); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index de2c66d153..2f2a36ccca 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -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) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 29723a852f..c11cb3dcb3 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -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; +} diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index cb3a50d9ba..b07fa41abc 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -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); diff --git a/src/network/networkd-util.c b/src/network/networkd-util.c index 2545621a93..93135bb658 100644 --- a/src/network/networkd-util.c +++ b/src/network/networkd-util.c @@ -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; -} diff --git a/src/network/networkd-util.h b/src/network/networkd-util.h index cc41aae85a..021ce4b128 100644 --- a/src/network/networkd-util.h +++ b/src/network/networkd-util.h @@ -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_; diff --git a/src/nspawn/nspawn-setuid.c b/src/nspawn/nspawn-setuid.c index aa6a16309c..014a40b243 100644 --- a/src/nspawn/nspawn-setuid.c +++ b/src/nspawn/nspawn-setuid.c @@ -261,10 +261,10 @@ int change_uid_gid(const char *user, char **_home) { return log_error_errno(errno, "Failed to set auxiliary groups: %m"); if (setresgid(gid, gid, gid) < 0) - return log_error_errno(errno, "setregid() failed: %m"); + return log_error_errno(errno, "setresgid() failed: %m"); if (setresuid(uid, uid, uid) < 0) - return log_error_errno(errno, "setreuid() failed: %m"); + return log_error_errno(errno, "setresuid() failed: %m"); if (_home) { *_home = home; diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index a4e13bd6aa..d619206dd6 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1482,7 +1482,7 @@ static int setup_journal(const char *directory) { } static int drop_capabilities(void) { - return capability_bounding_set_drop(~arg_retain, false); + return capability_bounding_set_drop(arg_retain, false); } static int reset_audit_loginuid(void) { diff --git a/src/resolve-host/resolve-host.c b/src/resolve-host/resolve-host.c index 2cabfeaefa..54a060ea5a 100644 --- a/src/resolve-host/resolve-host.c +++ b/src/resolve-host/resolve-host.c @@ -64,10 +64,12 @@ static void print_source(uint64_t flags, usec_t rtt) { fputs("\n-- Information acquired via", stdout); if (flags != 0) - printf(" protocol%s%s%s", + printf(" protocol%s%s%s%s%s", flags & SD_RESOLVED_DNS ? " DNS" :"", flags & SD_RESOLVED_LLMNR_IPV4 ? " LLMNR/IPv4" : "", - flags & SD_RESOLVED_LLMNR_IPV6 ? " LLMNR/IPv6" : ""); + flags & SD_RESOLVED_LLMNR_IPV6 ? " LLMNR/IPv6" : "", + flags & SD_RESOLVED_MDNS_IPV4 ? "mDNS/IPv4" : "", + flags & SD_RESOLVED_MDNS_IPV6 ? "mDNS/IPv6" : ""); assert_se(format_timespan(rtt_str, sizeof(rtt_str), rtt, 100)); @@ -769,10 +771,26 @@ static int show_statistics(sd_bus *bus) { uint64_t n_current_transactions, n_total_transactions, cache_size, n_cache_hit, n_cache_miss, n_dnssec_secure, n_dnssec_insecure, n_dnssec_bogus, n_dnssec_indeterminate; - int r; + int r, dnssec_supported; assert(bus); + r = sd_bus_get_property_trivial(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "DNSSECSupported", + &error, + 'b', + &dnssec_supported); + if (r < 0) + return log_error_errno(r, "Failed to get DNSSEC supported state: %s", bus_error_message(&error, r)); + + printf("DNSSEC supported by current servers: %s%s%s\n\n", + ansi_highlight(), + yes_no(dnssec_supported), + ansi_normal()); + r = sd_bus_get_property(bus, "org.freedesktop.resolve1", "/org/freedesktop/resolve1", @@ -916,7 +934,7 @@ static void help(void) { " --version Show package version\n" " -4 Resolve IPv4 addresses\n" " -6 Resolve IPv6 addresses\n" - " -i INTERFACE Look on interface\n" + " -i --interface=INTERFACE Look on interface\n" " -p --protocol=PROTOCOL Look via protocol\n" " -t --type=TYPE Query RR with DNS type\n" " -c --class=CLASS Query RR with DNS class\n" @@ -950,6 +968,7 @@ static int parse_argv(int argc, char *argv[]) { { "type", required_argument, NULL, 't' }, { "class", required_argument, NULL, 'c' }, { "legend", required_argument, NULL, ARG_LEGEND }, + { "interface", required_argument, NULL, 'i' }, { "protocol", required_argument, NULL, 'p' }, { "cname", required_argument, NULL, ARG_CNAME }, { "service", no_argument, NULL, ARG_SERVICE }, diff --git a/src/resolve/RFCs b/src/resolve/RFCs index ccc7f0d640..22004a00cd 100644 --- a/src/resolve/RFCs +++ b/src/resolve/RFCs @@ -9,18 +9,18 @@ Y https://tools.ietf.org/html/rfc1034 → DOMAIN NAMES - CONCEPTS AND FACILITIES Y https://tools.ietf.org/html/rfc1035 → DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION ? https://tools.ietf.org/html/rfc1101 → DNS Encoding of Network Names and Other Types Y https://tools.ietf.org/html/rfc1123 → Requirements for Internet Hosts -- Application and Support - https://tools.ietf.org/html/rfc1464 → Using the Domain Name System To Store Arbitrary String Attributes +~ https://tools.ietf.org/html/rfc1464 → Using the Domain Name System To Store Arbitrary String Attributes Y https://tools.ietf.org/html/rfc1536 → Common DNS Implementation Errors and Suggested Fixes Y https://tools.ietf.org/html/rfc1876 → A Means for Expressing Location Information in the Domain Name System Y https://tools.ietf.org/html/rfc2181 → Clarifications to the DNS Specification - https://tools.ietf.org/html/rfc2308 → Negative Caching of DNS Queries (DNS NCACHE) +Y https://tools.ietf.org/html/rfc2308 → Negative Caching of DNS Queries (DNS NCACHE) Y https://tools.ietf.org/html/rfc2782 → A DNS RR for specifying the location of services (DNS SRV) D https://tools.ietf.org/html/rfc3492 → Punycode: A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA) Y https://tools.ietf.org/html/rfc3596 → DNS Extensions to Support IP Version 6 Y https://tools.ietf.org/html/rfc3597 → Handling of Unknown DNS Resource Record (RR) Types - https://tools.ietf.org/html/rfc4033 → DNS Security Introduction and Requirements - https://tools.ietf.org/html/rfc4034 → Resource Records for the DNS Security Extensions - https://tools.ietf.org/html/rfc4035 → Protocol Modifications for the DNS Security Extensions +Y https://tools.ietf.org/html/rfc4033 → DNS Security Introduction and Requirements +Y https://tools.ietf.org/html/rfc4034 → Resource Records for the DNS Security Extensions +Y https://tools.ietf.org/html/rfc4035 → Protocol Modifications for the DNS Security Extensions ! https://tools.ietf.org/html/rfc4183 → A Suggested Scheme for DNS Resolution of Networks and Gateways Y https://tools.ietf.org/html/rfc4255 → Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints Y https://tools.ietf.org/html/rfc4343 → Domain Name System (DNS) Case Insensitivity Clarification @@ -30,28 +30,28 @@ Y https://tools.ietf.org/html/rfc4509 → Use of SHA-256 in DNSSEC Delegation Si ~ https://tools.ietf.org/html/rfc4592 → The Role of Wildcards in the Domain Name System ~ https://tools.ietf.org/html/rfc4697 → Observed DNS Resolution Misbehavior Y https://tools.ietf.org/html/rfc4795 → Link-Local Multicast Name Resolution (LLMNR) -! https://tools.ietf.org/html/rfc5011 → Automated Updates of DNS Security (DNSSEC) Trust Anchors - https://tools.ietf.org/html/rfc5155 → DNS Security (DNSSEC) Hashed Authenticated Denial of Existence - https://tools.ietf.org/html/rfc5452 → Measures for Making DNS More Resilient against Forged Answers +Y https://tools.ietf.org/html/rfc5011 → Automated Updates of DNS Security (DNSSEC) Trust Anchors +Y https://tools.ietf.org/html/rfc5155 → DNS Security (DNSSEC) Hashed Authenticated Denial of Existence +Y https://tools.ietf.org/html/rfc5452 → Measures for Making DNS More Resilient against Forged Answers Y https://tools.ietf.org/html/rfc5702 → Use of SHA-2 Algorithms with RSA in DNSKEY and RRSIG Resource Records for DNSSEC Y https://tools.ietf.org/html/rfc5890 → Internationalized Domain Names for Applications (IDNA): Definitions and Document Framework Y https://tools.ietf.org/html/rfc5891 → Internationalized Domain Names in Applications (IDNA): Protocol Y https://tools.ietf.org/html/rfc5966 → DNS Transport over TCP - Implementation Requirements Y https://tools.ietf.org/html/rfc6303 → Locally Served DNS Zones - https://tools.ietf.org/html/rfc6604 → xNAME RCODE and Status Bits Clarification +Y https://tools.ietf.org/html/rfc6604 → xNAME RCODE and Status Bits Clarification Y https://tools.ietf.org/html/rfc6605 → Elliptic Curve Digital Signature Algorithm (DSA) for DNSSEC https://tools.ietf.org/html/rfc6672 → DNAME Redirection in the DNS - https://tools.ietf.org/html/rfc6731 → Improved Recursive DNS Server Selection for Multi-Interfaced Nodes +! https://tools.ietf.org/html/rfc6731 → Improved Recursive DNS Server Selection for Multi-Interfaced Nodes Y https://tools.ietf.org/html/rfc6761 → Special-Use Domain Names https://tools.ietf.org/html/rfc6762 → Multicast DNS https://tools.ietf.org/html/rfc6763 → DNS-Based Service Discovery - https://tools.ietf.org/html/rfc6781 → DNSSEC Operational Practices, Version 2 - https://tools.ietf.org/html/rfc6840 → Clarifications and Implementation Notes for DNS Security (DNSSEC) +~ https://tools.ietf.org/html/rfc6781 → DNSSEC Operational Practices, Version 2 +Y https://tools.ietf.org/html/rfc6840 → Clarifications and Implementation Notes for DNS Security (DNSSEC) Y https://tools.ietf.org/html/rfc6891 → Extension Mechanisms for DNS (EDNS(0)) Y https://tools.ietf.org/html/rfc6944 → Applicability Statement: DNS Security (DNSSEC) DNSKEY Algorithm Implementation Status Y https://tools.ietf.org/html/rfc6975 → Signaling Cryptographic Algorithm Understanding in DNS Security Extensions (DNSSEC) - https://tools.ietf.org/html/rfc7129 → Authenticated Denial of Existence in the DNS -! https://tools.ietf.org/html/rfc7646 → Definition and Use of DNSSEC Negative Trust Anchors +Y https://tools.ietf.org/html/rfc7129 → Authenticated Denial of Existence in the DNS +Y https://tools.ietf.org/html/rfc7646 → Definition and Use of DNSSEC Negative Trust Anchors ~ https://tools.ietf.org/html/rfc7719 → DNS Terminology Also relevant: diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c index 0571d65f0b..058d14009a 100644 --- a/src/resolve/dns-type.c +++ b/src/resolve/dns-type.c @@ -77,7 +77,13 @@ bool dns_type_is_valid_query(uint16_t type) { 0, DNS_TYPE_OPT, DNS_TYPE_TSIG, - DNS_TYPE_TKEY); + DNS_TYPE_TKEY, + + /* RRSIG are technically valid as questions, but we refuse doing explicit queries for them, as + * they aren't really payload, but signatures for payload, and cannot be validated on their + * own. After all they are the signatures, and have no signatures of their own validating + * them. */ + DNS_TYPE_RRSIG); } bool dns_type_is_valid_rr(uint16_t type) { @@ -114,6 +120,69 @@ bool dns_type_may_redirect(uint16_t type) { DNS_TYPE_KEY); } +bool dns_type_may_wildcard(uint16_t type) { + + /* The following records may not be expanded from wildcard RRsets */ + + if (dns_type_is_pseudo(type)) + return false; + + return !IN_SET(type, + DNS_TYPE_NSEC3, + DNS_TYPE_SOA, + + /* Prohibited by https://tools.ietf.org/html/rfc4592#section-4.4 */ + DNS_TYPE_DNAME); +} + +bool dns_type_apex_only(uint16_t type) { + + /* Returns true for all RR types that may only appear signed in a zone apex */ + + return IN_SET(type, + DNS_TYPE_SOA, + DNS_TYPE_NS, /* this one can appear elsewhere, too, but not signed */ + DNS_TYPE_DNSKEY, + DNS_TYPE_NSEC3PARAM); +} + +bool dns_type_is_dnssec(uint16_t type) { + return IN_SET(type, + DNS_TYPE_DS, + DNS_TYPE_DNSKEY, + DNS_TYPE_RRSIG, + DNS_TYPE_NSEC, + DNS_TYPE_NSEC3, + DNS_TYPE_NSEC3PARAM); +} + +bool dns_type_is_obsolete(uint16_t type) { + return IN_SET(type, + /* Obsoleted by RFC 973 */ + DNS_TYPE_MD, + DNS_TYPE_MF, + DNS_TYPE_MAILA, + + /* Kinda obsoleted by RFC 2505 */ + DNS_TYPE_MB, + DNS_TYPE_MG, + DNS_TYPE_MR, + DNS_TYPE_MINFO, + DNS_TYPE_MAILB, + + /* RFC1127 kinda obsoleted this by recommending against its use */ + DNS_TYPE_WKS, + + /* Declared historical by RFC 6563 */ + DNS_TYPE_A6, + + /* Obsoleted by DNSSEC-bis */ + DNS_TYPE_NXT, + + /* RFC 1035 removed support for concepts that needed this from RFC 883 */ + DNS_TYPE_NULL); +} + const char *dns_class_to_string(uint16_t class) { switch (class) { diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h index c3bb26a5ee..78ff71b06e 100644 --- a/src/resolve/dns-type.h +++ b/src/resolve/dns-type.h @@ -129,6 +129,10 @@ bool dns_type_is_pseudo(uint16_t type); bool dns_type_is_valid_query(uint16_t type); bool dns_type_is_valid_rr(uint16_t type); bool dns_type_may_redirect(uint16_t type); +bool dns_type_is_dnssec(uint16_t type); +bool dns_type_is_obsolete(uint16_t type); +bool dns_type_may_wildcard(uint16_t type); +bool dns_type_apex_only(uint16_t type); bool dns_class_is_pseudo(uint16_t class); bool dns_class_is_valid_rr(uint16_t class); diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 4d4c1ca014..9110ea52a6 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -25,20 +25,9 @@ #include "dns-domain.h" #include "resolved-bus.h" #include "resolved-def.h" +#include "resolved-link-bus.h" static int reply_query_state(DnsQuery *q) { - _cleanup_free_ char *ip = NULL; - const char *name; - int r; - - if (q->request_address_valid) { - r = in_addr_to_string(q->request_family, &q->request_address, &ip); - if (r < 0) - return r; - - name = ip; - } else - name = dns_question_first_name(q->question); switch (q->state) { @@ -57,21 +46,24 @@ static int reply_query_state(DnsQuery *q) { case DNS_TRANSACTION_RESOURCES: return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_RESOURCES, "Not enough resources"); - case DNS_TRANSACTION_CONNECTION_FAILURE: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_CONNECTION_FAILURE, "DNS server connection failure"); - case DNS_TRANSACTION_ABORTED: return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "Query aborted"); case DNS_TRANSACTION_DNSSEC_FAILED: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "DNSSEC validation failed: %s", + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s", dnssec_result_to_string(q->answer_dnssec_result)); + case DNS_TRANSACTION_NO_TRUST_ANCHOR: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_TRUST_ANCHOR, "No suitable trust anchor known"); + + case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_RR_TYPE_UNSUPPORTED, "Server does not support requested resource record type"); + case DNS_TRANSACTION_RCODE_FAILURE: { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; if (q->answer_rcode == DNS_RCODE_NXDOMAIN) - sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", name); + sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); else { const char *rc, *n; char p[3]; /* the rcode is 4 bits long */ @@ -83,7 +75,7 @@ static int reply_query_state(DnsQuery *q) { } n = strjoina(_BUS_ERROR_DNS, rc); - sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", name, rc); + sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", dns_query_string(q), rc); } return sd_bus_reply_method_error(q->request, &error); @@ -91,6 +83,7 @@ static int reply_query_state(DnsQuery *q) { case DNS_TRANSACTION_NULL: case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: case DNS_TRANSACTION_SUCCESS: default: assert_not_reached("Impossible state"); @@ -152,12 +145,12 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { r = dns_query_process_cname(q); if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); goto finish; } if (r < 0) goto finish; - if (r > 0) /* This was a cname, and the query was restarted. */ + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ return; r = sd_bus_message_new_method_return(q->request, &reply); @@ -173,7 +166,11 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { int ifindex; DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - r = dns_question_matches_rr(q->question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + DnsQuestion *question; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); if (r < 0) goto finish; if (r == 0) @@ -191,7 +188,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { } if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_first_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); goto finish; } @@ -235,7 +232,7 @@ static int check_ifindex_flags(int ifindex, uint64_t *flags, uint64_t ok, sd_bus } static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; Manager *m = userdata; const char *hostname; int family, ifindex; @@ -265,11 +262,15 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, if (r < 0) return r; - r = dns_question_new_address(&question, family, hostname); + r = dns_question_new_address(&question_utf8, family, hostname, false); + if (r < 0) + return r; + + r = dns_question_new_address(&question_idna, family, hostname, true); if (r < 0) return r; - r = dns_query_new(m, &q, question, ifindex, flags); + r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags); if (r < 0) return r; @@ -294,6 +295,7 @@ fail: static void bus_method_resolve_address_complete(DnsQuery *q) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + DnsQuestion *question; DnsResourceRecord *rr; unsigned added = 0; int ifindex, r; @@ -307,12 +309,12 @@ static void bus_method_resolve_address_complete(DnsQuery *q) { r = dns_query_process_cname(q); if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); goto finish; } if (r < 0) goto finish; - if (r > 0) /* This was a cname, and the query was restarted. */ + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ return; r = sd_bus_message_new_method_return(q->request, &reply); @@ -323,20 +325,20 @@ static void bus_method_resolve_address_complete(DnsQuery *q) { if (r < 0) goto finish; - if (q->answer) { - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - r = dns_question_matches_rr(q->question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; + question = dns_query_question_for_protocol(q, q->answer_protocol); - r = sd_bus_message_append(reply, "(is)", ifindex, rr->ptr.name); - if (r < 0) - goto finish; + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; - added ++; - } + r = sd_bus_message_append(reply, "(is)", ifindex, rr->ptr.name); + if (r < 0) + goto finish; + + added ++; } if (added <= 0) { @@ -407,7 +409,7 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s if (r < 0) return r; - r = dns_query_new(m, &q, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); + r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); if (r < 0) return r; @@ -461,7 +463,10 @@ static int bus_message_append_rr(sd_bus_message *m, DnsResourceRecord *rr, int i static void bus_method_resolve_record_complete(DnsQuery *q) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + DnsResourceRecord *rr; + DnsQuestion *question; unsigned added = 0; + int ifindex; int r; assert(q); @@ -473,12 +478,12 @@ static void bus_method_resolve_record_complete(DnsQuery *q) { r = dns_query_process_cname(q); if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); goto finish; } if (r < 0) goto finish; - if (r > 0) /* Following a CNAME */ + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ return; r = sd_bus_message_new_method_return(q->request, &reply); @@ -489,27 +494,24 @@ static void bus_method_resolve_record_complete(DnsQuery *q) { if (r < 0) goto finish; - if (q->answer) { - DnsResourceRecord *rr; - int ifindex; + question = dns_query_question_for_protocol(q, q->answer_protocol); - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - r = dns_question_matches_rr(q->question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; - r = bus_message_append_rr(reply, rr, ifindex); - if (r < 0) - goto finish; + r = bus_message_append_rr(reply, rr, ifindex); + if (r < 0) + goto finish; - added ++; - } + added ++; } if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Name '%s' does not have any RR of the requested type", dns_question_first_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Name '%s' does not have any RR of the requested type", dns_query_string(q)); goto finish; } @@ -558,7 +560,9 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid name '%s'", name); if (!dns_type_is_valid_query(type)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid RR type for query %" PRIu16, type); + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified resource record type %" PRIu16 " may not be used in a query.", type); + if (dns_type_is_obsolete(type)) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Specified DNS resource record type %" PRIu16 " is obsolete.", type); r = check_ifindex_flags(ifindex, &flags, 0, error); if (r < 0) @@ -576,7 +580,7 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd if (r < 0) return r; - r = dns_query_new(m, &q, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); + r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); if (r < 0) return r; @@ -616,13 +620,16 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) * record for the SRV record */ LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { DnsResourceRecord *zz; + DnsQuestion *question; if (aux->state != DNS_TRANSACTION_SUCCESS) continue; if (aux->auxiliary_result != 0) continue; - r = dns_name_equal(dns_question_first_name(aux->question), rr->srv.name); + question = dns_query_question_for_protocol(aux, aux->answer_protocol); + + r = dns_name_equal(dns_question_first_name(question), rr->srv.name); if (r < 0) return r; if (r == 0) @@ -630,7 +637,7 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) DNS_ANSWER_FOREACH(zz, aux->answer) { - r = dns_question_matches_rr(aux->question, zz, NULL); + r = dns_question_matches_rr(question, zz, NULL); if (r < 0) return r; if (r == 0) @@ -667,6 +674,7 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { DnsResourceRecord *zz; + DnsQuestion *question; int ifindex; if (aux->state != DNS_TRANSACTION_SUCCESS) @@ -674,7 +682,9 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) if (aux->auxiliary_result != 0) continue; - r = dns_name_equal(dns_question_first_name(aux->question), rr->srv.name); + question = dns_query_question_for_protocol(aux, aux->answer_protocol); + + r = dns_name_equal(dns_question_first_name(question), rr->srv.name); if (r < 0) return r; if (r == 0) @@ -682,7 +692,7 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) DNS_ANSWER_FOREACH_IFINDEX(zz, ifindex, aux->answer) { - r = dns_question_matches_rr(aux->question, zz, NULL); + r = dns_question_matches_rr(question, zz, NULL); if (r < 0) return r; if (r == 0) @@ -740,8 +750,10 @@ static void resolve_service_all_complete(DnsQuery *q) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL; + DnsQuestion *question; + DnsResourceRecord *rr; + unsigned added = 0; DnsQuery *aux; - unsigned added = false; int r; assert(q); @@ -783,7 +795,7 @@ static void resolve_service_all_complete(DnsQuery *q) { assert(bad->auxiliary_result != 0); if (bad->auxiliary_result == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(bad->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(bad)); goto finish; } @@ -804,31 +816,28 @@ static void resolve_service_all_complete(DnsQuery *q) { if (r < 0) goto finish; - if (q->answer) { - DnsResourceRecord *rr; - - DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(q->question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; + question = dns_query_question_for_protocol(q, q->answer_protocol); + DNS_ANSWER_FOREACH(rr, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; - r = append_srv(q, reply, rr); - if (r < 0) - goto finish; - if (r == 0) /* not an SRV record */ - continue; + r = append_srv(q, reply, rr); + if (r < 0) + goto finish; + if (r == 0) /* not an SRV record */ + continue; - if (!canonical) - canonical = dns_resource_record_ref(rr); + if (!canonical) + canonical = dns_resource_record_ref(rr); - added++; - } + added++; } if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_first_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); goto finish; } @@ -840,20 +849,16 @@ static void resolve_service_all_complete(DnsQuery *q) { if (r < 0) goto finish; - if (q->answer) { - DnsResourceRecord *rr; - - DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(q->question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; + DNS_ANSWER_FOREACH(rr, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; - r = append_txt(reply, rr); - if (r < 0) - goto finish; - } + r = append_txt(reply, rr); + if (r < 0) + goto finish; } r = sd_bus_message_close_container(reply); @@ -896,7 +901,7 @@ static void resolve_service_hostname_complete(DnsQuery *q) { } r = dns_query_process_cname(q); - if (r > 0) /* This was a cname, and the query was restarted. */ + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ return; /* This auxiliary lookup is finished or failed, let's see if all are finished now. */ @@ -917,11 +922,11 @@ static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifin /* OK, we found an SRV record for the service. Let's resolve * the hostname included in it */ - r = dns_question_new_address(&question, q->request_family, rr->srv.name); + r = dns_question_new_address(&question, q->request_family, rr->srv.name, false); if (r < 0) return r; - r = dns_query_new(q->manager, &aux, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH); + r = dns_query_new(q->manager, &aux, question, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH); if (r < 0) return r; @@ -955,8 +960,11 @@ fail: } static void bus_method_resolve_service_complete(DnsQuery *q) { + bool has_root_domain = false; + DnsResourceRecord *rr; + DnsQuestion *question; unsigned found = 0; - int r; + int ifindex, r; assert(q); @@ -967,61 +975,56 @@ static void bus_method_resolve_service_complete(DnsQuery *q) { r = dns_query_process_cname(q); if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); goto finish; } if (r < 0) goto finish; - if (r > 0) /* This was a cname, and the query was restarted. */ + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ return; - if (q->answer) { - bool has_root_domain = false; - DnsResourceRecord *rr; - int ifindex; + question = dns_query_question_for_protocol(q, q->answer_protocol); - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - r = dns_question_matches_rr(q->question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; - if (rr->key->type != DNS_TYPE_SRV) - continue; + if (rr->key->type != DNS_TYPE_SRV) + continue; - if (dns_name_is_root(rr->srv.name)) { - has_root_domain = true; - continue; - } + if (dns_name_is_root(rr->srv.name)) { + has_root_domain = true; + continue; + } - if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { - q->block_all_complete ++; - r = resolve_service_hostname(q, rr, ifindex); - q->block_all_complete --; + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + q->block_all_complete ++; + r = resolve_service_hostname(q, rr, ifindex); + q->block_all_complete --; - if (r < 0) - goto finish; - } - - found++; + if (r < 0) + goto finish; } - if (has_root_domain && found == 0) { - /* If there's exactly one SRV RR and it uses - * the root domain as host name, then the - * service is explicitly not offered on the - * domain. Report this as a recognizable - * error. See RFC 2782, Section "Usage - * Rules". */ - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_SERVICE, "'%s' does not provide the requested service", dns_question_first_name(q->question)); - goto finish; - } + found++; + } + if (has_root_domain && found <= 0) { + /* If there's exactly one SRV RR and it uses + * the root domain as host name, then the + * service is explicitly not offered on the + * domain. Report this as a recognizable + * error. See RFC 2782, Section "Usage + * Rules". */ + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_SERVICE, "'%s' does not provide the requested service", dns_query_string(q)); + goto finish; } if (found <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_first_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); goto finish; } @@ -1039,8 +1042,8 @@ finish: } static int bus_method_resolve_service(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - const char *name, *type, *domain, *joined; + _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; + const char *name, *type, *domain; _cleanup_free_ char *n = NULL; Manager *m = userdata; int family, ifindex; @@ -1062,10 +1065,8 @@ static int bus_method_resolve_service(sd_bus_message *message, void *userdata, s if (isempty(name)) name = NULL; - else { - if (!dns_service_name_is_valid(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service name '%s'", name); - } + else if (!dns_service_name_is_valid(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service name '%s'", name); if (isempty(type)) type = NULL; @@ -1085,23 +1086,15 @@ static int bus_method_resolve_service(sd_bus_message *message, void *userdata, s if (r < 0) return r; - if (type) { - /* If the type is specified, we generate the full domain name to look up ourselves */ - r = dns_service_join(name, type, domain, &n); - if (r < 0) - return r; - - joined = n; - } else - /* If no type is specified, we assume the domain - * contains the full domain name to lookup already */ - joined = domain; + r = dns_question_new_service(&question_utf8, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), false); + if (r < 0) + return r; - r = dns_question_new_service(&question, joined, !(flags & SD_RESOLVED_NO_TXT)); + r = dns_question_new_service(&question_idna, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), true); if (r < 0) return r; - r = dns_query_new(m, &q, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); + r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags|SD_RESOLVED_NO_SEARCH); if (r < 0) return r; @@ -1124,17 +1117,23 @@ fail: return r; } -static int append_dns_server(sd_bus_message *reply, DnsServer *s) { +int bus_dns_server_append(sd_bus_message *reply, DnsServer *s, bool with_ifindex) { int r; assert(reply); assert(s); - r = sd_bus_message_open_container(reply, 'r', "iiay"); + r = sd_bus_message_open_container(reply, 'r', with_ifindex ? "iiay" : "iay"); if (r < 0) return r; - r = sd_bus_message_append(reply, "ii", s->link ? s->link->ifindex : 0, s->family); + if (with_ifindex) { + r = sd_bus_message_append(reply, "i", s->link ? s->link->ifindex : 0); + if (r < 0) + return r; + } + + r = sd_bus_message_append(reply, "i", s->family); if (r < 0) return r; @@ -1169,7 +1168,7 @@ static int bus_property_get_dns_servers( return r; LIST_FOREACH(servers, s, m->dns_servers) { - r = append_dns_server(reply, s); + r = bus_dns_server_append(reply, s, true); if (r < 0) return r; @@ -1178,7 +1177,7 @@ static int bus_property_get_dns_servers( HASHMAP_FOREACH(l, m->links, i) { LIST_FOREACH(servers, s, l->dns_servers) { - r = append_dns_server(reply, s); + r = bus_dns_server_append(reply, s, true); if (r < 0) return r; c++; @@ -1187,7 +1186,7 @@ static int bus_property_get_dns_servers( if (c == 0) { LIST_FOREACH(servers, s, m->fallback_dns_servers) { - r = append_dns_server(reply, s); + r = bus_dns_server_append(reply, s, true); if (r < 0) return r; } @@ -1300,6 +1299,23 @@ static int bus_property_get_dnssec_statistics( (uint64_t) m->n_dnssec_indeterminate); } +static int bus_property_get_dnssec_supported( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = userdata; + + assert(reply); + assert(m); + + return sd_bus_message_append(reply, "b", manager_dnssec_supported(m)); +} + static int bus_method_reset_statistics(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; DnsScope *s; @@ -1316,20 +1332,140 @@ static int bus_method_reset_statistics(sd_bus_message *message, void *userdata, return sd_bus_reply_method_return(message, NULL); } +static int get_any_link(Manager *m, int ifindex, Link **ret, sd_bus_error *error) { + Link *l; + + assert(m); + assert(ret); + + if (ifindex <= 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); + + l = hashmap_get(m->links, INT_TO_PTR(ifindex)); + if (!l) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_LINK, "Link %i not known", ifindex); + + *ret = l; + return 0; +} + +static int get_unmanaged_link(Manager *m, int ifindex, Link **ret, sd_bus_error *error) { + Link *l; + int r; + + assert(m); + assert(ret); + + r = get_any_link(m, ifindex, &l, error); + if (r < 0) + return r; + + if (l->flags & IFF_LOOPBACK) + return sd_bus_error_setf(error, BUS_ERROR_LINK_BUSY, "Link %s is loopback device.", l->name); + if (l->is_managed) + return sd_bus_error_setf(error, BUS_ERROR_LINK_BUSY, "Link %s is managed.", l->name); + + *ret = l; + return 0; +} + +static int call_link_method(Manager *m, sd_bus_message *message, sd_bus_message_handler_t handler, sd_bus_error *error) { + int ifindex, r; + Link *l; + + assert(m); + assert(message); + assert(handler); + + assert_cc(sizeof(int) == sizeof(int32_t)); + r = sd_bus_message_read(message, "i", &ifindex); + if (r < 0) + return r; + + r = get_unmanaged_link(m, ifindex, &l, error); + if (r < 0) + return r; + + return handler(message, l, error); +} + +static int bus_method_set_link_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_dns_servers, error); +} + +static int bus_method_set_link_search_domains(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_search_domains, error); +} + +static int bus_method_set_link_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_llmnr, error); +} + +static int bus_method_set_link_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_mdns, error); +} + +static int bus_method_set_link_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_dnssec, error); +} + +static int bus_method_set_link_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_dnssec_negative_trust_anchors, error); +} + +static int bus_method_revert_link(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_revert, error); +} + +static int bus_method_get_link(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *p = NULL; + Manager *m = userdata; + int r, ifindex; + Link *l; + + assert(message); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + r = sd_bus_message_read(message, "i", &ifindex); + if (r < 0) + return r; + + r = get_any_link(m, ifindex, &l, error); + if (r < 0) + return r; + + p = link_bus_path(l); + if (!p) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", p); +} + static const sd_bus_vtable resolve_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("LLMNRHostname", "s", NULL, offsetof(Manager, llmnr_hostname), 0), - SD_BUS_PROPERTY("DNSServers", "a(iiay)", bus_property_get_dns_servers, 0, 0), - SD_BUS_PROPERTY("SearchDomains", "a(is)", bus_property_get_search_domains, 0, 0), + SD_BUS_PROPERTY("DNS", "a(iiay)", bus_property_get_dns_servers, 0, 0), + SD_BUS_PROPERTY("Domains", "a(is)", bus_property_get_search_domains, 0, 0), SD_BUS_PROPERTY("TransactionStatistics", "(tt)", bus_property_get_transaction_statistics, 0, 0), SD_BUS_PROPERTY("CacheStatistics", "(ttt)", bus_property_get_cache_statistics, 0, 0), SD_BUS_PROPERTY("DNSSECStatistics", "(tttt)", bus_property_get_dnssec_statistics, 0, 0), + SD_BUS_PROPERTY("DNSSECSupported", "b", bus_property_get_dnssec_supported, 0, 0), SD_BUS_METHOD("ResolveHostname", "isit", "a(iiay)st", bus_method_resolve_hostname, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ResolveAddress", "iiayt", "a(is)t", bus_method_resolve_address, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ResolveRecord", "isqqt", "a(iqqay)t", bus_method_resolve_record, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ResolveService", "isssit", "a(qqqsa(iiay)s)aayssst", bus_method_resolve_service, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ResetStatistics", NULL, NULL, bus_method_reset_statistics, 0), + SD_BUS_METHOD("GetLink", "i", "o", bus_method_get_link, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLinkDNS", "ia(iay)", NULL, bus_method_set_link_dns_servers, 0), + SD_BUS_METHOD("SetLinkDomains", "ias", NULL, bus_method_set_link_search_domains, 0), + SD_BUS_METHOD("SetLinkLLMNR", "is", NULL, bus_method_set_link_llmnr, 0), + SD_BUS_METHOD("SetLinkMulticastDNS", "is", NULL, bus_method_set_link_mdns, 0), + SD_BUS_METHOD("SetLinkDNSSEC", "is", NULL, bus_method_set_link_dnssec, 0), + SD_BUS_METHOD("SetLinkDNSSECNegativeTrustAnchors", "ias", NULL, bus_method_set_link_dnssec_negative_trust_anchors, 0), + SD_BUS_METHOD("RevertLink", "i", NULL, bus_method_revert_link, 0), + SD_BUS_VTABLE_END, }; @@ -1387,6 +1523,7 @@ int manager_connect_bus(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to install bus reconnect time event: %m"); + (void) sd_event_source_set_description(m->bus_retry_event_source, "bus-retry"); return 0; } @@ -1394,6 +1531,14 @@ int manager_connect_bus(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to register object: %m"); + r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/resolve1/link", "org.freedesktop.resolve1.Link", link_vtable, link_object_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register link objects: %m"); + + r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/resolve1/link", link_node_enumerator, m); + if (r < 0) + return log_error_errno(r, "Failed to register link enumerator: %m"); + r = sd_bus_request_name(m->bus, "org.freedesktop.resolve1", 0); if (r < 0) return log_error_errno(r, "Failed to register name: %m"); diff --git a/src/resolve/resolved-bus.h b/src/resolve/resolved-bus.h index 1e72891178..1ee57ba43d 100644 --- a/src/resolve/resolved-bus.h +++ b/src/resolve/resolved-bus.h @@ -24,3 +24,4 @@ #include "resolved-manager.h" int manager_connect_bus(Manager *m); +int bus_dns_server_append(sd_bus_message *reply, DnsServer *s, bool with_ifindex); diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index 1b2f3e336e..88df7534c4 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -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; diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h index 668ea02bba..b4ef1b0378 100644 --- a/src/resolve/resolved-conf.h +++ b/src/resolve/resolved-conf.h @@ -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); diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c index 445999f545..f74e440531 100644 --- a/src/resolve/resolved-dns-answer.c +++ b/src/resolve/resolved-dns-answer.c @@ -320,6 +320,33 @@ int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a) { return false; } +int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone) { + DnsResourceRecord *rr; + int r; + + /* Checks whether the specified answer contains at least one NSEC3 RR in the specified zone */ + + DNS_ANSWER_FOREACH(rr, answer) { + const char *p; + + if (rr->key->type != DNS_TYPE_NSEC3) + continue; + + p = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_name_equal(p, zone); + if (r != 0) + return r; + } + + return false; +} + int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { DnsResourceRecord *rr, *soa = NULL; DnsAnswerFlags rr_flags, soa_flags = 0; @@ -524,6 +551,92 @@ int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) { return 1; } +int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) { + bool found = false, other = false; + DnsResourceRecord *rr; + unsigned i; + int r; + + assert(a); + assert(rm); + + /* Remove all entries matching the specified RR from *a */ + + DNS_ANSWER_FOREACH(rr, *a) { + r = dns_resource_record_equal(rr, rm); + if (r < 0) + return r; + if (r > 0) + found = true; + else + other = true; + + if (found && other) + break; + } + + if (!found) + return 0; + + if (!other) { + *a = dns_answer_unref(*a); /* Return NULL for the empty answer */ + return 1; + } + + if ((*a)->n_ref > 1) { + _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; + DnsAnswerFlags flags; + int ifindex; + + copy = dns_answer_new((*a)->n_rrs); + if (!copy) + return -ENOMEM; + + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { + r = dns_resource_record_equal(rr, rm); + if (r < 0) + return r; + if (r > 0) + continue; + + r = dns_answer_add_raw(copy, rr, ifindex, flags); + if (r < 0) + return r; + } + + dns_answer_unref(*a); + *a = copy; + copy = NULL; + + return 1; + } + + /* Only a single reference, edit in-place */ + + i = 0; + for (;;) { + if (i >= (*a)->n_rrs) + break; + + r = dns_resource_record_equal((*a)->items[i].rr, rm); + if (r < 0) + return r; + if (r > 0) { + /* Kill this entry */ + + dns_resource_record_unref((*a)->items[i].rr); + memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); + (*a)->n_rrs --; + continue; + + } else + /* Keep this entry */ + i++; + } + + return 1; +} + int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags) { DnsResourceRecord *rr_source; int ifindex_source, r; @@ -708,3 +821,40 @@ void dns_answer_dump(DnsAnswer *answer, FILE *f) { fputc('\n', f); } } + +bool dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname) { + DnsResourceRecord *rr; + int r; + + assert(cname); + + /* Checks whether the answer contains a DNAME record that indicates that the specified CNAME record is + * synthesized from it */ + + if (cname->key->type != DNS_TYPE_CNAME) + return 0; + + DNS_ANSWER_FOREACH(rr, a) { + _cleanup_free_ char *n = NULL; + + if (rr->key->type != DNS_TYPE_DNAME) + continue; + if (rr->key->class != cname->key->class) + continue; + + r = dns_name_change_suffix(cname->cname.name, rr->dname.name, DNS_RESOURCE_KEY_NAME(rr->key), &n); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_name_equal(n, DNS_RESOURCE_KEY_NAME(cname->key)); + if (r < 0) + return r; + if (r > 0) + return 1; + + } + + return 0; +} diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h index 28ded3b252..1875fd6136 100644 --- a/src/resolve/resolved-dns-answer.h +++ b/src/resolve/resolved-dns-answer.h @@ -64,6 +64,7 @@ int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *combined_flags); int dns_answer_contains_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags); int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a); +int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone); int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); @@ -77,9 +78,13 @@ int dns_answer_reserve(DnsAnswer **a, unsigned n_free); int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free); int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key); +int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rr); + int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags); int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags); +bool dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname); + static inline unsigned dns_answer_size(DnsAnswer *a) { return a ? a->n_rrs : 0; } diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index 1c7dd56b3b..fdb34d11df 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -247,6 +247,19 @@ static int dns_cache_link_item(DnsCache *c, DnsCacheItem *i) { first = hashmap_get(c->by_key, i->key); if (first) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; + + /* Keep a reference to the original key, while we manipulate the list. */ + k = dns_resource_key_ref(first->key); + + /* Now, try to reduce the number of keys we keep */ + dns_resource_key_reduce(&first->key, &i->key); + + if (first->rr) + dns_resource_key_reduce(&first->rr->key, &i->key); + if (i->rr) + dns_resource_key_reduce(&i->rr->key, &i->key); + LIST_PREPEND(by_key, first, i); assert_se(hashmap_replace(c->by_key, first->key, first) >= 0); } else { @@ -273,13 +286,13 @@ static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) { return NULL; } -static usec_t calculate_until(DnsResourceRecord *rr, usec_t timestamp, bool use_soa_minimum) { +static usec_t calculate_until(DnsResourceRecord *rr, uint32_t nsec_ttl, usec_t timestamp, bool use_soa_minimum) { uint32_t ttl; usec_t u; assert(rr); - ttl = rr->ttl; + ttl = MIN(rr->ttl, nsec_ttl); if (rr->key->type == DNS_TYPE_SOA && use_soa_minimum) { /* If this is a SOA RR, and it is requested, clamp to * the SOA's minimum field. This is used when we do @@ -339,7 +352,7 @@ static void dns_cache_item_update_positive( dns_resource_key_unref(i->key); i->key = dns_resource_key_ref(rr->key); - i->until = calculate_until(rr, timestamp, false); + i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); i->authenticated = authenticated; i->shared_owner = shared_owner; @@ -420,7 +433,7 @@ static int dns_cache_put_positive( i->type = DNS_CACHE_POSITIVE; i->key = dns_resource_key_ref(rr->key); i->rr = dns_resource_record_ref(rr); - i->until = calculate_until(rr, timestamp, false); + i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); i->authenticated = authenticated; i->shared_owner = shared_owner; i->owner_family = owner_family; @@ -448,6 +461,7 @@ static int dns_cache_put_negative( DnsResourceKey *key, int rcode, bool authenticated, + uint32_t nsec_ttl, usec_t timestamp, DnsResourceRecord *soa, int owner_family, @@ -470,13 +484,13 @@ static int dns_cache_put_negative( if (dns_type_is_pseudo(key->type)) return 0; - if (soa->soa.minimum <= 0 || soa->ttl <= 0) { + if (nsec_ttl <= 0 || soa->soa.minimum <= 0 || soa->ttl <= 0) { if (log_get_max_level() >= LOG_DEBUG) { r = dns_resource_key_to_string(key, &key_str); if (r < 0) return r; - log_debug("Not caching negative entry with zero SOA TTL: %s", key_str); + log_debug("Not caching negative entry with zero SOA/NSEC/NSEC3 TTL: %s", key_str); } return 0; @@ -496,7 +510,7 @@ static int dns_cache_put_negative( return -ENOMEM; i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN; - i->until = calculate_until(soa, timestamp, true); + i->until = calculate_until(soa, nsec_ttl, timestamp, true); i->authenticated = authenticated; i->owner_family = owner_family; i->owner_address = *owner_address; @@ -571,6 +585,7 @@ int dns_cache_put( int rcode, DnsAnswer *answer, bool authenticated, + uint32_t nsec_ttl, usec_t timestamp, int owner_family, const union in_addr_union *owner_address) { @@ -669,6 +684,7 @@ int dns_cache_put( key, rcode, authenticated, + nsec_ttl, timestamp, soa, owner_family, owner_address); diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h index 9c85ca4c58..e61b285df4 100644 --- a/src/resolve/resolved-dns-cache.h +++ b/src/resolve/resolved-dns-cache.h @@ -41,7 +41,7 @@ typedef struct DnsCache { void dns_cache_flush(DnsCache *c); void dns_cache_prune(DnsCache *c); -int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, bool authenticated, usec_t timestamp, int owner_family, const union in_addr_union *owner_address); +int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, bool authenticated, uint32_t nsec_ttl, usec_t timestamp, int owner_family, const union in_addr_union *owner_address); int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **answer, bool *authenticated); int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address); diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 1182201b7d..1f48f588ce 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -35,15 +35,12 @@ * * TODO: * - * - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing) - * - multi-label zone compatibility - * - cname/dname compatibility - * - per-interface DNSSEC setting - * - nxdomain on qname - * - retry on failed validation? - * - DNSSEC key revocation support? https://tools.ietf.org/html/rfc5011 - * - when doing negative caching, use NSEC/NSEC3 RR instead of SOA for TTL - * + * - bus calls to override DNSEC setting per interface + * - log all DNSSEC downgrades + * - log all RRs that failed validation + * - enable by default + * - Allow clients to request DNSSEC even if DNSSEC is off + * - make sure when getting an NXDOMAIN response through CNAME, we still process the first CNAMEs in the packet * */ #define VERIFY_RRS_MAX 256 @@ -52,8 +49,8 @@ /* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */ #define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE) -/* Maximum number of NSEC3 iterations we'll do. */ -#define NSEC3_ITERATIONS_MAX 2048 +/* Maximum number of NSEC3 iterations we'll do. RFC5155 says 2500 shall be the maximum useful value */ +#define NSEC3_ITERATIONS_MAX 2500 /* * The DNSSEC Chain of trust: @@ -79,9 +76,9 @@ static void initialize_libgcrypt(void) { gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); } -uint16_t dnssec_keytag(DnsResourceRecord *dnskey) { +uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) { const uint8_t *p; - uint32_t sum; + uint32_t sum, f; size_t i; /* The algorithm from RFC 4034, Appendix B. */ @@ -89,8 +86,12 @@ uint16_t dnssec_keytag(DnsResourceRecord *dnskey) { assert(dnskey); assert(dnskey->key->type == DNS_TYPE_DNSKEY); - sum = (uint32_t) dnskey->dnskey.flags + - ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm); + f = (uint32_t) dnskey->dnskey.flags; + + if (mask_revoke) + f &= ~DNSKEY_FLAG_REVOKE; + + sum = f + ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm); p = dnskey->dnskey.key; @@ -116,15 +117,15 @@ static int rr_compare(const void *a, const void *b) { assert(*y); assert((*y)->wire_format); - m = MIN((*x)->wire_format_size, (*y)->wire_format_size); + m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y)); - r = memcmp((*x)->wire_format, (*y)->wire_format, m); + r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m); if (r != 0) return r; - if ((*x)->wire_format_size < (*y)->wire_format_size) + if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) return -1; - else if ((*x)->wire_format_size > (*y)->wire_format_size) + else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) return 1; return 0; @@ -424,6 +425,57 @@ static void md_add_uint32(gcry_md_hd_t md, uint32_t v) { gcry_md_write(md, &v, sizeof(v)); } +static int dnssec_rrsig_prepare(DnsResourceRecord *rrsig) { + int n_key_labels, n_signer_labels; + const char *name; + int r; + + /* Checks whether the specified RRSIG RR is somewhat valid, and initializes the .n_skip_labels_source and + * .n_skip_labels_signer fields so that we can use them later on. */ + + assert(rrsig); + assert(rrsig->key->type == DNS_TYPE_RRSIG); + + /* Check if this RRSIG RR is already prepared */ + if (rrsig->n_skip_labels_source != (unsigned) -1) + return 0; + + if (rrsig->rrsig.inception > rrsig->rrsig.expiration) + return -EINVAL; + + name = DNS_RESOURCE_KEY_NAME(rrsig->key); + + n_key_labels = dns_name_count_labels(name); + if (n_key_labels < 0) + return n_key_labels; + if (rrsig->rrsig.labels > n_key_labels) + return -EINVAL; + + n_signer_labels = dns_name_count_labels(rrsig->rrsig.signer); + if (n_signer_labels < 0) + return n_signer_labels; + if (n_signer_labels > rrsig->rrsig.labels) + return -EINVAL; + + r = dns_name_skip(name, n_key_labels - n_signer_labels, &name); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + /* Check if the signer is really a suffix of us */ + r = dns_name_equal(name, rrsig->rrsig.signer); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + rrsig->n_skip_labels_source = n_key_labels - rrsig->rrsig.labels; + rrsig->n_skip_labels_signer = n_key_labels - n_signer_labels; + + return 0; +} + static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) { usec_t expiration, inception, skew; @@ -436,8 +488,9 @@ static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) { expiration = rrsig->rrsig.expiration * USEC_PER_SEC; inception = rrsig->rrsig.inception * USEC_PER_SEC; + /* Consider inverted validity intervals as expired */ if (inception > expiration) - return -EKEYREJECTED; + return true; /* Permit a certain amount of clock skew of 10% of the valid * time range. This takes inspiration from unbound's @@ -492,21 +545,52 @@ static int algorithm_to_gcrypt_md(uint8_t algorithm) { } } +static void dnssec_fix_rrset_ttl( + DnsResourceRecord *list[], + unsigned n, + DnsResourceRecord *rrsig, + usec_t realtime) { + + unsigned k; + + assert(list); + assert(n > 0); + assert(rrsig); + + for (k = 0; k < n; k++) { + DnsResourceRecord *rr = list[k]; + + /* Pick the TTL as the minimum of the RR's TTL, the + * RR's original TTL according to the RRSIG and the + * RRSIG's own TTL, see RFC 4035, Section 5.3.3 */ + rr->ttl = MIN3(rr->ttl, rrsig->rrsig.original_ttl, rrsig->ttl); + rr->expiry = rrsig->rrsig.expiration * USEC_PER_SEC; + + /* Copy over information about the signer and wildcard source of synthesis */ + rr->n_skip_labels_source = rrsig->n_skip_labels_source; + rr->n_skip_labels_signer = rrsig->n_skip_labels_signer; + } + + rrsig->expiry = rrsig->rrsig.expiration * USEC_PER_SEC; +} + int dnssec_verify_rrset( DnsAnswer *a, - DnsResourceKey *key, + const DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result) { uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX]; - size_t hash_size; - void *hash; DnsResourceRecord **list, *rr; + const char *source, *name; gcry_md_hd_t md = NULL; int r, md_algorithm; size_t k, n = 0; + size_t hash_size; + void *hash; + bool wildcard; assert(key); assert(rrsig); @@ -527,6 +611,14 @@ int dnssec_verify_rrset( if (md_algorithm < 0) return md_algorithm; + r = dnssec_rrsig_prepare(rrsig); + if (r == -EINVAL) { + *result = DNSSEC_INVALID; + return r; + } + if (r < 0) + return r; + r = dnssec_rrsig_expired(rrsig, realtime); if (r < 0) return r; @@ -535,8 +627,54 @@ int dnssec_verify_rrset( return 0; } + name = DNS_RESOURCE_KEY_NAME(key); + + /* Some keys may only appear signed in the zone apex, and are invalid anywhere else. (SOA, NS...) */ + if (dns_type_apex_only(rrsig->rrsig.type_covered)) { + r = dns_name_equal(rrsig->rrsig.signer, name); + if (r < 0) + return r; + if (r == 0) { + *result = DNSSEC_INVALID; + return 0; + } + } + + /* OTOH DS RRs may not appear in the zone apex, but are valid everywhere else. */ + if (rrsig->rrsig.type_covered == DNS_TYPE_DS) { + r = dns_name_equal(rrsig->rrsig.signer, name); + if (r < 0) + return r; + if (r > 0) { + *result = DNSSEC_INVALID; + return 0; + } + } + + /* Determine the "Source of Synthesis" and whether this is a wildcard RRSIG */ + r = dns_name_suffix(name, rrsig->rrsig.labels, &source); + if (r < 0) + return r; + if (r > 0 && !dns_type_may_wildcard(rrsig->rrsig.type_covered)) { + /* We refuse to validate NSEC3 or SOA RRs that are synthesized from wildcards */ + *result = DNSSEC_INVALID; + return 0; + } + if (r == 1) { + /* If we stripped a single label, then let's see if that maybe was "*". If so, we are not really + * synthesized from a wildcard, we are the wildcard itself. Treat that like a normal name. */ + r = dns_name_startswith(name, "*"); + if (r < 0) + return r; + if (r > 0) + source = name; + + wildcard = r == 0; + } else + wildcard = r > 0; + /* Collect all relevant RRs in a single array, so that we can look at the RRset */ - list = newa(DnsResourceRecord *, a->n_rrs); + list = newa(DnsResourceRecord *, dns_answer_size(a)); DNS_ANSWER_FOREACH(rr, a) { r = dns_resource_key_equal(key, rr->key); @@ -585,32 +723,30 @@ int dnssec_verify_rrset( goto finish; gcry_md_write(md, wire_format_name, r); + /* Convert the source of synthesis into wire format */ + r = dns_name_to_wire_format(source, wire_format_name, sizeof(wire_format_name), true); + if (r < 0) + goto finish; + for (k = 0; k < n; k++) { - const char *suffix; size_t l; + rr = list[k]; - r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &suffix); - if (r < 0) - goto finish; - if (r > 0) /* This is a wildcard! */ + /* Hash the source of synthesis. If this is a wildcard, then prefix it with the *. label */ + if (wildcard) gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2); - - r = dns_name_to_wire_format(suffix, wire_format_name, sizeof(wire_format_name), true); - if (r < 0) - goto finish; gcry_md_write(md, wire_format_name, r); md_add_uint16(md, rr->key->type); md_add_uint16(md, rr->key->class); md_add_uint32(md, rrsig->rrsig.original_ttl); - assert(rr->wire_format_rdata_offset <= rr->wire_format_size); - l = rr->wire_format_size - rr->wire_format_rdata_offset; + l = DNS_RESOURCE_RECORD_RDATA_SIZE(rr); assert(l <= 0xFFFF); md_add_uint16(md, (uint16_t) l); - gcry_md_write(md, (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset, l); + gcry_md_write(md, DNS_RESOURCE_RECORD_RDATA(rr), l); } hash = gcry_md_read(md, 0); @@ -646,7 +782,17 @@ int dnssec_verify_rrset( if (r < 0) goto finish; - *result = r ? DNSSEC_VALIDATED : DNSSEC_INVALID; + /* Now, fix the ttl, expiry, and remember the synthesizing source and the signer */ + if (r > 0) + dnssec_fix_rrset_ttl(list, n, rrsig, realtime); + + if (r == 0) + *result = DNSSEC_INVALID; + else if (wildcard) + *result = DNSSEC_VALIDATED_WILDCARD; + else + *result = DNSSEC_VALIDATED; + r = 0; finish: @@ -654,7 +800,7 @@ finish: return r; } -int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) { +int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) { assert(rrsig); assert(dnskey); @@ -671,20 +817,20 @@ int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnske return 0; if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) return 0; + if (!revoked_ok && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE)) + return 0; if (dnskey->dnskey.protocol != 3) return 0; if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm) return 0; - if (dnssec_keytag(dnskey) != rrsig->rrsig.key_tag) + if (dnssec_keytag(dnskey, false) != rrsig->rrsig.key_tag) return 0; return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer); } int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) { - int r; - assert(key); assert(rrsig); @@ -697,51 +843,16 @@ int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) if (rrsig->rrsig.type_covered != key->type) return 0; - /* Make sure signer is a parent of the RRset */ - r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rrsig->key), rrsig->rrsig.signer); - if (r <= 0) - return r; - - /* Make sure the owner name has at least as many labels as the "label" fields indicates. */ - r = dns_name_count_labels(DNS_RESOURCE_KEY_NAME(rrsig->key)); - if (r < 0) - return r; - if (r < rrsig->rrsig.labels) - return 0; - return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key)); } -static int dnssec_fix_rrset_ttl(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord *rrsig, usec_t realtime) { - DnsResourceRecord *rr; - int r; - - assert(key); - assert(rrsig); - - DNS_ANSWER_FOREACH(rr, a) { - r = dns_resource_key_equal(key, rr->key); - if (r < 0) - return r; - if (r == 0) - continue; - - /* Pick the TTL as the minimum of the RR's TTL, the - * RR's original TTL according to the RRSIG and the - * RRSIG's own TTL, see RFC 4035, Section 5.3.3 */ - rr->ttl = MIN3(rr->ttl, rrsig->rrsig.original_ttl, rrsig->ttl); - rr->expiry = rrsig->rrsig.expiration * USEC_PER_SEC; - } - - return 0; -} - int dnssec_verify_rrset_search( DnsAnswer *a, - DnsResourceKey *key, + const DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, - DnssecResult *result) { + DnssecResult *result, + DnsResourceRecord **ret_rrsig) { bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false; DnsResourceRecord *rrsig; @@ -777,7 +888,7 @@ int dnssec_verify_rrset_search( continue; /* Is this a DNSKEY RR that matches they key of our RRSIG? */ - r = dnssec_rrsig_match_dnskey(rrsig, dnskey); + r = dnssec_rrsig_match_dnskey(rrsig, dnskey, false); if (r < 0) return r; if (r == 0) @@ -801,13 +912,13 @@ int dnssec_verify_rrset_search( switch (one_result) { case DNSSEC_VALIDATED: + case DNSSEC_VALIDATED_WILDCARD: /* Yay, the RR has been validated, * return immediately, but fix up the expiry */ - r = dnssec_fix_rrset_ttl(a, key, rrsig, realtime); - if (r < 0) - return r; + if (ret_rrsig) + *ret_rrsig = rrsig; - *result = DNSSEC_VALIDATED; + *result = one_result; return 0; case DNSSEC_INVALID: @@ -852,6 +963,9 @@ int dnssec_verify_rrset_search( else *result = DNSSEC_NO_SIGNATURE; + if (ret_rrsig) + *ret_rrsig = NULL; + return 0; } @@ -883,23 +997,11 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { return -ENOBUFS; for (;;) { - size_t i; - r = dns_label_unescape(&n, buffer, buffer_max); if (r < 0) return r; if (r == 0) break; - if (r > 0) { - int k; - - /* DNSSEC validation is always done on the ASCII version of the label */ - k = dns_label_apply_idna(buffer, r, buffer, buffer_max); - if (k < 0) - return k; - if (k > 0) - r = k; - } if (buffer_max < (size_t) r + 2) return -ENOBUFS; @@ -911,11 +1013,7 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { if (memchr(buffer, '.', r)) return -EINVAL; - for (i = 0; i < (size_t) r; i ++) { - if (buffer[i] >= 'A' && buffer[i] <= 'Z') - buffer[i] = buffer[i] - 'A' + 'a'; - } - + ascii_strlower_n(buffer, (size_t) r); buffer[r] = '.'; buffer += r + 1; @@ -957,7 +1055,7 @@ static int digest_to_gcrypt_md(uint8_t algorithm) { } } -int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) { +int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) { char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX]; gcry_md_hd_t md = NULL; size_t hash_size; @@ -975,12 +1073,14 @@ int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) { return -EINVAL; if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) return -EKEYREJECTED; + if (!mask_revoke && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE)) + return -EKEYREJECTED; if (dnskey->dnskey.protocol != 3) return -EKEYREJECTED; if (dnskey->dnskey.algorithm != ds->ds.algorithm) return 0; - if (dnssec_keytag(dnskey) != ds->ds.key_tag) + if (dnssec_keytag(dnskey, mask_revoke) != ds->ds.key_tag) return 0; initialize_libgcrypt(); @@ -1004,7 +1104,10 @@ int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) { return -EIO; gcry_md_write(md, owner_name, r); - md_add_uint16(md, dnskey->dnskey.flags); + if (mask_revoke) + md_add_uint16(md, dnskey->dnskey.flags & ~DNSKEY_FLAG_REVOKE); + else + md_add_uint16(md, dnskey->dnskey.flags); md_add_uint8(md, dnskey->dnskey.protocol); md_add_uint8(md, dnskey->dnskey.algorithm); gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size); @@ -1022,7 +1125,7 @@ finish: return r; } -int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { +int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { DnsResourceRecord *ds; DnsAnswerFlags flags; int r; @@ -1039,7 +1142,6 @@ int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ if (ds->key->type != DNS_TYPE_DS) continue; - if (ds->key->class != dnskey->key->class) continue; @@ -1049,7 +1151,9 @@ int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ if (r == 0) continue; - r = dnssec_verify_dnskey(dnskey, ds); + r = dnssec_verify_dnskey_by_ds(dnskey, ds, false); + if (IN_SET(r, -EKEYREJECTED, -EOPNOTSUPP)) + return 0; /* The DNSKEY is revoked or otherwise invalid, or we don't support the digest algorithm */ if (r < 0) return r; if (r > 0) @@ -1073,7 +1177,7 @@ static int nsec3_hash_to_gcrypt_md(uint8_t algorithm) { } } -int dnssec_nsec3_hash(const DnsResourceRecord *nsec3, const char *name, void *ret) { +int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX]; gcry_md_hd_t md = NULL; size_t hash_size; @@ -1089,8 +1193,10 @@ int dnssec_nsec3_hash(const DnsResourceRecord *nsec3, const char *name, void *re if (nsec3->key->type != DNS_TYPE_NSEC3) return -EINVAL; - if (nsec3->nsec3.iterations > NSEC3_ITERATIONS_MAX) + if (nsec3->nsec3.iterations > NSEC3_ITERATIONS_MAX) { + log_debug("Ignoring NSEC3 RR %s with excessive number of iterations.", dns_resource_record_to_string(nsec3)); return -EOPNOTSUPP; + } algorithm = nsec3_hash_to_gcrypt_md(nsec3->nsec3.algorithm); if (algorithm < 0) @@ -1144,7 +1250,7 @@ finish: return r; } -static int nsec3_is_good(DnsResourceRecord *rr, DnsAnswerFlags flags, DnsResourceRecord *nsec3) { +static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) { const char *a, *b; int r; @@ -1164,6 +1270,13 @@ static int nsec3_is_good(DnsResourceRecord *rr, DnsAnswerFlags flags, DnsResourc if (rr->nsec3.iterations > NSEC3_ITERATIONS_MAX) return 0; + /* Ignore NSEC3 RRs generated from wildcards */ + if (rr->n_skip_labels_source != 0) + return 0; + /* Ignore NSEC3 RRs that are located anywhere else than one label below the zone */ + if (rr->n_skip_labels_signer != 1) + return 0; + if (!nsec3) return 1; @@ -1197,11 +1310,32 @@ static int nsec3_is_good(DnsResourceRecord *rr, DnsAnswerFlags flags, DnsResourc if (r == 0) return 0; + /* Make sure both have the same parent */ return dns_name_equal(a, b); } -static int nsec3_hashed_domain(const DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) { - _cleanup_free_ char *l = NULL, *hashed_domain = NULL; +static int nsec3_hashed_domain_format(const uint8_t *hashed, size_t hashed_size, const char *zone, char **ret) { + _cleanup_free_ char *l = NULL; + char *j; + + assert(hashed); + assert(hashed_size > 0); + assert(zone); + assert(ret); + + l = base32hexmem(hashed, hashed_size, false); + if (!l) + return -ENOMEM; + + j = strjoin(l, ".", zone, NULL); + if (!j) + return -ENOMEM; + + *ret = j; + return (int) hashed_size; +} + +static int nsec3_hashed_domain_make(DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) { uint8_t hashed[DNSSEC_HASH_SIZE_MAX]; int hashed_size; @@ -1214,18 +1348,7 @@ static int nsec3_hashed_domain(const DnsResourceRecord *nsec3, const char *domai if (hashed_size < 0) return hashed_size; - l = base32hexmem(hashed, hashed_size, false); - if (!l) - return -ENOMEM; - - hashed_domain = strjoin(l, ".", zone, NULL); - if (!hashed_domain) - return -ENOMEM; - - *ret = hashed_domain; - hashed_domain = NULL; - - return hashed_size; + return nsec3_hashed_domain_format(hashed, (size_t) hashed_size, zone, ret); } /* See RFC 5155, Section 8 @@ -1238,17 +1361,16 @@ static int nsec3_hashed_domain(const DnsResourceRecord *nsec3, const char *domai * that there is no proof either way. The latter is the case if a the proof of non-existence of a given * name uses an NSEC3 record with the opt-out bit set. Lastly, if we are given insufficient NSEC3 records * to conclude anything we indicate this by returning NO_RR. */ -static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) { - _cleanup_free_ char *next_closer_domain = NULL, *wildcard = NULL, *wildcard_domain = NULL; - const char *zone, *p, *pp = NULL; - DnsResourceRecord *rr, *enclosure_rr, *suffix_rr, *wildcard_rr = NULL; +static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { + _cleanup_free_ char *next_closer_domain = NULL, *wildcard_domain = NULL; + const char *zone, *p, *pp = NULL, *wildcard; + DnsResourceRecord *rr, *enclosure_rr, *zone_rr, *wildcard_rr = NULL; DnsAnswerFlags flags; int hashed_size, r; bool a, no_closer = false, no_wildcard = false, optout = false; assert(key); assert(result); - assert(authenticated); /* First step, find the zone name and the NSEC3 parameters of the zone. * it is sufficient to look for the longest common suffix we find with @@ -1257,14 +1379,14 @@ static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecR * parameters. */ zone = DNS_RESOURCE_KEY_NAME(key); for (;;) { - DNS_ANSWER_FOREACH_FLAGS(suffix_rr, flags, answer) { - r = nsec3_is_good(suffix_rr, flags, NULL); + DNS_ANSWER_FOREACH_FLAGS(zone_rr, flags, answer) { + r = nsec3_is_good(zone_rr, NULL); if (r < 0) return r; if (r == 0) continue; - r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(suffix_rr->key), 1, zone); + r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(zone_rr->key), 1, zone); if (r < 0) return r; if (r > 0) @@ -1288,7 +1410,7 @@ found_zone: for (;;) { _cleanup_free_ char *hashed_domain = NULL; - hashed_size = nsec3_hashed_domain(suffix_rr, p, zone, &hashed_domain); + hashed_size = nsec3_hashed_domain_make(zone_rr, p, zone, &hashed_domain); if (hashed_size == -EOPNOTSUPP) { *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM; return 0; @@ -1298,7 +1420,7 @@ found_zone: DNS_ANSWER_FOREACH_FLAGS(enclosure_rr, flags, answer) { - r = nsec3_is_good(enclosure_rr, flags, suffix_rr); + r = nsec3_is_good(enclosure_rr, zone_rr); if (r < 0) return r; if (r == 0) @@ -1357,45 +1479,41 @@ found_closest_encloser: else *result = DNSSEC_NSEC_NODATA; - *authenticated = a; + if (authenticated) + *authenticated = a; + if (ttl) + *ttl = enclosure_rr->ttl; return 0; } /* Prove that there is no next closer and whether or not there is a wildcard domain. */ - wildcard = strappend("*.", p); - if (!wildcard) - return -ENOMEM; - - r = nsec3_hashed_domain(enclosure_rr, wildcard, zone, &wildcard_domain); + wildcard = strjoina("*.", p); + r = nsec3_hashed_domain_make(enclosure_rr, wildcard, zone, &wildcard_domain); if (r < 0) return r; if (r != hashed_size) return -EBADMSG; - r = nsec3_hashed_domain(enclosure_rr, pp, zone, &next_closer_domain); + r = nsec3_hashed_domain_make(enclosure_rr, pp, zone, &next_closer_domain); if (r < 0) return r; if (r != hashed_size) return -EBADMSG; DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - _cleanup_free_ char *label = NULL, *next_hashed_domain = NULL; + _cleanup_free_ char *next_hashed_domain = NULL; - r = nsec3_is_good(rr, flags, suffix_rr); + r = nsec3_is_good(rr, zone_rr); if (r < 0) return r; if (r == 0) continue; - label = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false); - if (!label) - return -ENOMEM; - - next_hashed_domain = strjoin(label, ".", zone, NULL); - if (!next_hashed_domain) - return -ENOMEM; + r = nsec3_hashed_domain_format(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, zone, &next_hashed_domain); + if (r < 0) + return r; r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), next_closer_domain, next_hashed_domain); if (r < 0) @@ -1440,7 +1558,6 @@ found_closest_encloser: if (!no_closer) { *result = DNSSEC_NSEC_NO_RR; - return 0; } @@ -1476,80 +1593,531 @@ found_closest_encloser: } } - *authenticated = a; + if (authenticated) + *authenticated = a; + + if (ttl) + *ttl = enclosure_rr->ttl; return 0; } -int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated) { - DnsResourceRecord *rr; - bool have_nsec3 = false; +static int dnssec_nsec_wildcard_equal(DnsResourceRecord *rr, const char *name) { + char label[DNS_LABEL_MAX]; + const char *n; + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether the specified RR has a name beginning in "*.", and if the rest is a suffix of our name */ + + if (rr->n_skip_labels_source != 1) + return 0; + + n = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_label_unescape(&n, label, sizeof(label)); + if (r <= 0) + return r; + if (r != 1 || label[0] != '*') + return 0; + + return dns_name_endswith(name, n); +} + +static int dnssec_nsec_in_path(DnsResourceRecord *rr, const char *name) { + const char *nn, *common_suffix; + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether the specified nsec RR indicates that name is an empty non-terminal (ENT) + * + * A couple of examples: + * + * NSEC bar → waldo.foo.bar: indicates that foo.bar exists and is an ENT + * NSEC waldo.foo.bar → yyy.zzz.xoo.bar: indicates that xoo.bar and zzz.xoo.bar exist and are ENTs + * NSEC yyy.zzz.xoo.bar → bar: indicates pretty much nothing about ENTs + */ + + /* First, determine parent of next domain. */ + nn = rr->nsec.next_domain_name; + r = dns_name_parent(&nn); + if (r <= 0) + return r; + + /* If the name we just determined is not equal or child of the name we are interested in, then we can't say + * anything at all. */ + r = dns_name_endswith(nn, name); + if (r <= 0) + return r; + + /* If the name we we are interested in is not a prefix of the common suffix of the NSEC RR's owner and next domain names, then we can't say anything either. */ + r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix); + if (r < 0) + return r; + + return dns_name_endswith(name, common_suffix); +} + +static int dnssec_nsec_from_parent_zone(DnsResourceRecord *rr, const char *name) { + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether this NSEC originates to the parent zone or the child zone. */ + + r = dns_name_parent(&name); + if (r <= 0) + return r; + + r = dns_name_equal(name, DNS_RESOURCE_KEY_NAME(rr->key)); + if (r <= 0) + return r; + + /* DNAME, and NS without SOA is an indication for a delegation. */ + if (bitmap_isset(rr->nsec.types, DNS_TYPE_DNAME)) + return 1; + + if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) && !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) + return 1; + + return 0; +} + +static int dnssec_nsec_covers(DnsResourceRecord *rr, const char *name) { + const char *common_suffix, *p; + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether the "Next Closer" is witin the space covered by the specified RR. */ + + r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix); + if (r < 0) + return r; + + for (;;) { + p = name; + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r == 0) + return 0; + + r = dns_name_equal(name, common_suffix); + if (r < 0) + return r; + if (r > 0) + break; + } + + /* p is now the "Next Closer". */ + + return dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), p, rr->nsec.next_domain_name); +} + +static int dnssec_nsec_covers_wildcard(DnsResourceRecord *rr, const char *name) { + const char *common_suffix, *wc; + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether the "Wildcard at the Closest Encloser" is within the space covered by the specified + * RR. Specifically, checks whether 'name' has the common suffix of the NSEC RR's owner and next names as + * suffix, and whether the NSEC covers the name generated by that suffix prepended with an asterisk label. + * + * NSEC bar → waldo.foo.bar: indicates that *.bar and *.foo.bar do not exist + * NSEC waldo.foo.bar → yyy.zzz.xoo.bar: indicates that *.xoo.bar and *.zzz.xoo.bar do not exist (and more ...) + * NSEC yyy.zzz.xoo.bar → bar: indicates that a number of wildcards don#t exist either... + */ + + r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix); + if (r < 0) + return r; + + /* If the common suffix is not shared by the name we are interested in, it has nothing to say for us. */ + r = dns_name_endswith(name, common_suffix); + if (r <= 0) + return r; + + wc = strjoina("*.", common_suffix, NULL); + return dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), wc, rr->nsec.next_domain_name); +} + +int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { + bool have_nsec3 = false, covering_rr_authenticated = false, wildcard_rr_authenticated = false; + DnsResourceRecord *rr, *covering_rr = NULL, *wildcard_rr = NULL; DnsAnswerFlags flags; + const char *name; int r; assert(key); assert(result); - assert(authenticated); /* Look for any NSEC/NSEC3 RRs that say something about the specified key. */ + name = DNS_RESOURCE_KEY_NAME(key); + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { if (rr->key->class != key->class) continue; - switch (rr->key->type) { + have_nsec3 = have_nsec3 || (rr->key->type == DNS_TYPE_NSEC3); - case DNS_TYPE_NSEC: + if (rr->key->type != DNS_TYPE_NSEC) + continue; + + /* The following checks only make sense for NSEC RRs that are not expanded from a wildcard */ + r = dns_resource_record_is_synthetic(rr); + if (r < 0) + return r; + if (r > 0) + continue; - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key)); + /* Check if this is a direct match. If so, we have encountered a NODATA case */ + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), name); + if (r < 0) + return r; + if (r == 0) { + /* If it's not a direct match, maybe it's a wild card match? */ + r = dnssec_nsec_wildcard_equal(rr, name); if (r < 0) return r; - if (r > 0) { - if (bitmap_isset(rr->nsec.types, key->type)) - *result = DNSSEC_NSEC_FOUND; - else if (bitmap_isset(rr->nsec.types, DNS_TYPE_CNAME)) - *result = DNSSEC_NSEC_CNAME; - else - *result = DNSSEC_NSEC_NODATA; - *authenticated = flags & DNS_ANSWER_AUTHENTICATED; - return 0; + } + if (r > 0) { + if (key->type == DNS_TYPE_DS) { + /* If we look for a DS RR and the server sent us the NSEC RR of the child zone + * we have a problem. For DS RRs we want the NSEC RR from the parent */ + if (bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) + continue; + } else { + /* For all RR types, ensure that if NS is set SOA is set too, so that we know + * we got the child's NSEC. */ + if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) && + !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) + continue; } - r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key), rr->nsec.next_domain_name); - if (r < 0) - return r; - if (r > 0) { - *result = DNSSEC_NSEC_NXDOMAIN; + if (bitmap_isset(rr->nsec.types, key->type)) + *result = DNSSEC_NSEC_FOUND; + else if (bitmap_isset(rr->nsec.types, DNS_TYPE_CNAME)) + *result = DNSSEC_NSEC_CNAME; + else + *result = DNSSEC_NSEC_NODATA; + + if (authenticated) *authenticated = flags & DNS_ANSWER_AUTHENTICATED; - return 0; - } - break; + if (ttl) + *ttl = rr->ttl; - case DNS_TYPE_NSEC3: - have_nsec3 = true; - break; + return 0; + } + + /* Check if the name we are looking for is an empty non-terminal within the owner or next name + * of the NSEC RR. */ + r = dnssec_nsec_in_path(rr, name); + if (r < 0) + return r; + if (r > 0) { + *result = DNSSEC_NSEC_NODATA; + + if (authenticated) + *authenticated = flags & DNS_ANSWER_AUTHENTICATED; + if (ttl) + *ttl = rr->ttl; + + return 0; } + + /* The following two "covering" checks, are not useful if the NSEC is from the parent */ + r = dnssec_nsec_from_parent_zone(rr, name); + if (r < 0) + return r; + if (r > 0) + continue; + + /* Check if this NSEC RR proves the absence of an explicit RR under this name */ + r = dnssec_nsec_covers(rr, name); + if (r < 0) + return r; + if (r > 0 && (!covering_rr || !covering_rr_authenticated)) { + covering_rr = rr; + covering_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; + } + + /* Check if this NSEC RR proves the absence of a wildcard RR under this name */ + r = dnssec_nsec_covers_wildcard(rr, name); + if (r < 0) + return r; + if (r > 0 && (!wildcard_rr || !wildcard_rr_authenticated)) { + wildcard_rr = rr; + wildcard_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; + } + } + + if (covering_rr && wildcard_rr) { + /* If we could prove that neither the name itself, nor the wildcard at the closest encloser exists, we + * proved the NXDOMAIN case. */ + *result = DNSSEC_NSEC_NXDOMAIN; + + if (authenticated) + *authenticated = covering_rr_authenticated && wildcard_rr_authenticated; + if (ttl) + *ttl = MIN(covering_rr->ttl, wildcard_rr->ttl); + + return 0; } /* OK, this was not sufficient. Let's see if NSEC3 can help. */ if (have_nsec3) - return dnssec_test_nsec3(answer, key, result, authenticated); + return dnssec_test_nsec3(answer, key, result, authenticated, ttl); /* No approproate NSEC RR found, report this. */ *result = DNSSEC_NSEC_NO_RR; 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); +int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name, const char *zone, bool *authenticated) { + DnsResourceRecord *rr; + DnsAnswerFlags flags; + int r; + + assert(name); + assert(zone); + + /* Checks whether there's an NSEC/NSEC3 that proves that the specified 'name' is non-existing in the specified + * 'zone'. The 'zone' must be a suffix of the 'name'. */ + + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + bool found = false; + + if (rr->key->type != type && type != DNS_TYPE_ANY) + continue; + + switch (rr->key->type) { + + case DNS_TYPE_NSEC: + + /* We only care for NSEC RRs from the indicated zone */ + r = dns_resource_record_is_signer(rr, zone); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), name, rr->nsec.next_domain_name); + if (r < 0) + return r; + + found = r > 0; + break; + + case DNS_TYPE_NSEC3: { + _cleanup_free_ char *hashed_domain = NULL, *next_hashed_domain = NULL; + + /* We only care for NSEC3 RRs from the indicated zone */ + r = dns_resource_record_is_signer(rr, zone); + if (r < 0) + return r; + if (r == 0) + continue; + + r = nsec3_is_good(rr, NULL); + if (r < 0) + return r; + if (r == 0) + break; + + /* Format the domain we are testing with the NSEC3 RR's hash function */ + r = nsec3_hashed_domain_make( + rr, + name, + zone, + &hashed_domain); + if (r < 0) + return r; + if ((size_t) r != rr->nsec3.next_hashed_name_size) + break; + + /* Format the NSEC3's next hashed name as proper domain name */ + r = nsec3_hashed_domain_format( + rr->nsec3.next_hashed_name, + rr->nsec3.next_hashed_name_size, + zone, + &next_hashed_domain); + if (r < 0) + return r; + + r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), hashed_domain, next_hashed_domain); + if (r < 0) + return r; + + found = r > 0; + break; + } + + default: + continue; + } + + if (found) { + if (authenticated) + *authenticated = flags & DNS_ANSWER_AUTHENTICATED; + return 1; + } + } + + return 0; +} + +static int dnssec_test_positive_wildcard_nsec3( + DnsAnswer *answer, + const char *name, + const char *source, + const char *zone, + bool *authenticated) { + + const char *next_closer = NULL; + int r; + + /* Run a positive NSEC3 wildcard proof. Specifically: + * + * A proof that the the "next closer" of the generating wildcard does not exist. + * + * Note a key difference between the NSEC3 and NSEC versions of the proof. NSEC RRs don't have to exist for + * empty non-transients. NSEC3 RRs however have to. This means it's sufficient to check if the next closer name + * exists for the NSEC3 RR and we are done. + * + * To prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f all we have to check is that + * c.d.e.f does not exist. */ + + for (;;) { + next_closer = name; + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r == 0) + return 0; + + r = dns_name_equal(name, source); + if (r < 0) + return r; + if (r > 0) + break; + } + + return dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC3, next_closer, zone, authenticated); +} + +static int dnssec_test_positive_wildcard_nsec( + DnsAnswer *answer, + const char *name, + const char *source, + const char *zone, + bool *_authenticated) { + + bool authenticated = true; + int r; + + /* Run a positive NSEC wildcard proof. Specifically: + * + * A proof that there's neither a wildcard name nor a non-wildcard name that is a suffix of the name "name" and + * a prefix of the synthesizing source "source" in the zone "zone". + * + * See RFC 5155, Section 8.8 and RFC 4035, Section 5.3.4 + * + * Note that if we want to prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f, then we + * have to prove that none of the following exist: + * + * 1) a.b.c.d.e.f + * 2) *.b.c.d.e.f + * 3) b.c.d.e.f + * 4) *.c.d.e.f + * 5) c.d.e.f + * + */ + + for (;;) { + _cleanup_free_ char *wc = NULL; + bool a = false; + + /* Check if there's an NSEC or NSEC3 RR that proves that the mame we determined is really non-existing, + * i.e between the owner name and the next name of an NSEC RR. */ + r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, name, zone, &a); + if (r <= 0) + return r; + + authenticated = authenticated && a; + + /* Strip one label off */ + r = dns_name_parent(&name); + if (r <= 0) + return r; + + /* Did we reach the source of synthesis? */ + r = dns_name_equal(name, source); + if (r < 0) + return r; + if (r > 0) { + /* Successful exit */ + *_authenticated = authenticated; + return 1; + } + + /* Safety check, that the source of synthesis is still our suffix */ + r = dns_name_endswith(name, source); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + + /* Replace the label we stripped off with an asterisk */ + wc = strappend("*.", name); + if (!wc) + return -ENOMEM; + + /* And check if the proof holds for the asterisk name, too */ + r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, wc, zone, &a); + if (r <= 0) + return r; + + authenticated = authenticated && a; + /* In the next iteration we'll check the non-asterisk-prefixed version */ + } +} + +int dnssec_test_positive_wildcard( + DnsAnswer *answer, + const char *name, + const char *source, + const char *zone, + bool *authenticated) { + + int r; + + assert(name); + assert(source); + assert(zone); + assert(authenticated); + + r = dns_answer_contains_zone_nsec3(answer, zone); + if (r < 0) + return r; + if (r > 0) + return dnssec_test_positive_wildcard_nsec3(answer, name, source, zone, authenticated); + else + return dnssec_test_positive_wildcard_nsec(answer, name, source, zone, authenticated); +} static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = { [DNSSEC_VALIDATED] = "validated", + [DNSSEC_VALIDATED_WILDCARD] = "validated-wildcard", [DNSSEC_INVALID] = "invalid", [DNSSEC_SIGNATURE_EXPIRED] = "signature-expired", [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm", diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h index f106875027..955017e8cb 100644 --- a/src/resolve/resolved-dns-dnssec.h +++ b/src/resolve/resolved-dns-dnssec.h @@ -28,27 +28,10 @@ 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() */ + /* These five are returned by dnssec_verify_rrset() */ DNSSEC_VALIDATED, + DNSSEC_VALIDATED_WILDCARD, /* Validated via a wildcard RRSIG, further NSEC/NSEC3 checks necessary */ DNSSEC_INVALID, DNSSEC_SIGNATURE_EXPIRED, DNSSEC_UNSUPPORTED_ALGORITHM, @@ -72,26 +55,26 @@ enum DnssecResult { /* The longest digest we'll ever generate, of all digest algorithms we support */ #define DNSSEC_HASH_SIZE_MAX (MAX(20, 32)) -int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey); +int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok); int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig); -int dnssec_verify_rrset(DnsAnswer *answer, DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result); -int dnssec_verify_rrset_search(DnsAnswer *answer, DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, DnssecResult *result); +int dnssec_verify_rrset(DnsAnswer *answer, const DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result); +int dnssec_verify_rrset_search(DnsAnswer *answer, const DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, DnssecResult *result, DnsResourceRecord **rrsig); -int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds); -int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds); +int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke); +int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds); int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key); -uint16_t dnssec_keytag(DnsResourceRecord *dnskey); +uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke); int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max); -int dnssec_nsec3_hash(const DnsResourceRecord *nsec3, const char *name, void *ret); +int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret); typedef enum DnssecNsecResult { DNSSEC_NSEC_NO_RR, /* No suitable NSEC/NSEC3 RR found */ - DNSSEC_NSEC_CNAME, /* Would be NODATA, but for the existence of a CNAME RR */ + DNSSEC_NSEC_CNAME, /* Didn't find what was asked for, but did find CNAME */ DNSSEC_NSEC_UNSUPPORTED_ALGORITHM, DNSSEC_NSEC_NXDOMAIN, DNSSEC_NSEC_NODATA, @@ -99,10 +82,11 @@ typedef enum DnssecNsecResult { DNSSEC_NSEC_OPTOUT, } DnssecNsecResult; -int dnssec_test_nsec(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated); +int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl); + +int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name, const char *zone, bool *authenticated); -const char* dnssec_mode_to_string(DnssecMode m) _const_; -DnssecMode dnssec_mode_from_string(const char *s) _pure_; +int dnssec_test_positive_wildcard(DnsAnswer *a, const char *name, const char *source, const char *zone, bool *authenticated); const char* dnssec_result_to_string(DnssecResult m) _const_; DnssecResult dnssec_result_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 4750bf1f5d..9a5223ef01 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -466,12 +466,8 @@ int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, bool canonica /* Generate in canonical form, as defined by DNSSEC * RFC 4034, Section 6.2, i.e. all lower-case. */ - for (i = 0; i < l; i++) { - if (d[i] >= 'A' && d[i] <= 'Z') - w[i] = (uint8_t) (d[i] - 'A' + 'a'); - else - w[i] = (uint8_t) d[i]; - } + for (i = 0; i < l; i++) + w[i] = (uint8_t) ascii_tolower(d[i]); } else /* Otherwise, just copy the string unaltered. This is * essential for DNS-SD, where the casing of labels @@ -503,7 +499,6 @@ int dns_packet_append_name( const char *z = name; char label[DNS_LABEL_MAX]; size_t n = 0; - int k; if (allow_compression) n = PTR_TO_SIZE(hashmap_get(p->names, name)); @@ -523,17 +518,6 @@ int dns_packet_append_name( if (r < 0) goto fail; - if (p->protocol == DNS_PROTOCOL_DNS) - k = dns_label_apply_idna(label, r, label, sizeof(label)); - else - k = dns_label_undo_idna(label, r, label, sizeof(label)); - if (k < 0) { - r = k; - goto fail; - } - if (k > 0) - r = k; - r = dns_packet_append_label(p, label, r, canonical_candidate, &n); if (r < 0) goto fail; @@ -1087,7 +1071,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star /* Let's calculate the actual data size and update the field */ rdlength = p->size - rdlength_offset - sizeof(uint16_t); if (rdlength > 0xFFFF) { - r = ENOSPC; + r = -ENOSPC; goto fail; } @@ -2021,6 +2005,48 @@ fail: return r; } +static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) { + const uint8_t* p; + bool found_dau_dhu_n3u = false; + size_t l; + + /* Checks whether the specified OPT RR is well-formed and whether it contains RFC6975 data (which is not OK in + * a reply). */ + + assert(rr); + assert(rr->key->type == DNS_TYPE_OPT); + + /* Check that the version is 0 */ + if (((rr->ttl >> 16) & UINT32_C(0xFF)) != 0) + return false; + + p = rr->opt.data; + l = rr->opt.size; + while (l > 0) { + uint16_t option_code, option_length; + + /* At least four bytes for OPTION-CODE and OPTION-LENGTH are required */ + if (l < 4U) + return false; + + option_code = unaligned_read_be16(p); + option_length = unaligned_read_be16(p + 2); + + if (l < option_length + 4U) + return false; + + /* RFC 6975 DAU, DHU or N3U fields found. */ + if (IN_SET(option_code, 5, 6, 7)) + found_dau_dhu_n3u = true; + + p += option_length + 4U; + l -= option_length + 4U; + } + + *rfc6975 = found_dau_dhu_n3u; + return true; +} + int dns_packet_extract(DnsPacket *p) { _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; @@ -2068,6 +2094,9 @@ int dns_packet_extract(DnsPacket *p) { n = DNS_PACKET_RRCOUNT(p); if (n > 0) { + DnsResourceRecord *previous = NULL; + bool bad_opt = false; + answer = dns_answer_new(n); if (!answer) { r = -ENOMEM; @@ -2082,35 +2111,62 @@ int dns_packet_extract(DnsPacket *p) { if (r < 0) goto finish; + /* Try to reduce memory usage a bit */ + if (previous) + dns_resource_key_reduce(&rr->key, &previous->key); + if (rr->key->type == DNS_TYPE_OPT) { + bool has_rfc6975; + + if (p->opt || bad_opt) { + /* Multiple OPT RRs? if so, let's ignore all, because there's something wrong + * with the server, and if one is valid we wouldn't know which one. */ + log_debug("Multiple OPT RRs detected, ignoring all."); + bad_opt = true; + continue; + } if (!dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key))) { - r = -EBADMSG; - goto finish; + /* If the OPT RR qis not owned by the root domain, then it is bad, let's ignore + * it. */ + log_debug("OPT RR is not owned by root domain, ignoring."); + bad_opt = true; + continue; } - /* The OPT RR is only valid in the Additional section */ if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)) { - r = -EBADMSG; - goto finish; + /* OPT RR is in the wrong section? Some Belkin routers do this. This is a hint + * the EDNS implementation is borked, like the Belkin one is, hence ignore + * it. */ + log_debug("OPT RR in wrong section, ignoring."); + bad_opt = true; + continue; } - /* Two OPT RRs? */ - if (p->opt) { - r = -EBADMSG; - goto finish; + if (!opt_is_good(rr, &has_rfc6975)) { + log_debug("Malformed OPT RR, ignoring."); + bad_opt = true; + continue; + } + + if (has_rfc6975) { + /* OPT RR contains RFC6975 algorithm data, then this is indication that the + * server just copied the OPT it got from us (which contained that data) back + * into the reply. If so, then it doesn't properly support EDNS, as RFC6975 + * makes it very clear that the algorithm data should only be contained in + * questions, never in replies. Crappy Belkin copy the OPT data for example, + * hence let's detect this so that we downgrade early. */ + log_debug("OPT RR contained RFC6975 data, ignoring."); + bad_opt = true; + continue; } p->opt = dns_resource_record_ref(rr); } else { - /* According to RFC 4795, section - * 2.9. only the RRs from the Answer - * section shall be cached. Hence mark - * only those RRs as cacheable by - * default, but not the ones from the - * Additional or Authority - * sections. */ + /* According to RFC 4795, section 2.9. only the RRs from the Answer section shall be + * cached. Hence mark only those RRs as cacheable by default, but not the ones from the + * Additional or Authority sections. */ r = dns_answer_add(answer, rr, p->ifindex, (i < DNS_PACKET_ANCOUNT(p) ? DNS_ANSWER_CACHEABLE : 0) | @@ -2119,6 +2175,9 @@ int dns_packet_extract(DnsPacket *p) { goto finish; } } + + if (bad_opt) + p->opt = dns_resource_record_unref(p->opt); } p->question = question; diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index 6821be73e4..c53431576b 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -247,7 +247,7 @@ DnsProtocol dns_protocol_from_string(const char *s) _pure_; #define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } }) #define MDNS_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 251U) }) -#define MDNS_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xfb } }) +#define MDNS_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb } }) static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family, bool authenticated) { uint64_t f; diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 610b914e74..fc5bf4020f 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -24,6 +24,7 @@ #include "hostname-util.h" #include "local-addresses.h" #include "resolved-dns-query.h" +#include "string-util.h" /* How long to wait for the query in total */ #define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC) @@ -217,6 +218,7 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { } static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) { + DnsQuestion *question; DnsResourceKey *key; int n = 0, r; @@ -224,8 +226,10 @@ static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) { dns_query_candidate_stop(c); + question = dns_query_question_for_protocol(c->query, c->scope->protocol); + /* Create one transaction per question key */ - DNS_QUESTION_FOREACH(key, c->query->question) { + DNS_QUESTION_FOREACH(key, question) { _cleanup_(dns_resource_key_unrefp) DnsResourceKey *new_key = NULL; if (c->search_domain) { @@ -305,6 +309,25 @@ static void dns_query_stop(DnsQuery *q) { dns_query_candidate_stop(c); } +static void dns_query_free_candidates(DnsQuery *q) { + assert(q); + + while (q->candidates) + dns_query_candidate_free(q->candidates); +} + +static void dns_query_reset_answer(DnsQuery *q) { + assert(q); + + q->answer = dns_answer_unref(q->answer); + q->answer_rcode = 0; + q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + q->answer_authenticated = false; + q->answer_protocol = _DNS_PROTOCOL_INVALID; + q->answer_family = AF_UNSPEC; + q->answer_search_domain = dns_search_domain_unref(q->answer_search_domain); +} + DnsQuery *dns_query_free(DnsQuery *q) { if (!q) return NULL; @@ -318,16 +341,18 @@ DnsQuery *dns_query_free(DnsQuery *q) { LIST_REMOVE(auxiliary_queries, q->auxiliary_for->auxiliary_queries, q); } - while (q->candidates) - dns_query_candidate_free(q->candidates); + dns_query_free_candidates(q); - dns_question_unref(q->question); - dns_answer_unref(q->answer); - dns_search_domain_unref(q->answer_search_domain); + dns_question_unref(q->question_idna); + dns_question_unref(q->question_utf8); + + dns_query_reset_answer(q); sd_bus_message_unref(q->request); sd_bus_track_unref(q->bus_track); + free(q->request_address_string); + if (q->manager) { LIST_REMOVE(queries, q->manager->dns_queries, q); q->manager->n_dns_queries--; @@ -338,17 +363,50 @@ DnsQuery *dns_query_free(DnsQuery *q) { return NULL; } -int dns_query_new(Manager *m, DnsQuery **ret, DnsQuestion *question, int ifindex, uint64_t flags) { +int dns_query_new( + Manager *m, + DnsQuery **ret, + DnsQuestion *question_utf8, + DnsQuestion *question_idna, + int ifindex, uint64_t flags) { + _cleanup_(dns_query_freep) DnsQuery *q = NULL; - unsigned i; + DnsResourceKey *key; + bool good = false; int r; assert(m); - assert(question); - r = dns_question_is_valid_for_query(question); + if (dns_question_size(question_utf8) > 0) { + r = dns_question_is_valid_for_query(question_utf8); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + good = true; + } + + /* If the IDNA and UTF8 questions are the same, merge their references */ + r = dns_question_is_equal(question_idna, question_utf8); if (r < 0) return r; + if (r > 0) + question_idna = question_utf8; + else { + if (dns_question_size(question_idna) > 0) { + r = dns_question_is_valid_for_query(question_idna); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + good = true; + } + } + + if (!good) /* don't allow empty queries */ + return -EINVAL; if (m->n_dns_queries >= QUERIES_MAX) return -EBUSY; @@ -357,20 +415,40 @@ int dns_query_new(Manager *m, DnsQuery **ret, DnsQuestion *question, int ifindex if (!q) return -ENOMEM; - q->question = dns_question_ref(question); + q->question_utf8 = dns_question_ref(question_utf8); + q->question_idna = dns_question_ref(question_idna); q->ifindex = ifindex; q->flags = flags; - q->answer_family = AF_UNSPEC; + q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; q->answer_protocol = _DNS_PROTOCOL_INVALID; + q->answer_family = AF_UNSPEC; + + /* First dump UTF8 question */ + DNS_QUESTION_FOREACH(key, question_utf8) { + _cleanup_free_ char *p = NULL; - for (i = 0; i < question->n_keys; i++) { - _cleanup_free_ char *p; + r = dns_resource_key_to_string(key, &p); + if (r < 0) + return r; + + log_debug("Looking up RR for %s.", strstrip(p)); + } + + /* And then dump the IDNA question, but only what hasn't been dumped already through the UTF8 question. */ + DNS_QUESTION_FOREACH(key, question_idna) { + _cleanup_free_ char *p = NULL; + + r = dns_question_contains(question_utf8, key); + if (r < 0) + return r; + if (r > 0) + continue; - r = dns_resource_key_to_string(question->keys[i], &p); + r = dns_resource_key_to_string(key, &p); if (r < 0) return r; - log_debug("Looking up RR for %s", p); + log_debug("Looking up IDNA RR for %s.", strstrip(p)); } LIST_PREPEND(queries, m->dns_queries, q); @@ -446,7 +524,7 @@ static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) { /* If this a single-label domain on DNS, we might append a suitable search domain first. */ if ((q->flags & SD_RESOLVED_NO_SEARCH) == 0) { - r = dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question)); + r = dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question_idna)); if (r < 0) goto fail; if (r > 0) { @@ -534,7 +612,7 @@ static int dns_type_to_af(uint16_t t) { } } -static int synthesize_localhost_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) { +static int synthesize_localhost_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) { int r; assert(q); @@ -590,7 +668,7 @@ static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, return dns_answer_add(*answer, rr, ifindex, flags); } -static int synthesize_localhost_ptr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) { +static int synthesize_localhost_ptr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) { int r; assert(q); @@ -682,7 +760,7 @@ static int answer_add_addresses_ptr( return 0; } -static int synthesize_system_hostname_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) { +static int synthesize_system_hostname_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) { _cleanup_free_ struct local_address *addresses = NULL; int n = 0, af; @@ -766,7 +844,7 @@ static int synthesize_system_hostname_ptr(DnsQuery *q, int af, const union in_ad return answer_add_addresses_ptr(answer, q->manager->mdns_hostname, addresses, n, af, address); } -static int synthesize_gateway_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) { +static int synthesize_gateway_rr(DnsQuery *q, const DnsResourceKey *key, DnsAnswer **answer) { _cleanup_free_ struct local_address *addresses = NULL; int n = 0, af; @@ -801,7 +879,7 @@ static int synthesize_gateway_ptr(DnsQuery *q, int af, const union in_addr_union static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - unsigned i; + DnsResourceKey *key; int r; assert(q); @@ -816,39 +894,39 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { DNS_TRANSACTION_ATTEMPTS_MAX_REACHED)) return 0; - for (i = 0; i < q->question->n_keys; i++) { + DNS_QUESTION_FOREACH(key, q->question_utf8) { union in_addr_union address; const char *name; int af; - if (q->question->keys[i]->class != DNS_CLASS_IN && - q->question->keys[i]->class != DNS_CLASS_ANY) + if (key->class != DNS_CLASS_IN && + key->class != DNS_CLASS_ANY) continue; - name = DNS_RESOURCE_KEY_NAME(q->question->keys[i]); + name = DNS_RESOURCE_KEY_NAME(key); if (is_localhost(name)) { - r = synthesize_localhost_rr(q, q->question->keys[i], &answer); + r = synthesize_localhost_rr(q, key, &answer); if (r < 0) return log_error_errno(r, "Failed to synthesize localhost RRs: %m"); } else if (manager_is_own_hostname(q->manager, name)) { - r = synthesize_system_hostname_rr(q, q->question->keys[i], &answer); + r = synthesize_system_hostname_rr(q, key, &answer); if (r < 0) return log_error_errno(r, "Failed to synthesize system hostname RRs: %m"); } else if (is_gateway_hostname(name)) { - r = synthesize_gateway_rr(q, q->question->keys[i], &answer); + r = synthesize_gateway_rr(q, key, &answer); if (r < 0) return log_error_errno(r, "Failed to synthesize gateway RRs: %m"); } else if ((dns_name_endswith(name, "127.in-addr.arpa") > 0 && dns_name_equal(name, "2.0.0.127.in-addr.arpa") == 0) || dns_name_equal(name, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) { - r = synthesize_localhost_ptr(q, q->question->keys[i], &answer); + r = synthesize_localhost_ptr(q, key, &answer); if (r < 0) return log_error_errno(r, "Failed to synthesize localhost PTR RRs: %m"); @@ -884,7 +962,6 @@ int dns_query_go(DnsQuery *q) { DnsScopeMatch found = DNS_SCOPE_NO; DnsScope *s, *first = NULL; DnsQueryCandidate *c; - const char *name; int r; assert(q); @@ -892,13 +969,13 @@ int dns_query_go(DnsQuery *q) { if (q->state != DNS_TRANSACTION_NULL) return 0; - assert(q->question); - assert(q->question->n_keys > 0); - - name = dns_question_first_name(q->question); - LIST_FOREACH(scopes, s, q->manager->dns_scopes) { DnsScopeMatch match; + const char *name; + + name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol)); + if (!name) + continue; match = dns_scope_good_domain(s, q->ifindex, q->flags, name); if (match < 0) @@ -934,6 +1011,11 @@ int dns_query_go(DnsQuery *q) { LIST_FOREACH(scopes, s, first->scopes_next) { DnsScopeMatch match; + const char *name; + + name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol)); + if (!name) + continue; match = dns_scope_good_domain(s, q->ifindex, q->flags, name); if (match < 0) @@ -961,6 +1043,8 @@ int dns_query_go(DnsQuery *q) { if (r < 0) goto fail; + (void) sd_event_source_set_description(q->timeout_event_source, "query-timeout"); + q->state = DNS_TRANSACTION_PENDING; q->block_ready++; @@ -1113,50 +1197,78 @@ void dns_query_ready(DnsQuery *q) { } static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) { - _cleanup_(dns_question_unrefp) DnsQuestion *nq = NULL; - int r; + _cleanup_(dns_question_unrefp) DnsQuestion *nq_idna = NULL, *nq_utf8 = NULL; + int r, k; assert(q); - log_debug("Following CNAME %s → %s", dns_question_first_name(q->question), cname->cname.name); - q->n_cname_redirects ++; if (q->n_cname_redirects > CNAME_MAX) return -ELOOP; - r = dns_question_cname_redirect(q->question, cname, &nq); + r = dns_question_cname_redirect(q->question_idna, cname, &nq_idna); if (r < 0) return r; + else if (r > 0) + log_debug("Following CNAME/DNAME %s → %s.", dns_question_first_name(q->question_idna), dns_question_first_name(nq_idna)); - dns_question_unref(q->question); - q->question = nq; - nq = NULL; + k = dns_question_is_equal(q->question_idna, q->question_utf8); + if (k < 0) + return r; + if (k > 0) { + /* Same question? Shortcut new question generation */ + nq_utf8 = dns_question_ref(nq_idna); + k = r; + } else { + k = dns_question_cname_redirect(q->question_utf8, cname, &nq_utf8); + if (k < 0) + return k; + else if (k > 0) + log_debug("Following UTF8 CNAME/DNAME %s → %s.", dns_question_first_name(q->question_utf8), dns_question_first_name(nq_utf8)); + } - dns_query_stop(q); + if (r == 0 && k == 0) /* No actual cname happened? */ + return -ELOOP; + + dns_question_unref(q->question_idna); + q->question_idna = nq_idna; + nq_idna = NULL; + + dns_question_unref(q->question_utf8); + q->question_utf8 = nq_utf8; + nq_utf8 = NULL; + + dns_query_free_candidates(q); + dns_query_reset_answer(q); q->state = DNS_TRANSACTION_NULL; + /* Turn off searching for the new name */ + q->flags |= SD_RESOLVED_NO_SEARCH; + return 0; } int dns_query_process_cname(DnsQuery *q) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL; + DnsQuestion *question; DnsResourceRecord *rr; int r; assert(q); - if (q->state != DNS_TRANSACTION_SUCCESS) - return 0; + if (!IN_SET(q->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_NULL)) + return DNS_QUERY_NOMATCH; - DNS_ANSWER_FOREACH(rr, q->answer) { + question = dns_query_question_for_protocol(q, q->answer_protocol); - r = dns_question_matches_rr(q->question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + DNS_ANSWER_FOREACH(rr, q->answer) { + r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); if (r < 0) return r; if (r > 0) - return 0; /* The answer matches directly, no need to follow cnames */ + return DNS_QUERY_MATCH; /* The answer matches directly, no need to follow cnames */ - r = dns_question_matches_cname(q->question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + r = dns_question_matches_cname_or_dname(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); if (r < 0) return r; if (r > 0 && !cname) @@ -1164,7 +1276,7 @@ int dns_query_process_cname(DnsQuery *q) { } if (!cname) - return 0; /* No cname to follow */ + return DNS_QUERY_NOMATCH; /* No match and no cname to follow */ if (q->flags & SD_RESOLVED_NO_CNAME) return -ELOOP; @@ -1176,20 +1288,16 @@ int dns_query_process_cname(DnsQuery *q) { /* Let's see if the answer can already answer the new * redirected question */ - DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(q->question, rr, NULL); - if (r < 0) - return r; - if (r > 0) - return 0; /* It can answer it, yay! */ - } + r = dns_query_process_cname(q); + if (r != DNS_QUERY_NOMATCH) + return r; /* OK, it cannot, let's begin with the new query */ r = dns_query_go(q); if (r < 0) return r; - return 1; /* We return > 0, if we restarted the query for a new cname */ + return DNS_QUERY_RESTARTED; /* We restarted the query for a new cname */ } static int on_bus_track(sd_bus_track *t, void *userdata) { @@ -1221,3 +1329,42 @@ int dns_query_bus_track(DnsQuery *q, sd_bus_message *m) { return 0; } + +DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol) { + assert(q); + + switch (protocol) { + + case DNS_PROTOCOL_DNS: + return q->question_idna; + + case DNS_PROTOCOL_MDNS: + case DNS_PROTOCOL_LLMNR: + return q->question_utf8; + + default: + return NULL; + } +} + +const char *dns_query_string(DnsQuery *q) { + const char *name; + int r; + + /* Returns a somewhat useful human-readable lookup key string for this query */ + + if (q->request_address_string) + return q->request_address_string; + + if (q->request_address_valid) { + r = in_addr_to_string(q->request_family, &q->request_address, &q->request_address_string); + if (r >= 0) + return q->request_address_string; + } + + name = dns_question_first_name(q->question_utf8); + if (name) + return name; + + return dns_question_first_name(q->question_idna); +} diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index 44edd5bfff..9f618d6f6b 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -59,7 +59,13 @@ struct DnsQuery { unsigned n_auxiliary_queries; int auxiliary_result; - DnsQuestion *question; + /* The question, formatted in IDNA for use on classic DNS, and as UTF8 for use in LLMNR or mDNS. Note that even + * on classic DNS some labels might use UTF8 encoding. Specifically, DNS-SD service names (in contrast to their + * domain suffixes) use UTF-8 encoding even on DNS. Thus, the difference between these two fields is mostly + * relevant only for explicit *hostname* lookups as well as the domain suffixes of service lookups. */ + DnsQuestion *question_idna; + DnsQuestion *question_utf8; + uint64_t flags; int ifindex; @@ -84,6 +90,7 @@ struct DnsQuery { bool request_address_valid; union in_addr_union request_address; unsigned block_all_complete; + char *request_address_string; /* Completion callback */ void (*complete)(DnsQuery* q); @@ -95,10 +102,16 @@ struct DnsQuery { LIST_FIELDS(DnsQuery, auxiliary_queries); }; +enum { + DNS_QUERY_MATCH, + DNS_QUERY_NOMATCH, + DNS_QUERY_RESTARTED, +}; + DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c); void dns_query_candidate_notify(DnsQueryCandidate *c); -int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question, int family, uint64_t flags); +int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question_utf8, DnsQuestion *question_idna, int family, uint64_t flags); DnsQuery *dns_query_free(DnsQuery *q); int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for); @@ -110,4 +123,8 @@ int dns_query_process_cname(DnsQuery *q); int dns_query_bus_track(DnsQuery *q, sd_bus_message *m); +DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol); + +const char *dns_query_string(DnsQuery *q); + DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuery*, dns_query_free); diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c index 4ed7434d3c..1e41a9aa3c 100644 --- a/src/resolve/resolved-dns-question.c +++ b/src/resolve/resolved-dns-question.c @@ -21,6 +21,7 @@ #include "alloc-util.h" #include "dns-domain.h" +#include "dns-type.h" #include "resolved-dns-question.h" DnsQuestion *dns_question_new(unsigned n) { @@ -107,7 +108,7 @@ int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *s return 0; } -int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { +int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { unsigned i; int r; @@ -116,7 +117,14 @@ int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr, const char if (!q) return 0; + if (!IN_SET(rr->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)) + return 0; + for (i = 0; i < q->n_keys; i++) { + /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */ + if (!dns_type_may_redirect(q->keys[i]->type)) + return 0; + r = dns_resource_key_match_cname_or_dname(q->keys[i], rr->key, search_domain); if (r != 0) return r; @@ -144,18 +152,23 @@ int dns_question_is_valid_for_query(DnsQuestion *q) { return 0; /* Check that all keys in this question bear the same name */ - for (i = 1; i < q->n_keys; i++) { + for (i = 0; i < q->n_keys; i++) { assert(q->keys[i]); - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), name); - if (r <= 0) - return r; + if (i > 0) { + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), name); + if (r <= 0) + return r; + } + + if (!dns_type_is_valid_query(q->keys[i]->type)) + return 0; } return 1; } -int dns_question_contains(DnsQuestion *a, DnsResourceKey *k) { +int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k) { unsigned j; int r; @@ -177,6 +190,9 @@ int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b) { unsigned j; int r; + if (a == b) + return 1; + if (!a) return !b || b->n_keys == 0; if (!b) @@ -201,32 +217,27 @@ int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b) { int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret) { _cleanup_(dns_question_unrefp) DnsQuestion *n = NULL; + DnsResourceKey *key; bool same = true; - unsigned i; int r; assert(cname); assert(ret); assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); - if (!q) { - n = dns_question_new(0); - if (!n) - return -ENOMEM; - - *ret = n; - n = 0; + if (dns_question_size(q) <= 0) { + *ret = NULL; return 0; } - for (i = 0; i < q->n_keys; i++) { + DNS_QUESTION_FOREACH(key, q) { _cleanup_free_ char *destination = NULL; const char *d; if (cname->key->type == DNS_TYPE_CNAME) d = cname->cname.name; else { - r = dns_name_change_suffix(DNS_RESOURCE_KEY_NAME(q->keys[i]), DNS_RESOURCE_KEY_NAME(cname->key), cname->dname.name, &destination); + r = dns_name_change_suffix(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname->key), cname->dname.name, &destination); if (r < 0) return r; if (r == 0) @@ -235,7 +246,7 @@ int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, d = destination; } - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), d); + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), d); if (r < 0) return r; @@ -245,9 +256,9 @@ int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, } } + /* Fully the same, indicate we didn't do a thing */ if (same) { - /* Shortcut, the names are already right */ - *ret = dns_question_ref(q); + *ret = NULL; return 0; } @@ -256,10 +267,10 @@ int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, return -ENOMEM; /* Create a new question, and patch in the new name */ - for (i = 0; i < q->n_keys; i++) { + DNS_QUESTION_FOREACH(key, q) { _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; - k = dns_resource_key_new_redirect(q->keys[i], cname); + k = dns_resource_key_new_redirect(key, cname); if (!k) return -ENOMEM; @@ -285,8 +296,9 @@ const char *dns_question_first_name(DnsQuestion *q) { return DNS_RESOURCE_KEY_NAME(q->keys[0]); } -int dns_question_new_address(DnsQuestion **ret, int family, const char *name) { +int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna) { _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; + _cleanup_free_ char *buf = NULL; int r; assert(ret); @@ -295,6 +307,14 @@ int dns_question_new_address(DnsQuestion **ret, int family, const char *name) { if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) return -EAFNOSUPPORT; + if (convert_idna) { + r = dns_name_apply_idna(name, &buf); + if (r < 0) + return r; + + name = buf; + } + q = dns_question_new(family == AF_UNSPEC ? 2 : 1); if (!q) return -ENOMEM; @@ -365,13 +385,60 @@ int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_ return 0; } -int dns_question_new_service(DnsQuestion **ret, const char *name, bool with_txt) { +int dns_question_new_service( + DnsQuestion **ret, + const char *service, + const char *type, + const char *domain, + bool with_txt, + bool convert_idna) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; + _cleanup_free_ char *buf = NULL, *joined = NULL; + const char *name; int r; assert(ret); - assert(name); + + /* We support three modes of invocation: + * + * 1. Only a domain is specified, in which case we assume a properly encoded SRV RR name, including service + * type and possibly a service name. If specified in this way we assume it's already IDNA converted if + * that's necessary. + * + * 2. Both service type and a domain specified, in which case a normal SRV RR is assumed, without a DNS-SD + * style prefix. In this case we'll IDNA convert the domain, if that's requested. + * + * 3. All three of service name, type and domain are specified, in which case a DNS-SD service is put + * together. The service name is never IDNA converted, and the domain is if requested. + * + * It's not supported to specify a service name without a type, or no domain name. + */ + + if (!domain) + return -EINVAL; + + if (type) { + if (convert_idna) { + r = dns_name_apply_idna(domain, &buf); + if (r < 0) + return r; + + domain = buf; + } + + r = dns_service_join(service, type, domain, &joined); + if (r < 0) + return r; + + name = joined; + } else { + if (service) + return -EINVAL; + + name = domain; + } q = dns_question_new(1 + with_txt); if (!q) diff --git a/src/resolve/resolved-dns-question.h b/src/resolve/resolved-dns-question.h index 5ffb63e250..98e1f0e366 100644 --- a/src/resolve/resolved-dns-question.h +++ b/src/resolve/resolved-dns-question.h @@ -38,22 +38,26 @@ DnsQuestion *dns_question_new(unsigned n); DnsQuestion *dns_question_ref(DnsQuestion *q); DnsQuestion *dns_question_unref(DnsQuestion *q); -int dns_question_new_address(DnsQuestion **ret, int family, const char *name); +int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna); int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a); -int dns_question_new_service(DnsQuestion **ret, const char *name, bool with_txt); +int dns_question_new_service(DnsQuestion **ret, const char *service, const char *type, const char *domain, bool with_txt, bool convert_idna); int dns_question_add(DnsQuestion *q, DnsResourceKey *key); int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain); -int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr, const char* search_domain); +int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char* search_domain); int dns_question_is_valid_for_query(DnsQuestion *q); -int dns_question_contains(DnsQuestion *a, DnsResourceKey *k); +int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k); int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b); int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret); const char *dns_question_first_name(DnsQuestion *q); +static inline unsigned dns_question_size(DnsQuestion *q) { + return q ? q->n_keys : 0; +} + DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref); #define _DNS_QUESTION_FOREACH(u, key, q) \ diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 76723ec4d0..7273ef3825 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -334,6 +334,46 @@ int dns_resource_key_to_string(const DnsResourceKey *key, char **ret) { return 0; } +bool dns_resource_key_reduce(DnsResourceKey **a, DnsResourceKey **b) { + assert(a); + assert(b); + + /* Try to replace one RR key by another if they are identical, thus saving a bit of memory. Note that we do + * this only for RR keys, not for RRs themselves, as they carry a lot of additional metadata (where they come + * from, validity data, and suchlike), and cannot be replaced so easily by other RRs that have the same + * superficial data. */ + + if (!*a) + return false; + if (!*b) + return false; + + /* We refuse merging const keys */ + if ((*a)->n_ref == (unsigned) -1) + return false; + if ((*b)->n_ref == (unsigned) -1) + return false; + + /* Already the same? */ + if (*a == *b) + return true; + + /* Are they really identical? */ + if (dns_resource_key_equal(*a, *b) <= 0) + return false; + + /* Keep the one which already has more references. */ + if ((*a)->n_ref > (*b)->n_ref) { + dns_resource_key_unref(*b); + *b = dns_resource_key_ref(*a); + } else { + dns_resource_key_unref(*a); + *a = dns_resource_key_ref(*b); + } + + return true; +} + DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) { DnsResourceRecord *rr; @@ -344,6 +384,7 @@ DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) { rr->n_ref = 1; rr->key = dns_resource_key_ref(key); rr->expiry = USEC_INFINITY; + rr->n_skip_labels_signer = rr->n_skip_labels_source = (unsigned) -1; return rr; } @@ -1085,6 +1126,239 @@ int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) { return 0; } +int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret) { + const char *n; + int r; + + assert(rr); + assert(ret); + + /* Returns the RRset's signer, if it is known. */ + + if (rr->n_skip_labels_signer == (unsigned) -1) + return -ENODATA; + + n = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_skip(n, rr->n_skip_labels_signer, &n); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + *ret = n; + return 0; +} + +int dns_resource_record_source(DnsResourceRecord *rr, const char **ret) { + const char *n; + int r; + + assert(rr); + assert(ret); + + /* Returns the RRset's synthesizing source, if it is known. */ + + if (rr->n_skip_labels_source == (unsigned) -1) + return -ENODATA; + + n = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_skip(n, rr->n_skip_labels_source, &n); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + *ret = n; + return 0; +} + +int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone) { + const char *signer; + int r; + + assert(rr); + + r = dns_resource_record_signer(rr, &signer); + if (r < 0) + return r; + + return dns_name_equal(zone, signer); +} + +int dns_resource_record_is_synthetic(DnsResourceRecord *rr) { + int r; + + assert(rr); + + /* Returns > 0 if the RR is generated from a wildcard, and is not the asterisk name itself */ + + if (rr->n_skip_labels_source == (unsigned) -1) + return -ENODATA; + + if (rr->n_skip_labels_source == 0) + return 0; + + if (rr->n_skip_labels_source > 1) + return 1; + + r = dns_name_startswith(DNS_RESOURCE_KEY_NAME(rr->key), "*"); + if (r < 0) + return r; + + return !r; +} + +static void dns_resource_record_hash_func(const void *i, struct siphash *state) { + const DnsResourceRecord *rr = i; + + assert(rr); + + dns_resource_key_hash_func(rr->key, state); + + switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { + + case DNS_TYPE_SRV: + siphash24_compress(&rr->srv.priority, sizeof(rr->srv.priority), state); + siphash24_compress(&rr->srv.weight, sizeof(rr->srv.weight), state); + siphash24_compress(&rr->srv.port, sizeof(rr->srv.port), state); + dns_name_hash_func(rr->srv.name, state); + break; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + dns_name_hash_func(rr->ptr.name, state); + break; + + case DNS_TYPE_HINFO: + string_hash_func(rr->hinfo.cpu, state); + string_hash_func(rr->hinfo.os, state); + break; + + case DNS_TYPE_TXT: + case DNS_TYPE_SPF: { + DnsTxtItem *j; + + LIST_FOREACH(items, j, rr->txt.items) { + siphash24_compress(j->data, j->length, state); + + /* Add an extra NUL byte, so that "a" followed by "b" doesn't result in the same hash as "ab" + * followed by "". */ + siphash24_compress_byte(0, state); + } + break; + } + + case DNS_TYPE_A: + siphash24_compress(&rr->a.in_addr, sizeof(rr->a.in_addr), state); + break; + + case DNS_TYPE_AAAA: + siphash24_compress(&rr->aaaa.in6_addr, sizeof(rr->aaaa.in6_addr), state); + break; + + case DNS_TYPE_SOA: + dns_name_hash_func(rr->soa.mname, state); + dns_name_hash_func(rr->soa.rname, state); + siphash24_compress(&rr->soa.serial, sizeof(rr->soa.serial), state); + siphash24_compress(&rr->soa.refresh, sizeof(rr->soa.refresh), state); + siphash24_compress(&rr->soa.retry, sizeof(rr->soa.retry), state); + siphash24_compress(&rr->soa.expire, sizeof(rr->soa.expire), state); + siphash24_compress(&rr->soa.minimum, sizeof(rr->soa.minimum), state); + break; + + case DNS_TYPE_MX: + siphash24_compress(&rr->mx.priority, sizeof(rr->mx.priority), state); + dns_name_hash_func(rr->mx.exchange, state); + break; + + case DNS_TYPE_LOC: + siphash24_compress(&rr->loc.version, sizeof(rr->loc.version), state); + siphash24_compress(&rr->loc.size, sizeof(rr->loc.size), state); + siphash24_compress(&rr->loc.horiz_pre, sizeof(rr->loc.horiz_pre), state); + siphash24_compress(&rr->loc.vert_pre, sizeof(rr->loc.vert_pre), state); + siphash24_compress(&rr->loc.latitude, sizeof(rr->loc.latitude), state); + siphash24_compress(&rr->loc.longitude, sizeof(rr->loc.longitude), state); + siphash24_compress(&rr->loc.altitude, sizeof(rr->loc.altitude), state); + break; + + case DNS_TYPE_SSHFP: + siphash24_compress(&rr->sshfp.algorithm, sizeof(rr->sshfp.algorithm), state); + siphash24_compress(&rr->sshfp.fptype, sizeof(rr->sshfp.fptype), state); + siphash24_compress(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, state); + break; + + case DNS_TYPE_DNSKEY: + siphash24_compress(&rr->dnskey.flags, sizeof(rr->dnskey.flags), state); + siphash24_compress(&rr->dnskey.protocol, sizeof(rr->dnskey.protocol), state); + siphash24_compress(&rr->dnskey.algorithm, sizeof(rr->dnskey.algorithm), state); + siphash24_compress(rr->dnskey.key, rr->dnskey.key_size, state); + break; + + case DNS_TYPE_RRSIG: + siphash24_compress(&rr->rrsig.type_covered, sizeof(rr->rrsig.type_covered), state); + siphash24_compress(&rr->rrsig.algorithm, sizeof(rr->rrsig.algorithm), state); + siphash24_compress(&rr->rrsig.labels, sizeof(rr->rrsig.labels), state); + siphash24_compress(&rr->rrsig.original_ttl, sizeof(rr->rrsig.original_ttl), state); + siphash24_compress(&rr->rrsig.expiration, sizeof(rr->rrsig.expiration), state); + siphash24_compress(&rr->rrsig.inception, sizeof(rr->rrsig.inception), state); + siphash24_compress(&rr->rrsig.key_tag, sizeof(rr->rrsig.key_tag), state); + dns_name_hash_func(rr->rrsig.signer, state); + siphash24_compress(rr->rrsig.signature, rr->rrsig.signature_size, state); + break; + + case DNS_TYPE_NSEC: + dns_name_hash_func(rr->nsec.next_domain_name, state); + /* FIXME: we leave out the type bitmap here. Hash + * would be better if we'd take it into account + * too. */ + break; + + case DNS_TYPE_DS: + siphash24_compress(&rr->ds.key_tag, sizeof(rr->ds.key_tag), state); + siphash24_compress(&rr->ds.algorithm, sizeof(rr->ds.algorithm), state); + siphash24_compress(&rr->ds.digest_type, sizeof(rr->ds.digest_type), state); + siphash24_compress(rr->ds.digest, rr->ds.digest_size, state); + break; + + case DNS_TYPE_NSEC3: + siphash24_compress(&rr->nsec3.algorithm, sizeof(rr->nsec3.algorithm), state); + siphash24_compress(&rr->nsec3.flags, sizeof(rr->nsec3.flags), state); + siphash24_compress(&rr->nsec3.iterations, sizeof(rr->nsec3.iterations), state); + siphash24_compress(rr->nsec3.salt, rr->nsec3.salt_size, state); + siphash24_compress(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, state); + /* FIXME: We leave the bitmaps out */ + break; + + default: + siphash24_compress(rr->generic.data, rr->generic.size, state); + break; + } +} + +static int dns_resource_record_compare_func(const void *a, const void *b) { + const DnsResourceRecord *x = a, *y = b; + int ret; + + ret = dns_resource_key_compare_func(x->key, y->key); + if (ret != 0) + return ret; + + if (dns_resource_record_equal(x, y)) + return 0; + + /* This is a bit dirty, we don't implement proper odering, but + * the hashtable doesn't need ordering anyway, hence we don't + * care. */ + return x < y ? -1 : 1; +} + +const struct hash_ops dns_resource_record_hash_ops = { + .hash = dns_resource_record_hash_func, + .compare = dns_resource_record_compare_func, +}; + DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i) { DnsTxtItem *n; diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index 90c3629166..d9c31e81c5 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -34,8 +34,9 @@ typedef struct DnsResourceRecord DnsResourceRecord; typedef struct DnsTxtItem DnsTxtItem; /* DNSKEY RR flags */ -#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8) #define DNSKEY_FLAG_SEP (UINT16_C(1) << 0) +#define DNSKEY_FLAG_REVOKE (UINT16_C(1) << 7) +#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8) /* mDNS RR flags */ #define MDNS_RR_CACHE_FLUSH (UINT16_C(1) << 15) @@ -80,7 +81,7 @@ enum { }; struct DnsResourceKey { - unsigned n_ref; + unsigned n_ref; /* (unsigned -1) for const keys, see below */ uint16_t class, type; char *_name; /* don't access directy, use DNS_RESOURCE_KEY_NAME()! */ }; @@ -107,14 +108,24 @@ struct DnsTxtItem { struct DnsResourceRecord { unsigned n_ref; DnsResourceKey *key; + char *to_string; + uint32_t ttl; usec_t expiry; /* RRSIG signature expiry */ + + /* How many labels to strip to determine "signer" of the RRSIG (aka, the zone). -1 if not signed. */ + unsigned n_skip_labels_signer; + /* How many labels to strip to determine "synthesizing source" of this RR, i.e. the wildcard's immediate parent. -1 if not signed. */ + unsigned n_skip_labels_source; + bool unparseable:1; + bool wire_format_canonical:1; void *wire_format; size_t wire_format_size; size_t wire_format_rdata_offset; + union { struct { void *data; @@ -235,7 +246,7 @@ struct DnsResourceRecord { }; static inline const char* DNS_RESOURCE_KEY_NAME(const DnsResourceKey *key) { - if (_unlikely_(!key)) + if (!key) return NULL; if (key->_name) @@ -244,6 +255,27 @@ static inline const char* DNS_RESOURCE_KEY_NAME(const DnsResourceKey *key) { return (char*) key + sizeof(DnsResourceKey); } +static inline const void* DNS_RESOURCE_RECORD_RDATA(DnsResourceRecord *rr) { + if (!rr) + return NULL; + + if (!rr->wire_format) + return NULL; + + assert(rr->wire_format_rdata_offset <= rr->wire_format_size); + return (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset; +} + +static inline size_t DNS_RESOURCE_RECORD_RDATA_SIZE(DnsResourceRecord *rr) { + if (!rr) + return 0; + if (!rr->wire_format) + return 0; + + assert(rr->wire_format_rdata_offset <= rr->wire_format_size); + return rr->wire_format_size - rr->wire_format_rdata_offset; +} + DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name); DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname); int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name); @@ -262,6 +294,8 @@ static inline bool dns_key_is_shared(const DnsResourceKey *key) { return IN_SET(key->type, DNS_TYPE_PTR); } +bool dns_resource_key_reduce(DnsResourceKey **a, DnsResourceKey **b); + DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key); DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name); DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr); @@ -274,10 +308,16 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref); int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical); +int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret); +int dns_resource_record_source(DnsResourceRecord *rr, const char **ret); +int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone); +int dns_resource_record_is_synthetic(DnsResourceRecord *rr); + DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i); bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b); extern const struct hash_ops dns_resource_key_hash_ops; +extern const struct hash_ops dns_resource_record_hash_ops; int dnssec_algorithm_to_string_alloc(int i, char **ret); int dnssec_algorithm_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 13be2a3792..8a52d66fad 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -57,6 +57,21 @@ 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 = link_get_dnssec_mode(l); + else + s->dnssec_mode = manager_get_dnssec_mode(m); + } + LIST_PREPEND(scopes, m->dns_scopes, s); dns_scope_llmnr_membership(s, true); @@ -895,6 +910,8 @@ int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr) { if (r < 0) return log_debug_errno(r, "Failed to add conflict dispatch event: %m"); + (void) sd_event_source_set_description(scope->conflict_event_source, "scope-conflict"); + return 0; } diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index fbd1c27c14..5a86661807 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -135,6 +135,7 @@ DnsServer* dns_server_unref(DnsServer *s) { if (s->n_ref > 0) return NULL; + free(s->server_string); free(s); return NULL; } @@ -224,31 +225,57 @@ void dns_server_move_back_and_unmark(DnsServer *s) { } } -void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel level, usec_t rtt, size_t size) { +static void dns_server_verified(DnsServer *s, DnsServerFeatureLevel level) { assert(s); - if (level == DNS_SERVER_FEATURE_LEVEL_LARGE) { - /* Even if we successfully receive a reply to a - request announcing support for large packets, that - does not mean we can necessarily receive large - packets. */ + if (s->verified_feature_level > level) + return; - if (s->verified_feature_level < DNS_SERVER_FEATURE_LEVEL_LARGE - 1) { - s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_LARGE - 1; - assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0); - } - } else if (s->verified_feature_level < level) { + if (s->verified_feature_level != level) { + log_debug("Verified we get a response at feature level %s from DNS server %s.", + dns_server_feature_level_to_string(level), + dns_server_string(s)); s->verified_feature_level = level; - assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0); } - if (s->possible_feature_level == level) - s->n_failed_attempts = 0; + assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0); +} + +void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t rtt, size_t size) { + assert(s); + + if (protocol == IPPROTO_UDP) { + if (s->possible_feature_level == level) + s->n_failed_udp = 0; + + /* If the RRSIG data is missing, then we can only validate EDNS0 at max */ + if (s->packet_rrsig_missing && level >= DNS_SERVER_FEATURE_LEVEL_DO) + level = DNS_SERVER_FEATURE_LEVEL_DO - 1; + + /* If the OPT RR got lost, then we can only validate UDP at max */ + if (s->packet_bad_opt && level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) + level = DNS_SERVER_FEATURE_LEVEL_EDNS0 - 1; + + /* Even if we successfully receive a reply to a request announcing support for large packets, + that does not mean we can necessarily receive large packets. */ + if (level == DNS_SERVER_FEATURE_LEVEL_LARGE) + level = DNS_SERVER_FEATURE_LEVEL_LARGE - 1; + + } else if (protocol == IPPROTO_TCP) { + + if (s->possible_feature_level == level) + s->n_failed_tcp = 0; + + /* Successful TCP connections are only useful to verify the TCP feature level. */ + level = DNS_SERVER_FEATURE_LEVEL_TCP; + } + + dns_server_verified(s, level); /* Remember the size of the largest UDP packet we received from a server, we know that we can always announce support for packets with at least this size. */ - if (s->received_udp_packet_max < size) + if (protocol == IPPROTO_UDP && s->received_udp_packet_max < size) s->received_udp_packet_max = size; if (s->max_rtt < rtt) { @@ -257,12 +284,16 @@ void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel level, usec_ } } -void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel level, usec_t usec) { +void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec) { assert(s); assert(s->manager); - if (s->possible_feature_level == level) - s->n_failed_attempts ++; + if (s->possible_feature_level == level) { + if (protocol == IPPROTO_UDP) + s->n_failed_udp ++; + else if (protocol == IPPROTO_TCP) + s->n_failed_tcp ++; + } if (s->resend_timeout > usec) return; @@ -272,23 +303,50 @@ void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel level, usec_t us void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level) { assert(s); - assert(s->manager); + + /* Invoked whenever we get a FORMERR, SERVFAIL or NOTIMP rcode from a server. */ if (s->possible_feature_level != level) return; - s->n_failed_attempts = (unsigned) -1; + s->packet_failed = true; } -void dns_server_packet_rrsig_missing(DnsServer *s) { - _cleanup_free_ char *ip = NULL; +void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level) { assert(s); - assert(s->manager); - in_addr_to_string(s->family, &s->address, &ip); - log_warning("DNS server %s does not augment replies with RRSIG records, DNSSEC not available.", strna(ip)); + /* Invoked whenever we get a packet with TC bit set. */ + + if (s->possible_feature_level != level) + return; + + s->packet_truncated = true; +} + +void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level) { + assert(s); + + if (level < DNS_SERVER_FEATURE_LEVEL_DO) + return; - s->rrsig_missing = true; + /* If the RRSIG RRs are missing, we have to downgrade what we previously verified */ + if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) + s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_DO-1; + + s->packet_rrsig_missing = true; +} + +void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level) { + assert(s); + + if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0) + return; + + /* If the OPT RR got lost, we have to downgrade what we previously verified */ + if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) + s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0-1; + + s->packet_bad_opt = true; } static bool dns_server_grace_period_expired(DnsServer *s) { @@ -310,33 +368,122 @@ static bool dns_server_grace_period_expired(DnsServer *s) { return true; } +static void dns_server_reset_counters(DnsServer *s) { + assert(s); + + s->n_failed_udp = 0; + s->n_failed_tcp = 0; + s->packet_failed = false; + s->packet_truncated = false; + s->verified_usec = 0; + + /* Note that we do not reset s->packet_bad_opt and s->packet_rrsig_missing here. We reset them only when the + * grace period ends, but not when lowering the possible feature level, as a lower level feature level should + * not make RRSIGs appear or OPT appear, but rather make them disappear. If the reappear anyway, then that's + * indication for a differently broken OPT/RRSIG implementation, and we really don't want to support that + * either. + * + * This is particularly important to deal with certain Belkin routers which break OPT for certain lookups (A), + * but pass traffic through for others (AAAA). If we detect the broken behaviour on one lookup we should not + * reenable it for another, because we cannot validate things anyway, given that the RRSIG/OPT data will be + * incomplete. */ +} + DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { assert(s); if (s->possible_feature_level != DNS_SERVER_FEATURE_LEVEL_BEST && dns_server_grace_period_expired(s)) { - _cleanup_free_ char *ip = NULL; s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; - s->n_failed_attempts = 0; - s->verified_usec = 0; - s->rrsig_missing = false; - in_addr_to_string(s->family, &s->address, &ip); - log_info("Grace period over, resuming full feature set for DNS server %s", strna(ip)); + dns_server_reset_counters(s); + + s->packet_bad_opt = false; + s->packet_rrsig_missing = false; + + log_info("Grace period over, resuming full feature set (%s) for DNS server %s.", + dns_server_feature_level_to_string(s->possible_feature_level), + dns_server_string(s)); + } else if (s->possible_feature_level <= s->verified_feature_level) s->possible_feature_level = s->verified_feature_level; - else if (s->n_failed_attempts >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && - s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_WORST) { - _cleanup_free_ char *ip = NULL; + else { + DnsServerFeatureLevel p = s->possible_feature_level; + + if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && + s->possible_feature_level == DNS_SERVER_FEATURE_LEVEL_TCP) { + + /* We are at the TCP (lowest) level, and we tried a couple of TCP connections, and it didn't + * work. Upgrade back to UDP again. */ + log_debug("Reached maximum number of failed TCP connection attempts, trying UDP again..."); + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP; + + } else if (s->packet_bad_opt && + s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) { + + /* A reply to one of our EDNS0 queries didn't carry a valid OPT RR, then downgrade to below + * EDNS0 levels. After all, some records generate different responses with and without OPT RR + * in the request. Example: + * https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */ + + log_debug("Server doesn't support EDNS(0) properly, downgrading feature level..."); + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP; + + } else if (s->packet_rrsig_missing && + s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) { + + /* RRSIG data was missing on a EDNS0 packet with DO bit set. This means the server doesn't + * augment responses with DNSSEC RRs. If so, let's better not ask the server for it anymore, + * after all some servers generate different replies depending if an OPT RR is in the query or + * not. */ + + log_debug("Detected server responses lack RRSIG records, downgrading feature level..."); + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0; + + } else if (s->n_failed_udp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && + s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_UDP) { + + /* We lost too many UDP packets in a row, and are on a feature level of UDP or higher. If the + * packets are lost, maybe the server cannot parse them, hence downgrading sounds like a good + * idea. We might downgrade all the way down to TCP this way. */ - s->possible_feature_level --; - s->n_failed_attempts = 0; - s->verified_usec = 0; + log_debug("Lost too many UDP packets, downgrading feature level..."); + s->possible_feature_level--; - in_addr_to_string(s->family, &s->address, &ip); - log_warning("Using degraded feature set (%s) for DNS server %s", - dns_server_feature_level_to_string(s->possible_feature_level), strna(ip)); + } else if (s->packet_failed && + s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) { + + /* We got a failure packet, and are at a feature level above UDP. Note that in this case we + * downgrade no further than UDP, under the assumption that a failure packet indicates an + * incompatible packet contents, but not a problem with the transport. */ + + log_debug("Got server failure, downgrading feature level..."); + s->possible_feature_level--; + + } else if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && + s->packet_truncated && + s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) { + + /* We got too many TCP connection failures in a row, we had at least one truncated packet, and + * are on a feature level above UDP. By downgrading things and getting rid of DNSSEC or EDNS0 + * data we hope to make the packet smaller, so that it still works via UDP given that TCP + * appears not to be a fallback. Note that if we are already at the lowest UDP level, we don't + * go further down, since that's TCP, and TCP failed too often after all. */ + + log_debug("Got too many failed TCP connection failures and truncated UDP packets, downgrading feature level..."); + s->possible_feature_level--; + } + + if (p != s->possible_feature_level) { + + /* We changed the feature level, reset the counting */ + dns_server_reset_counters(s); + + log_warning("Using degraded feature set (%s) for DNS server %s.", + dns_server_feature_level_to_string(s->possible_feature_level), + dns_server_string(s)); + } } return s->possible_feature_level; @@ -370,6 +517,36 @@ int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeature return dns_packet_append_opt(packet, packet_size, edns_do, NULL); } +const char *dns_server_string(DnsServer *server) { + assert(server); + + if (!server->server_string) + (void) in_addr_to_string(server->family, &server->address, &server->server_string); + + return strna(server->server_string); +} + +bool dns_server_dnssec_supported(DnsServer *server) { + assert(server); + + /* Returns whether the server supports DNSSEC according to what we know about it */ + + if (server->possible_feature_level < DNS_SERVER_FEATURE_LEVEL_DO) + return false; + + if (server->packet_bad_opt) + return false; + + if (server->packet_rrsig_missing) + return false; + + /* DNSSEC servers need to support TCP properly (see RFC5966), if they don't, we assume DNSSEC is borked too */ + if (server->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS) + return false; + + return true; +} + static void dns_server_hash_func(const void *p, struct siphash *state) { const DnsServer *s = p; @@ -461,12 +638,8 @@ DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) { if (m->current_dns_server == s) return s; - if (s) { - _cleanup_free_ char *ip = NULL; - - in_addr_to_string(s->family, &s->address, &ip); - log_info("Switching to system DNS server %s.", strna(ip)); - } + if (s) + log_info("Switching to system DNS server %s.", dns_server_string(s)); dns_server_unref(m->current_dns_server); m->current_dns_server = dns_server_ref(s); diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index da21e6571a..02bd3463a7 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -61,22 +61,27 @@ struct DnsServer { int family; union in_addr_union address; + char *server_string; + usec_t resend_timeout; usec_t max_rtt; DnsServerFeatureLevel verified_feature_level; DnsServerFeatureLevel possible_feature_level; + size_t received_udp_packet_max; - unsigned n_failed_attempts; + + unsigned n_failed_udp; + unsigned n_failed_tcp; + + bool packet_failed:1; + bool packet_truncated:1; + bool packet_bad_opt:1; + bool packet_rrsig_missing:1; + usec_t verified_usec; usec_t features_grace_period_usec; - /* Indicates whether responses are augmented with RRSIG by - * server or not. Note that this is orthogonal to the feature - * level stuff, as it's only information describing responses, - * and has no effect on how the questions are asked. */ - bool rrsig_missing:1; - /* Used when GC'ing old DNS servers when configuration changes. */ bool marked:1; @@ -99,15 +104,21 @@ DnsServer* dns_server_unref(DnsServer *s); void dns_server_unlink(DnsServer *s); void dns_server_move_back_and_unmark(DnsServer *s); -void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel level, usec_t rtt, size_t size); -void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel level, usec_t usec); +void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t rtt, size_t size); +void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec); void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level); -void dns_server_packet_rrsig_missing(DnsServer *s); +void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level); +void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level); +void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level); DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s); int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level); +const char *dns_server_string(DnsServer *server); + +bool dns_server_dnssec_supported(DnsServer *server); + DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr); void dns_server_unlink_all(DnsServer *first); diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index 180f8e0877..b72e6cc06f 100644 --- a/src/resolve/resolved-dns-stream.c +++ b/src/resolve/resolved-dns-stream.c @@ -367,6 +367,8 @@ int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { if (r < 0) return r; + (void) sd_event_source_set_description(s->io_event_source, "dns-stream-io"); + r = sd_event_add_time( m->event, &s->timeout_event_source, @@ -376,6 +378,8 @@ int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { if (r < 0) return r; + (void) sd_event_source_set_description(s->timeout_event_source, "dns-stream-timeout"); + LIST_PREPEND(streams, m->dns_streams, s); s->manager = m; s->fd = fd; diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index f7671e070f..5640cd1d33 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -19,6 +19,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <sd-messages.h> + #include "af-list.h" #include "alloc-util.h" #include "dns-domain.h" @@ -29,6 +31,8 @@ #include "resolved-llmnr.h" #include "string-table.h" +#define TRANSACTIONS_MAX 4096 + static void dns_transaction_reset_answer(DnsTransaction *t) { assert(t); @@ -38,6 +42,18 @@ static void dns_transaction_reset_answer(DnsTransaction *t) { t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; t->answer_authenticated = false; + t->answer_nsec_ttl = (uint32_t) -1; +} + +static void dns_transaction_flush_dnssec_transactions(DnsTransaction *t) { + DnsTransaction *z; + + assert(t); + + while ((z = set_steal_first(t->dnssec_transactions))) { + set_remove(z->notify_transactions, t); + dns_transaction_gc(z); + } } static void dns_transaction_close_connection(DnsTransaction *t) { @@ -62,6 +78,8 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { if (!t) return NULL; + log_debug("Freeing transaction %" PRIu16 ".", t->id); + dns_transaction_close_connection(t); dns_transaction_stop_timeout(t); @@ -90,10 +108,7 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { set_remove(z->dnssec_transactions, t); set_free(t->notify_transactions); - while ((z = set_steal_first(t->dnssec_transactions))) { - set_remove(z->notify_transactions, t); - dns_transaction_gc(z); - } + dns_transaction_flush_dnssec_transactions(t); set_free(t->dnssec_transactions); dns_answer_unref(t->validated_keys); @@ -106,16 +121,36 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { DEFINE_TRIVIAL_CLEANUP_FUNC(DnsTransaction*, dns_transaction_free); -void dns_transaction_gc(DnsTransaction *t) { +bool dns_transaction_gc(DnsTransaction *t) { assert(t); if (t->block_gc > 0) - return; + return true; if (set_isempty(t->notify_query_candidates) && set_isempty(t->notify_zone_items) && - set_isempty(t->notify_transactions)) + set_isempty(t->notify_transactions)) { dns_transaction_free(t); + return false; + } + + return true; +} + +static uint16_t pick_new_id(Manager *m) { + uint16_t new_id; + + /* Find a fresh, unused transaction id. Note that this loop is bounded because there's a limit on the number of + * transactions, and it's much lower than the space of IDs. */ + + assert_cc(TRANSACTIONS_MAX < 0xFFFF); + + do + random_bytes(&new_id, sizeof(new_id)); + while (new_id == 0 || + hashmap_get(m->dns_transactions, UINT_TO_PTR(new_id))); + + return new_id; } int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) { @@ -129,11 +164,16 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) /* Don't allow looking up invalid or pseudo RRs */ if (!dns_type_is_valid_query(key->type)) return -EINVAL; + if (dns_type_is_obsolete(key->type)) + return -EOPNOTSUPP; /* We only support the IN class */ if (key->class != DNS_CLASS_IN && key->class != DNS_CLASS_ANY) return -EOPNOTSUPP; + if (hashmap_size(s->manager->dns_transactions) >= TRANSACTIONS_MAX) + return -EBUSY; + r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL); if (r < 0) return r; @@ -149,13 +189,11 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) t->dns_udp_fd = -1; t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + t->answer_nsec_ttl = (uint32_t) -1; t->key = dns_resource_key_ref(key); + t->current_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; - /* Find a fresh, unused transaction id */ - do - random_bytes(&t->id, sizeof(t->id)); - while (t->id == 0 || - hashmap_get(s->manager->dns_transactions, UINT_TO_PTR(t->id))); + t->id = pick_new_id(s->manager); r = hashmap_put(s->manager->dns_transactions, UINT_TO_PTR(t->id), t); if (r < 0) { @@ -182,6 +220,22 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) return 0; } +static void dns_transaction_shuffle_id(DnsTransaction *t) { + uint16_t new_id; + assert(t); + + /* Pick a new ID for this transaction. */ + + new_id = pick_new_id(t->scope->manager); + assert_se(hashmap_remove_and_put(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id), UINT_TO_PTR(new_id), t) >= 0); + + log_debug("Transaction %" PRIu16 " is now %" PRIu16 ".", t->id, new_id); + t->id = new_id; + + /* Make sure we generate a new packet with the new ID */ + t->sent = dns_packet_unref(t->sent); +} + static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { _cleanup_free_ char *pretty = NULL; DnsZoneItem *z; @@ -237,6 +291,7 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { if (state == DNS_TRANSACTION_DNSSEC_FAILED) log_struct(LOG_NOTICE, + LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_FAILURE), LOG_MESSAGE("DNSSEC validation failed for question %s: %s", dns_transaction_key_string(t), dnssec_result_to_string(t->answer_dnssec_result)), "DNS_TRANSACTION=%" PRIu16, t->id, "DNS_QUESTION=%s", dns_transaction_key_string(t), @@ -314,7 +369,7 @@ static int dns_transaction_pick_server(DnsTransaction *t) { if (!server) return -ESRCH; - t->current_features = dns_server_possible_feature_level(server); + t->current_feature_level = dns_server_possible_feature_level(server); if (server == t->server) return 0; @@ -325,6 +380,41 @@ static int dns_transaction_pick_server(DnsTransaction *t) { return 1; } +static void dns_transaction_retry(DnsTransaction *t) { + int r; + + assert(t); + + log_debug("Retrying transaction %" PRIu16 ".", t->id); + + /* Before we try again, switch to a new server. */ + dns_scope_next_dns_server(t->scope); + + r = dns_transaction_go(t); + if (r < 0) + dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); +} + +static int dns_transaction_maybe_restart(DnsTransaction *t) { + assert(t); + + if (!t->server) + return 0; + + if (t->current_feature_level <= dns_server_possible_feature_level(t->server)) + return 0; + + /* The server's current feature level is lower than when we sent the original query. We learnt something from + the response or possibly an auxiliary DNSSEC response that we didn't know before. We take that as reason to + restart the whole transaction. This is a good idea to deal with servers that respond rubbish if we include + OPT RR or DO bit. One of these cases is documented here, for example: + https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */ + + log_debug("Server feature level is now lower than when we began our transaction. Restarting with new ID."); + dns_transaction_shuffle_id(t); + return dns_transaction_go(t); +} + static int on_stream_complete(DnsStream *s, int error) { _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; DnsTransaction *t; @@ -339,11 +429,16 @@ static int on_stream_complete(DnsStream *s, int error) { t->stream = dns_stream_free(t->stream); - if (IN_SET(error, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE)) { - dns_transaction_complete(t, DNS_TRANSACTION_CONNECTION_FAILURE); + if (ERRNO_IS_DISCONNECT(error)) { + usec_t usec; + + log_debug_errno(error, "Connection failure for DNS TCP stream: %m"); + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &usec) >= 0); + dns_server_packet_lost(t->server, IPPROTO_TCP, t->current_feature_level, usec - t->start_usec); + + dns_transaction_retry(t); return 0; } - if (error != 0) { dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); return 0; @@ -387,7 +482,10 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { if (r < 0) return r; - r = dns_server_adjust_opt(t->server, t->sent, t->current_features); + if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) + return -EOPNOTSUPP; + + r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); if (r < 0) return r; @@ -473,6 +571,7 @@ static void dns_transaction_cache_answer(DnsTransaction *t) { t->answer_rcode, t->answer, t->answer_authenticated, + t->answer_nsec_ttl, 0, t->received->family, &t->received->sender); @@ -491,13 +590,86 @@ static bool dns_transaction_dnssec_is_live(DnsTransaction *t) { return false; } +static int dns_transaction_dnssec_ready(DnsTransaction *t) { + DnsTransaction *dt; + Iterator i; + + assert(t); + + /* Checks whether the auxiliary DNSSEC transactions of our transaction have completed, or are still + * ongoing. Returns 0, if we aren't ready for the DNSSEC validation, positive if we are. */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + switch (dt->state) { + + case DNS_TRANSACTION_NULL: + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: + /* Still ongoing */ + return 0; + + case DNS_TRANSACTION_RCODE_FAILURE: + if (dt->answer_rcode != DNS_RCODE_NXDOMAIN) { + log_debug("Auxiliary DNSSEC RR query failed with rcode=%s.", dns_rcode_to_string(dt->answer_rcode)); + goto fail; + } + + /* Fall-through: NXDOMAIN is good enough for us. This is because some DNS servers erronously + * return NXDOMAIN for empty non-terminals (Akamai...), and we need to handle that nicely, when + * asking for parent SOA or similar RRs to make unsigned proofs. */ + + case DNS_TRANSACTION_SUCCESS: + /* All good. */ + break; + + case DNS_TRANSACTION_DNSSEC_FAILED: + /* We handle DNSSEC failures different from other errors, as we care about the DNSSEC + * validationr result */ + + log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(dt->answer_dnssec_result)); + t->answer_dnssec_result = dt->answer_dnssec_result; /* Copy error code over */ + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); + return 0; + + + default: + log_debug("Auxiliary DNSSEC RR query failed with %s", dns_transaction_state_to_string(dt->state)); + goto fail; + } + } + + /* All is ready, we can go and validate */ + return 1; + +fail: + t->answer_dnssec_result = DNSSEC_FAILED_AUXILIARY; + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); + return 0; +} + static void dns_transaction_process_dnssec(DnsTransaction *t) { int r; assert(t); /* Are there ongoing DNSSEC transactions? If so, let's wait for them. */ - if (dns_transaction_dnssec_is_live(t)) + r = dns_transaction_dnssec_ready(t); + if (r < 0) { + dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + return; + } + if (r == 0) /* We aren't ready yet (or one of our auxiliary transactions failed, and we shouldn't validate now */ + return; + + /* See if we learnt things from the additional DNSSEC transactions, that we didn't know before, and better + * restart the lookup immediately. */ + r = dns_transaction_maybe_restart(t); + if (r < 0) { + dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + return; + } + if (r > 0) /* Transaction got restarted... */ return; /* All our auxiliary DNSSEC transactions are complete now. Try @@ -632,17 +804,11 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { /* Request failed, immediately try again with reduced features */ log_debug("Server returned error: %s", dns_rcode_to_string(DNS_PACKET_RCODE(p))); - dns_server_packet_failed(t->server, t->current_features); - - r = dns_transaction_go(t); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } - + dns_server_packet_failed(t->server, t->current_feature_level); + dns_transaction_retry(t); return; - } else - dns_server_packet_received(t->server, t->current_features, ts - t->start_usec, p->size); + } else if (DNS_PACKET_TC(p)) + dns_server_packet_truncated(t->server, t->current_feature_level); break; @@ -663,6 +829,8 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { return; } + log_debug("Reply truncated, retrying via TCP."); + /* Response was truncated, let's try again with good old TCP */ r = dns_transaction_open_tcp(t); if (r == -ESRCH) { @@ -670,34 +838,50 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); return; } + if (r == -EOPNOTSUPP) { + /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ + dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); + return; + } if (r < 0) { /* On LLMNR, if we cannot connect to the host, * we immediately give up */ - if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { + if (t->scope->protocol != DNS_PROTOCOL_DNS) { dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); return; } /* On DNS, couldn't send? Try immediately again, with a new server */ - dns_scope_next_dns_server(t->scope); - - r = dns_transaction_go(t); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } + dns_transaction_retry(t); } return; } - /* Parse message, if it isn't parsed yet. */ + /* After the superficial checks, actually parse the message. */ r = dns_packet_extract(p); if (r < 0) { dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); return; } + /* Report that the OPT RR was missing */ + if (t->server) { + if (!p->opt) + dns_server_packet_bad_opt(t->server, t->current_feature_level); + + dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, ts - t->start_usec, p->size); + } + + /* See if we know things we didn't know before that indicate we better restart the lookup immediately. */ + r = dns_transaction_maybe_restart(t); + if (r < 0) { + dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + return; + } + if (r > 0) /* Transaction got restarted... */ + return; + if (IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) { /* Only consider responses with equivalent query section to the request */ @@ -718,7 +902,22 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; t->answer_authenticated = false; + /* Block GC while starting requests for additional DNSSEC RRs */ + t->block_gc++; r = dns_transaction_request_dnssec_keys(t); + t->block_gc--; + + /* Maybe the transaction is ready for GC'ing now? If so, free it and return. */ + if (!dns_transaction_gc(t)) + return; + + /* Requesting additional keys might have resulted in + * this transaction to fail, since the auxiliary + * request failed for some reason. If so, we are not + * in pending state anymore, and we should exit + * quickly. */ + if (t->state != DNS_TRANSACTION_PENDING) + return; if (r < 0) { dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); return; @@ -744,15 +943,40 @@ static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *use assert(t->scope); r = manager_recv(t->scope->manager, fd, DNS_PROTOCOL_DNS, &p); - if (r <= 0) - return r; + if (ERRNO_IS_DISCONNECT(-r)) { + usec_t usec; - if (dns_packet_validate_reply(p) > 0 && - DNS_PACKET_ID(p) == t->id) - dns_transaction_process_reply(t, p); - else - log_debug("Invalid DNS UDP packet, ignoring."); + /* UDP connection failure get reported via ICMP and then are possible delivered to us on the next + * recvmsg(). Treat this like a lost packet. */ + + log_debug_errno(r, "Connection failure for DNS UDP packet: %m"); + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &usec) >= 0); + dns_server_packet_lost(t->server, IPPROTO_UDP, t->current_feature_level, usec - t->start_usec); + + dns_transaction_retry(t); + return 0; + } + if (r < 0) { + dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + return 0; + } + r = dns_packet_validate_reply(p); + if (r < 0) { + log_debug_errno(r, "Received invalid DNS packet as response, ignoring: %m"); + return 0; + } + if (r == 0) { + log_debug("Received inappropriate DNS packet as response, ignoring."); + return 0; + } + + if (DNS_PACKET_ID(p) != t->id) { + log_debug("Received packet with incorrect transaction ID, ignoring."); + return 0; + } + + dns_transaction_process_reply(t, p); return 0; } @@ -767,9 +991,12 @@ static int dns_transaction_emit_udp(DnsTransaction *t) { if (r < 0) return r; - if (t->current_features < DNS_SERVER_FEATURE_LEVEL_UDP) + if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_UDP) return -EAGAIN; + if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) + return -EOPNOTSUPP; + if (r > 0 || t->dns_udp_fd < 0) { /* Server changed, or no connection yet. */ int fd; @@ -785,10 +1012,11 @@ static int dns_transaction_emit_udp(DnsTransaction *t) { return r; } + (void) sd_event_source_set_description(t->dns_udp_event_source, "dns-transaction-udp"); t->dns_udp_fd = fd; } - r = dns_server_adjust_opt(t->server, t->sent, t->current_features); + r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); if (r < 0) return r; } else @@ -805,7 +1033,6 @@ static int dns_transaction_emit_udp(DnsTransaction *t) { static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) { DnsTransaction *t = userdata; - int r; assert(s); assert(t); @@ -816,7 +1043,7 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat case DNS_PROTOCOL_DNS: assert(t->server); - dns_server_packet_lost(t->server, t->current_features, usec - t->start_usec); + dns_server_packet_lost(t->server, t->stream ? IPPROTO_TCP : IPPROTO_UDP, t->current_feature_level, usec - t->start_usec); break; case DNS_PROTOCOL_LLMNR: @@ -834,13 +1061,7 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat log_debug("Timeout reached on transaction %" PRIu16 ".", t->id); - /* ...and try again with a new server */ - dns_scope_next_dns_server(t->scope); - - r = dns_transaction_go(t); - if (r < 0) - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - + dns_transaction_retry(t); return 0; } @@ -889,6 +1110,7 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { t->start_usec = ts; dns_transaction_reset_answer(t); + dns_transaction_flush_dnssec_transactions(t); /* Check the trust anchor. Do so only on classic DNS, since DNSSEC does not apply otherwise. */ if (t->scope->protocol == DNS_PROTOCOL_DNS) { @@ -902,6 +1124,41 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); return 0; } + + if (dns_name_is_root(DNS_RESOURCE_KEY_NAME(t->key)) && + t->key->type == DNS_TYPE_DS) { + + /* Hmm, this is a request for the root DS? A + * DS RR doesn't exist in the root zone, and + * if our trust anchor didn't know it either, + * this means we cannot do any DNSSEC logic + * anymore. */ + + 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 + * depending on this one can continue + * assuming there was no DS, and hence + * the root zone was unsigned. */ + + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR; + t->answer_authenticated = false; + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + } else + /* If we are not in downgrade mode, + * then fail the lookup, because we + * cannot reasonably answer it. There + * might be DS RRs, but we don't know + * them, and the DNS server won't tell + * them to us (and even if it would, + * we couldn't validate it and trust + * it). */ + dns_transaction_complete(t, DNS_TRANSACTION_NO_TRUST_ANCHOR); + + return 0; + } } /* Check the zone, but only if this transaction is not used @@ -1026,6 +1283,8 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) { if (r < 0) return r; + (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); + other->state = DNS_TRANSACTION_PENDING; other->next_attempt_after = ts; @@ -1141,6 +1400,8 @@ int dns_transaction_go(DnsTransaction *t) { if (r < 0) return r; + (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); + t->n_attempts = 0; t->next_attempt_after = ts; t->state = DNS_TRANSACTION_PENDING; @@ -1172,6 +1433,10 @@ int dns_transaction_go(DnsTransaction *t) { /* Try via UDP, and if that fails due to large size or lack of * support try via TCP */ r = dns_transaction_emit_udp(t); + if (r == -EMSGSIZE) + log_debug("Sending query via TCP since it is too large."); + if (r == -EAGAIN) + log_debug("Sending query via TCP since server doesn't support UDP."); if (r == -EMSGSIZE || r == -EAGAIN) r = dns_transaction_open_tcp(t); } @@ -1180,7 +1445,13 @@ int dns_transaction_go(DnsTransaction *t) { /* No servers to send this to? */ dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); return 0; - } else if (r < 0) { + } + if (r == -EOPNOTSUPP) { + /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ + dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); + return 0; + } + if (r < 0) { if (t->scope->protocol != DNS_PROTOCOL_DNS) { dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); return 0; @@ -1203,12 +1474,36 @@ int dns_transaction_go(DnsTransaction *t) { if (r < 0) return r; + (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); + t->state = DNS_TRANSACTION_PENDING; t->next_attempt_after = ts; return 1; } +static int dns_transaction_find_cyclic(DnsTransaction *t, DnsTransaction *aux) { + DnsTransaction *n; + Iterator i; + int r; + + assert(t); + assert(aux); + + /* Try to find cyclic dependencies between transaction objects */ + + if (t == aux) + return 1; + + SET_FOREACH(n, aux->dnssec_transactions, i) { + r = dns_transaction_find_cyclic(t, n); + if (r != 0) + return r; + } + + return 0; +} + static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResourceKey *key, DnsTransaction **ret) { DnsTransaction *aux; int r; @@ -1227,6 +1522,18 @@ static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResource *ret = aux; return 0; } + + r = dns_transaction_find_cyclic(t, aux); + if (r < 0) + return r; + if (r > 0) { + log_debug("Detected potential cyclic dependency, refusing to add transaction %" PRIu16 " (%s) as dependency for %" PRIu16 " (%s).", + aux->id, + strna(dns_transaction_key_string(aux)), + t->id, + strna(dns_transaction_key_string(t))); + return -ELOOP; + } } r = set_ensure_allocated(&t->dnssec_transactions, NULL); @@ -1263,12 +1570,6 @@ static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey * assert(t); assert(key); - r = dns_resource_key_equal(t->key, key); - if (r < 0) - return r; - if (r > 0) /* Don't go in circles */ - return 0; - /* Try to get the data from the trust anchor */ r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, key, &a); if (r < 0) @@ -1283,6 +1584,8 @@ static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey * /* This didn't work, ask for it via the network/cache then. */ r = dns_transaction_add_dnssec_transaction(t, key, &aux); + if (r == -ELOOP) /* This would result in a cyclic dependency */ + return 0; if (r < 0) return r; @@ -1292,7 +1595,7 @@ static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey * return r; } - return 0; + return 1; } static int dns_transaction_has_positive_answer(DnsTransaction *t, DnsAnswerFlags *flags) { @@ -1314,6 +1617,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; @@ -1330,7 +1652,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) @@ -1386,6 +1708,44 @@ static int dns_transaction_is_primary_response(DnsTransaction *t, DnsResourceRec return rr->key->type == DNS_TYPE_NSEC; } +static bool dns_transaction_dnssec_supported(DnsTransaction *t) { + assert(t); + + /* Checks whether our transaction's DNS server is assumed to be compatible with DNSSEC. Returns false as soon + * as we changed our mind about a server, and now believe it is incompatible with DNSSEC. */ + + if (t->scope->protocol != DNS_PROTOCOL_DNS) + return false; + + /* If we have picked no server, then we are working from the cache or some other source, and DNSSEC might well + * be supported, hence return true. */ + if (!t->server) + return true; + + if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_DO) + return false; + + return dns_server_dnssec_supported(t->server); +} + +static bool dns_transaction_dnssec_supported_full(DnsTransaction *t) { + DnsTransaction *dt; + Iterator i; + + assert(t); + + /* Checks whether our transaction our any of the auxiliary transactions couldn't do DNSSEC. */ + + if (!dns_transaction_dnssec_supported(t)) + return false; + + SET_FOREACH(dt, t->dnssec_transactions, i) + if (!dns_transaction_dnssec_supported(dt)) + return false; + + return true; +} + int dns_transaction_request_dnssec_keys(DnsTransaction *t) { DnsResourceRecord *rr; @@ -1409,11 +1769,10 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (t->scope->dnssec_mode == DNSSEC_NO) return 0; - - if (t->current_features < DNS_SERVER_FEATURE_LEVEL_DO) - return 0; /* Server doesn't do DNSSEC, there's no point in requesting any RRs then. */ - if (t->server && t->server->rrsig_missing) - return 0; /* Server handles DNSSEC requests, but isn't augmenting responses with RRSIGs. No point in trying DNSSEC then. */ + if (t->answer_source != DNS_TRANSACTION_NETWORK) + return 0; /* We only need to validate stuff from the network */ + if (!dns_transaction_dnssec_supported(t)) + return 0; /* If we can't do DNSSEC anyway there's no point in geting the auxiliary RRs */ DNS_ANSWER_FOREACH(rr, t->answer) { @@ -1421,7 +1780,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) @@ -1494,7 +1853,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (!ds) return -ENOMEM; - log_debug("Requesting DS to validate transaction %" PRIu16" (%s, DNSKEY with key tag: %" PRIu16 ").", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dnssec_keytag(rr)); + log_debug("Requesting DS to validate transaction %" PRIu16" (%s, DNSKEY with key tag: %" PRIu16 ").", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dnssec_keytag(rr, false)); r = dns_transaction_request_dnssec_rr(t, ds); if (r < 0) return r; @@ -1565,6 +1924,12 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (r > 0) continue; + r = dns_answer_has_dname_for_cname(t->answer, rr); + if (r < 0) + return r; + if (r > 0) + continue; + name = DNS_RESOURCE_KEY_NAME(rr->key); r = dns_name_parent(&name); if (r < 0) @@ -1664,69 +2029,15 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { } void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source) { - int r; - assert(t); assert(source); - if (!IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)) - return; - - /* Invoked whenever any of our auxiliary DNSSEC transactions - completed its work. We copy any RRs from that transaction - over into our list of validated keys -- but only if the - answer is authenticated. - - Note that we fail our transaction if the auxiliary - transaction failed, except on NXDOMAIN. This is because - some broken DNS servers (Akamai...) will return NXDOMAIN - for empty non-terminals. */ - - switch (source->state) { - - case DNS_TRANSACTION_DNSSEC_FAILED: - - log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(source->answer_dnssec_result)); - t->answer_dnssec_result = source->answer_dnssec_result; /* Copy error code over */ - dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); - break; - - case DNS_TRANSACTION_RCODE_FAILURE: - - if (source->answer_rcode != DNS_RCODE_NXDOMAIN) { - log_debug("Auxiliary DNSSEC RR query failed with rcode=%i.", source->answer_rcode); - goto fail; - } - - /* fall-through: NXDOMAIN is good enough for us */ - - case DNS_TRANSACTION_SUCCESS: - if (source->answer_authenticated) { - r = dns_answer_extend(&t->validated_keys, source->answer); - if (r < 0) { - log_error_errno(r, "Failed to merge validated DNSSEC key data: %m"); - goto fail; - } - } - - /* If the state is still PENDING, we are still in the loop - * that adds further DNSSEC transactions, hence don't check if - * we are ready yet. If the state is VALIDATING however, we - * should check if we are complete now. */ - if (t->state == DNS_TRANSACTION_VALIDATING) - dns_transaction_process_dnssec(t); - break; - - default: - log_debug("Auxiliary DNSSEC RR query failed with %s", dns_transaction_state_to_string(source->state)); - goto fail; - } + /* Invoked whenever any of our auxiliary DNSSEC transactions completed its work. If the state is still PENDING, + we are still in the loop that adds further DNSSEC transactions, hence don't check if we are ready yet. If + the state is VALIDATING however, we should check if we are complete now. */ - return; - -fail: - t->answer_dnssec_result = DNSSEC_FAILED_AUXILIARY; - dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); + if (t->state == DNS_TRANSACTION_VALIDATING) + dns_transaction_process_dnssec(t); } static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) { @@ -1741,7 +2052,7 @@ static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) { DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) { - r = dnssec_verify_dnskey_search(rr, t->validated_keys); + r = dnssec_verify_dnskey_by_ds_search(rr, t->validated_keys); if (r < 0) return r; if (r == 0) @@ -1771,7 +2082,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) @@ -1897,6 +2208,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; @@ -1914,12 +2285,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)) { @@ -1971,7 +2354,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) @@ -2044,9 +2427,91 @@ static int dns_transaction_known_signed(DnsTransaction *t, DnsResourceRecord *rr dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key)); } +static int dns_transaction_check_revoked_trust_anchors(DnsTransaction *t) { + DnsResourceRecord *rr; + int r; + + assert(t); + + /* Maybe warn the user that we encountered a revoked DNSKEY + * for a key from our trust anchor. Note that we don't care + * whether the DNSKEY can be authenticated or not. It's + * sufficient if it is self-signed. */ + + DNS_ANSWER_FOREACH(rr, t->answer) { + r = dns_trust_anchor_check_revoked(&t->scope->manager->trust_anchor, rr, t->answer); + if (r < 0) + return r; + } + + return 0; +} + +static int dns_transaction_invalidate_revoked_keys(DnsTransaction *t) { + bool changed; + int r; + + assert(t); + + /* Removes all DNSKEY/DS objects from t->validated_keys that + * our trust anchors database considers revoked. */ + + do { + DnsResourceRecord *rr; + + changed = false; + + DNS_ANSWER_FOREACH(rr, t->validated_keys) { + r = dns_trust_anchor_is_revoked(&t->scope->manager->trust_anchor, rr); + if (r < 0) + return r; + if (r > 0) { + r = dns_answer_remove_by_rr(&t->validated_keys, rr); + if (r < 0) + return r; + + assert(r > 0); + changed = true; + break; + } + } + } while (changed); + + return 0; +} + +static int dns_transaction_copy_validated(DnsTransaction *t) { + DnsTransaction *dt; + Iterator i; + int r; + + assert(t); + + /* Copy all validated RRs from the auxiliary DNSSEC transactions into our set of validated RRs */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (DNS_TRANSACTION_IS_LIVE(dt->state)) + continue; + + if (!dt->answer_authenticated) + continue; + + r = dns_answer_extend(&t->validated_keys, dt->answer); + if (r < 0) + return r; + } + + return 0; +} + int dns_transaction_validate_dnssec(DnsTransaction *t) { _cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL; - bool dnskeys_finalized = false; + enum { + PHASE_DNSKEY, /* Phase #1, only validate DNSKEYs */ + PHASE_NSEC, /* Phase #2, only validate NSEC+NSEC3 */ + PHASE_ALL, /* Phase #3, validate everything else */ + } phase; DnsResourceRecord *rr; DnsAnswerFlags flags; int r; @@ -2075,30 +2540,78 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { if (t->answer_source != DNS_TRANSACTION_NETWORK) return 0; - if (t->current_features < DNS_SERVER_FEATURE_LEVEL_DO || - (t->server && t->server->rrsig_missing)) { + if (!dns_transaction_dnssec_supported_full(t)) { /* The server does not support DNSSEC, or doesn't augment responses with RRSIGs. */ t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER; + log_debug("Not validating response, server lacks DNSSEC support."); return 0; } log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, dns_transaction_key_string(t)); - /* First see if there are DNSKEYs we already known a validated DS for. */ + /* First, see if this response contains any revoked trust + * anchors we care about */ + r = dns_transaction_check_revoked_trust_anchors(t); + if (r < 0) + return r; + + /* Third, copy all RRs we acquired successfully from auxiliary RRs over. */ + r = dns_transaction_copy_validated(t); + if (r < 0) + return r; + + /* Second, see if there are DNSKEYs we already know a + * validated DS for. */ r = dns_transaction_validate_dnskey_by_ds(t); if (r < 0) return r; + /* Fourth, remove all DNSKEY and DS RRs again that our trust + * anchor says are revoked. After all we might have marked + * some keys revoked above, but they might still be lingering + * in our validated_keys list. */ + r = dns_transaction_invalidate_revoked_keys(t); + if (r < 0) + return r; + + phase = PHASE_DNSKEY; for (;;) { - bool changed = false; + bool changed = false, have_nsec = false; DNS_ANSWER_FOREACH(rr, t->answer) { + DnsResourceRecord *rrsig = NULL; DnssecResult result; - if (rr->key->type == DNS_TYPE_RRSIG) + switch (rr->key->type) { + + case DNS_TYPE_RRSIG: continue; - r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result); + case DNS_TYPE_DNSKEY: + /* We validate DNSKEYs only in the DNSKEY and ALL phases */ + if (phase == PHASE_NSEC) + continue; + break; + + case DNS_TYPE_NSEC: + case DNS_TYPE_NSEC3: + have_nsec = true; + + /* We validate NSEC/NSEC3 only in the NSEC and ALL phases */ + if (phase == PHASE_DNSKEY) + continue; + + break; + + default: + /* We validate all other RRs only in the ALL phases */ + if (phase != PHASE_ALL) + continue; + + break; + } + + r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result, &rrsig); if (r < 0) return r; @@ -2116,6 +2629,14 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; + + /* some of the DNSKEYs we just + * added might already have + * been revoked, remove them + * again in that case. */ + r = dns_transaction_invalidate_revoked_keys(t); + if (r < 0) + return r; } /* Add the validated RRset to the new @@ -2132,127 +2653,198 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { /* Exit the loop, we dropped something from the answer, start from the beginning */ changed = true; break; + } - } else if (dnskeys_finalized) { + /* If we haven't read all DNSKEYs yet a negative result of the validation is irrelevant, as + * there might be more DNSKEYs coming. Similar, if we haven't read all NSEC/NSEC3 RRs yet, we + * cannot do positive wildcard proofs yet, as those require the NSEC/NSEC3 RRs. */ + if (phase != PHASE_ALL) + continue; - /* If we haven't read all DNSKEYs yet - * a negative result of the validation - * is irrelevant, as there might be - * more DNSKEYs coming. */ + if (result == DNSSEC_VALIDATED_WILDCARD) { + bool authenticated = false; + const char *source; - if (result == DNSSEC_NO_SIGNATURE) { - r = dns_transaction_requires_rrsig(t, rr); - if (r < 0) - return r; - if (r == 0) { - /* Data does not require signing. In that case, just copy it over, - * but remember that this is by no means authenticated.*/ - r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); - if (r < 0) - return r; + /* This RRset validated, but as a wildcard. This means we need to prove via NSEC/NSEC3 + * that no matching non-wildcard RR exists.*/ - t->scope->manager->n_dnssec_insecure++; + /* First step, determine the source of synthesis */ + r = dns_resource_record_source(rrsig, &source); + if (r < 0) + return r; - changed = true; - break; - } + r = dnssec_test_positive_wildcard( + validated, + DNS_RESOURCE_KEY_NAME(rr->key), + source, + rrsig->rrsig.signer, + &authenticated); - r = dns_transaction_known_signed(t, rr); + /* Unless the NSEC proof showed that the key really doesn't exist something is off. */ + if (r == 0) + result = DNSSEC_INVALID; + else { + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, authenticated ? (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE) : 0); if (r < 0) return r; - if (r > 0) { - /* This is an RR we know has to be signed. If it isn't this means - * the server is not attaching RRSIGs, hence complain. */ - dns_server_packet_rrsig_missing(t->server); + if (authenticated) + t->scope->manager->n_dnssec_secure++; + else + t->scope->manager->n_dnssec_insecure++; - if (t->scope->dnssec_mode == DNSSEC_DOWNGRADE_OK) { + /* Exit the loop, we dropped something from the answer, start from the beginning */ + changed = true; + break; + } + } - /* Downgrading is OK? If so, just consider the information unsigned */ + if (result == DNSSEC_NO_SIGNATURE) { + r = dns_transaction_requires_rrsig(t, rr); + if (r < 0) + return r; + if (r == 0) { + /* Data does not require signing. In that case, just copy it over, + * but remember that this is by no means authenticated.*/ + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); + if (r < 0) + return r; - 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; + } - t->scope->manager->n_dnssec_insecure++; - changed = true; - break; - } + r = dns_transaction_known_signed(t, rr); + if (r < 0) + return r; + if (r > 0) { + /* This is an RR we know has to be signed. If it isn't this means + * the server is not attaching RRSIGs, hence complain. */ - /* Otherwise, fail */ - t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER; - return 0; - } - } + dns_server_packet_rrsig_missing(t->server, t->current_feature_level); - if (IN_SET(result, - DNSSEC_MISSING_KEY, - DNSSEC_SIGNATURE_EXPIRED, - DNSSEC_UNSUPPORTED_ALGORITHM)) { + if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) { - r = dns_transaction_dnskey_authenticated(t, rr); - if (r < 0 && r != -ENXIO) - return r; - if (r == 0) { - /* The DNSKEY transaction was not authenticated, this means there's - * no DS for this, which means it's OK if no keys are found for this signature. */ + /* Downgrading is OK? If so, just consider the information unsigned */ 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, - DNSSEC_INVALID, - DNSSEC_SIGNATURE_EXPIRED, - DNSSEC_NO_SIGNATURE, - DNSSEC_UNSUPPORTED_ALGORITHM)) - t->scope->manager->n_dnssec_bogus++; - else - t->scope->manager->n_dnssec_indeterminate++; + /* Otherwise, fail */ + t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER; + return 0; + } - r = dns_transaction_is_primary_response(t, rr); + r = dns_transaction_in_private_tld(t, rr->key); if (r < 0) return r; if (r > 0) { - /* This is a primary response - * to our question, and it - * failed validation. That's - * fatal. */ - t->answer_dnssec_result = result; - return 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, + DNSSEC_MISSING_KEY, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_UNSUPPORTED_ALGORITHM)) { + + r = dns_transaction_dnskey_authenticated(t, rr); + if (r < 0 && r != -ENXIO) + return r; + if (r == 0) { + /* The DNSKEY transaction was not authenticated, this means there's + * no DS for this, which means it's OK if no keys are found for this signature. */ + + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); + if (r < 0) + return r; - /* This is just some auxiliary - * data. Just remove the RRset and - * continue. */ - r = dns_answer_remove_by_key(&t->answer, rr->key); + t->scope->manager->n_dnssec_insecure++; + changed = true; + break; + } + } + + if (IN_SET(result, + DNSSEC_INVALID, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_NO_SIGNATURE)) + t->scope->manager->n_dnssec_bogus++; + else /* DNSSEC_MISSING_KEY or DNSSEC_UNSUPPORTED_ALGORITHM */ + t->scope->manager->n_dnssec_indeterminate++; + + r = dns_transaction_is_primary_response(t, rr); + if (r < 0) + return r; + if (r > 0) { + + /* Look for a matching DNAME for this CNAME */ + r = dns_answer_has_dname_for_cname(t->answer, rr); if (r < 0) return r; + if (r == 0) { + /* Also look among the stuff we already validated */ + r = dns_answer_has_dname_for_cname(validated, rr); + if (r < 0) + return r; + } - /* Exit the loop, we dropped something from the answer, start from the beginning */ - changed = true; - break; + if (r == 0) { + /* This is a primary response to our question, and it failed validation. That's + * fatal. */ + t->answer_dnssec_result = result; + return 0; + } + + /* This is a primary response, but we do have a DNAME RR in the RR that can replay this + * CNAME, hence rely on that, and we can remove the CNAME in favour of it. */ } + + /* This is just some auxiliary data. Just remove the RRset and continue. */ + r = dns_answer_remove_by_key(&t->answer, rr->key); + if (r < 0) + return r; + + /* Exit the loop, we dropped something from the answer, start from the beginning */ + changed = true; + break; } + /* Restart the inner loop as long as we managed to achieve something */ if (changed) continue; - if (!dnskeys_finalized) { - /* OK, now we know we have added all DNSKEYs - * we possibly could to our validated - * list. Now run the whole thing once more, - * and strip everything we still cannot - * validate. - */ - dnskeys_finalized = true; + if (phase == PHASE_DNSKEY && have_nsec) { + /* OK, we processed all DNSKEYs, and there are NSEC/NSEC3 RRs, look at those now. */ + phase = PHASE_NSEC; + continue; + } + + if (phase != PHASE_ALL) { + /* OK, we processed all DNSKEYs and NSEC/NSEC3 RRs, look at all the rest now. Note that in this + * third phase we start to remove RRs we couldn't validate. */ + phase = PHASE_ALL; continue; } @@ -2288,7 +2880,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { bool authenticated = false; /* Bummer! Let's check NSEC/NSEC3 */ - r = dnssec_test_nsec(t->answer, t->key, &nr, &authenticated); + r = dnssec_nsec_test(t->answer, t->key, &nr, &authenticated, &t->answer_nsec_ttl); if (r < 0) return r; @@ -2373,9 +2965,10 @@ static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] [DNS_TRANSACTION_ATTEMPTS_MAX_REACHED] = "attempts-max-reached", [DNS_TRANSACTION_INVALID_REPLY] = "invalid-reply", [DNS_TRANSACTION_RESOURCES] = "resources", - [DNS_TRANSACTION_CONNECTION_FAILURE] = "connection-failure", [DNS_TRANSACTION_ABORTED] = "aborted", [DNS_TRANSACTION_DNSSEC_FAILED] = "dnssec-failed", + [DNS_TRANSACTION_NO_TRUST_ANCHOR] = "no-trust-anchor", + [DNS_TRANSACTION_RR_TYPE_UNSUPPORTED] = "rr-type-unsupported", }; DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState); diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h index faf3ce6fb9..76cf6e71db 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -36,9 +36,10 @@ enum DnsTransactionState { DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, DNS_TRANSACTION_INVALID_REPLY, DNS_TRANSACTION_RESOURCES, - DNS_TRANSACTION_CONNECTION_FAILURE, DNS_TRANSACTION_ABORTED, DNS_TRANSACTION_DNSSEC_FAILED, + DNS_TRANSACTION_NO_TRUST_ANCHOR, + DNS_TRANSACTION_RR_TYPE_UNSUPPORTED, _DNS_TRANSACTION_STATE_MAX, _DNS_TRANSACTION_STATE_INVALID = -1 }; @@ -80,6 +81,7 @@ struct DnsTransaction { int answer_rcode; DnssecResult answer_dnssec_result; DnsTransactionSource answer_source; + uint32_t answer_nsec_ttl; /* Indicates whether the primary answer is authenticated, * i.e. whether the RRs from answer which directly match the @@ -111,7 +113,7 @@ struct DnsTransaction { DnsServer *server; /* The features of the DNS server at time of transaction start */ - DnsServerFeatureLevel current_features; + DnsServerFeatureLevel current_feature_level; /* Query candidates this transaction is referenced by and that * shall be notified about this specific transaction @@ -140,7 +142,7 @@ struct DnsTransaction { int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key); DnsTransaction* dns_transaction_free(DnsTransaction *t); -void dns_transaction_gc(DnsTransaction *t); +bool dns_transaction_gc(DnsTransaction *t); int dns_transaction_go(DnsTransaction *t); void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p); diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c index 03c5b9406e..02d7ac91e1 100644 --- a/src/resolve/resolved-dns-trust-anchor.c +++ b/src/resolve/resolved-dns-trust-anchor.c @@ -19,6 +19,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <sd-messages.h> + #include "alloc-util.h" #include "conf-files.h" #include "def.h" @@ -28,18 +30,30 @@ #include "hexdecoct.h" #include "parse-util.h" #include "resolved-dns-trust-anchor.h" +#include "resolved-dns-dnssec.h" #include "set.h" #include "string-util.h" #include "strv.h" -static const char trust_anchor_dirs[] = CONF_PATHS_NULSTR("systemd/dnssec-trust-anchors.d"); +static const char trust_anchor_dirs[] = CONF_PATHS_NULSTR("dnssec-trust-anchors.d"); /* The DS RR from https://data.iana.org/root-anchors/root-anchors.xml, retrieved December 2015 */ 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; @@ -50,7 +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, "."))) + /* 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 */ @@ -82,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; @@ -188,6 +295,14 @@ static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, u r = safe_atou16(flags, &f); if (r < 0) return log_warning_errno(r, "Failed to parse DNSKEY flags field %s on line %s:%u", flags, path, line); + if ((f & DNSKEY_FLAG_ZONE_KEY) == 0) { + log_warning("DNSKEY lacks zone key bit set on line %s:%u", path, line); + return -EINVAL; + } + if ((f & DNSKEY_FLAG_REVOKE)) { + log_warning("DNSKEY is already revoked on line %s:%u", path, line); + return -EINVAL; + } a = dnssec_algorithm_from_string(algorithm); if (a < 0) { @@ -222,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); @@ -265,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) @@ -326,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; + + 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); - SET_FOREACH(n, d->negative_by_name, i) - log_info("%s%s", n, endswith(n, ".") ? "" : "."); + 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) { @@ -359,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); @@ -370,13 +511,18 @@ int dns_trust_anchor_load(DnsTrustAnchor *d) { void dns_trust_anchor_flush(DnsTrustAnchor *d) { DnsAnswer *a; + DnsResourceRecord *rr; assert(d); while ((a = hashmap_steal_first(d->positive_by_key))) dns_answer_unref(a); - d->positive_by_key = hashmap_free(d->positive_by_key); + + while ((rr = set_steal_first(d->revoked_by_rr))) + dns_resource_record_unref(rr); + d->revoked_by_rr = set_free(d->revoked_by_rr); + d->negative_by_name = set_free_free(d->negative_by_name); } @@ -405,3 +551,195 @@ int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name) { return set_contains(d->negative_by_name, name); } + +static int dns_trust_anchor_revoked_put(DnsTrustAnchor *d, DnsResourceRecord *rr) { + int r; + + assert(d); + + r = set_ensure_allocated(&d->revoked_by_rr, &dns_resource_record_hash_ops); + if (r < 0) + return r; + + r = set_put(d->revoked_by_rr, rr); + if (r < 0) + return r; + if (r > 0) + dns_resource_record_ref(rr); + + return r; +} + +static int dns_trust_anchor_remove_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) { + _cleanup_(dns_answer_unrefp) DnsAnswer *new_answer = NULL; + DnsAnswer *old_answer; + int r; + + /* Remember that this is a revoked trust anchor RR */ + r = dns_trust_anchor_revoked_put(d, rr); + if (r < 0) + return r; + + /* Remove this from the positive trust anchor */ + old_answer = hashmap_get(d->positive_by_key, rr->key); + if (!old_answer) + return 0; + + new_answer = dns_answer_ref(old_answer); + + r = dns_answer_remove_by_rr(&new_answer, rr); + if (r <= 0) + return r; + + /* We found the key! Warn the user */ + log_struct(LOG_WARNING, + LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED), + LOG_MESSAGE("DNSSEC Trust anchor %s has been revoked. Please update the trust anchor, or upgrade your operating system."), strna(dns_resource_record_to_string(rr)), + "TRUST_ANCHOR=%s", dns_resource_record_to_string(rr), + NULL); + + if (dns_answer_size(new_answer) <= 0) { + assert_se(hashmap_remove(d->positive_by_key, rr->key) == old_answer); + dns_answer_unref(old_answer); + return 1; + } + + r = hashmap_replace(d->positive_by_key, new_answer->items[0].rr->key, new_answer); + if (r < 0) + return r; + + new_answer = NULL; + dns_answer_unref(old_answer); + return 1; +} + +static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor *d, DnsResourceRecord *revoked_dnskey) { + DnsAnswer *a; + int r; + + assert(d); + assert(revoked_dnskey); + assert(revoked_dnskey->key->type == DNS_TYPE_DNSKEY); + assert(revoked_dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE); + + a = hashmap_get(d->positive_by_key, revoked_dnskey->key); + if (a) { + DnsResourceRecord *anchor; + + /* First, look for the precise DNSKEY in our trust anchor database */ + + DNS_ANSWER_FOREACH(anchor, a) { + + if (anchor->dnskey.protocol != revoked_dnskey->dnskey.protocol) + continue; + + if (anchor->dnskey.algorithm != revoked_dnskey->dnskey.algorithm) + continue; + + if (anchor->dnskey.key_size != revoked_dnskey->dnskey.key_size) + continue; + + /* Note that we allow the REVOKE bit to be + * different! It will be set in the revoked + * key, but unset in our version of it */ + if (((anchor->dnskey.flags ^ revoked_dnskey->dnskey.flags) | DNSKEY_FLAG_REVOKE) != DNSKEY_FLAG_REVOKE) + continue; + + if (memcmp(anchor->dnskey.key, revoked_dnskey->dnskey.key, anchor->dnskey.key_size) != 0) + continue; + + dns_trust_anchor_remove_revoked(d, anchor); + break; + } + } + + a = hashmap_get(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(revoked_dnskey->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(revoked_dnskey->key))); + if (a) { + DnsResourceRecord *anchor; + + /* Second, look for DS RRs matching this DNSKEY in our trust anchor database */ + + DNS_ANSWER_FOREACH(anchor, a) { + + /* We set mask_revoke to true here, since our + * DS fingerprint will be the one of the + * unrevoked DNSKEY, but the one we got passed + * here has the bit set. */ + r = dnssec_verify_dnskey_by_ds(revoked_dnskey, anchor, true); + if (r < 0) + return r; + if (r == 0) + continue; + + dns_trust_anchor_remove_revoked(d, anchor); + break; + } + } + + return 0; +} + +int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsResourceRecord *dnskey, DnsAnswer *rrs) { + DnsResourceRecord *rrsig; + int r; + + assert(d); + assert(dnskey); + + /* Looks if "dnskey" is a self-signed RR that has been revoked + * and matches one of our trust anchor entries. If so, removes + * it from the trust anchor and returns > 0. */ + + if (dnskey->key->type != DNS_TYPE_DNSKEY) + return 0; + + /* Is this DNSKEY revoked? */ + if ((dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE) == 0) + return 0; + + /* 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_positive(d, DNS_RESOURCE_KEY_NAME(dnskey->key))) + return 0; + + /* Look for a self-signed RRSIG in the other rrs belonging to this DNSKEY */ + DNS_ANSWER_FOREACH(rrsig, rrs) { + DnssecResult result; + + if (rrsig->key->type != DNS_TYPE_RRSIG) + continue; + + r = dnssec_rrsig_match_dnskey(rrsig, dnskey, true); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_verify_rrset(rrs, dnskey->key, rrsig, dnskey, USEC_INFINITY, &result); + if (r < 0) + return r; + if (result != DNSSEC_VALIDATED) + continue; + + /* Bingo! This is a revoked self-signed DNSKEY. Let's + * see if this precise one exists in our trust anchor + * database, too. */ + r = dns_trust_anchor_check_revoked_one(d, dnskey); + if (r < 0) + return r; + + return 1; + } + + return 0; +} + +int dns_trust_anchor_is_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) { + assert(d); + + if (!IN_SET(rr->key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY)) + return 0; + + return set_contains(d->revoked_by_rr, rr); +} diff --git a/src/resolve/resolved-dns-trust-anchor.h b/src/resolve/resolved-dns-trust-anchor.h index 1140cde041..5d137faae1 100644 --- a/src/resolve/resolved-dns-trust-anchor.h +++ b/src/resolve/resolved-dns-trust-anchor.h @@ -32,6 +32,7 @@ typedef struct DnsTrustAnchor DnsTrustAnchor; struct DnsTrustAnchor { Hashmap *positive_by_key; Set *negative_by_name; + Set *revoked_by_rr; }; int dns_trust_anchor_load(DnsTrustAnchor *d); @@ -39,3 +40,6 @@ void dns_trust_anchor_flush(DnsTrustAnchor *d); int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey* key, DnsAnswer **answer); int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name); + +int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsResourceRecord *dnskey, DnsAnswer *rrs); +int dns_trust_anchor_is_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr); diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf index c815eae850..82f26215df 100644 --- a/src/resolve/resolved-gperf.gperf +++ b/src/resolve/resolved-gperf.gperf @@ -14,8 +14,8 @@ 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.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode) diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c new file mode 100644 index 0000000000..20352a3e51 --- /dev/null +++ b/src/resolve/resolved-link-bus.c @@ -0,0 +1,528 @@ +/*-*- 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 "alloc-util.h" +#include "bus-util.h" +#include "parse-util.h" +#include "resolve-util.h" +#include "resolved-bus.h" +#include "resolved-link-bus.h" +#include "strv.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_resolve_support, resolve_support, ResolveSupport); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_dnssec_mode, dnssec_mode, DnssecMode); + +static int property_get_dns( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Link *l = userdata; + DnsServer *s; + int r; + + assert(reply); + assert(l); + + r = sd_bus_message_open_container(reply, 'a', "(iay)"); + if (r < 0) + return r; + + LIST_FOREACH(servers, s, l->dns_servers) { + r = bus_dns_server_append(reply, s, false); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_domains( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Link *l = userdata; + DnsSearchDomain *d; + int r; + + assert(reply); + assert(l); + + r = sd_bus_message_open_container(reply, 'a', "s"); + if (r < 0) + return r; + + LIST_FOREACH(domains, d, l->search_domains) { + r = sd_bus_message_append(reply, "s", d->name); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_scopes_mask( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Link *l = userdata; + uint64_t mask; + + assert(reply); + assert(l); + + mask = (l->unicast_scope ? SD_RESOLVED_DNS : 0) | + (l->llmnr_ipv4_scope ? SD_RESOLVED_LLMNR_IPV4 : 0) | + (l->llmnr_ipv6_scope ? SD_RESOLVED_LLMNR_IPV6 : 0) | + (l->mdns_ipv4_scope ? SD_RESOLVED_MDNS_IPV4 : 0) | + (l->mdns_ipv6_scope ? SD_RESOLVED_MDNS_IPV6 : 0); + + return sd_bus_message_append(reply, "t", mask); +} + +static int property_get_ntas( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Link *l = userdata; + const char *name; + Iterator i; + int r; + + assert(reply); + assert(l); + + r = sd_bus_message_open_container(reply, 'a', "s"); + if (r < 0) + return r; + + SET_FOREACH(name, l->dnssec_negative_trust_anchors, i) { + r = sd_bus_message_append(reply, "s", name); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_dnssec_supported( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Link *l = userdata; + + assert(reply); + assert(l); + + return sd_bus_message_append(reply, "b", link_dnssec_supported(l)); +} + +int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ struct in_addr_data *dns = NULL; + size_t allocated = 0, n = 0; + Link *l = userdata; + unsigned i; + int r; + + assert(message); + assert(l); + + r = sd_bus_message_enter_container(message, 'a', "(iay)"); + if (r < 0) + return r; + + for (;;) { + int family; + size_t sz; + const void *d; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_enter_container(message, 'r', "iay"); + if (r < 0) + return r; + if (r == 0) + break; + + r = sd_bus_message_read(message, "i", &family); + if (r < 0) + return r; + + if (!IN_SET(family, AF_INET, AF_INET6)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); + + r = sd_bus_message_read_array(message, 'y', &d, &sz); + if (r < 0) + return r; + if (sz != FAMILY_ADDRESS_SIZE(family)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid address size"); + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (!GREEDY_REALLOC(dns, allocated, n+1)) + return -ENOMEM; + + dns[n].family = family; + memcpy(&dns[n].address, d, sz); + n++; + } + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + dns_server_mark_all(l->dns_servers); + + for (i = 0; i < n; i++) { + DnsServer *s; + + s = dns_server_find(l->dns_servers, dns[i].family, &dns[i].address); + if (s) + dns_server_move_back_and_unmark(s); + else { + r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, dns[i].family, &dns[i].address); + if (r < 0) + goto clear; + } + + } + + dns_server_unlink_marked(l->dns_servers); + link_allocate_scopes(l); + + return sd_bus_reply_method_return(message, NULL); + +clear: + dns_server_unlink_all(l->dns_servers); + return r; +} + +int bus_link_method_set_search_domains(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char **domains = NULL; + Link *l = userdata; + char **i; + int r; + + assert(message); + assert(l); + + r = sd_bus_message_read_strv(message, &domains); + if (r < 0) + return r; + + STRV_FOREACH(i, domains) { + + r = dns_name_is_valid(*i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid search domain %s", *i); + if (dns_name_is_root(*i)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Root domain is not suitable as search domain"); + } + + dns_search_domain_mark_all(l->search_domains); + + STRV_FOREACH(i, domains) { + DnsSearchDomain *d; + + r = dns_search_domain_find(l->search_domains, *i, &d); + if (r < 0) + goto clear; + + if (r > 0) + dns_search_domain_move_back_and_unmark(d); + else { + r = dns_search_domain_new(l->manager, NULL, DNS_SEARCH_DOMAIN_LINK, l, *i); + if (r < 0) + goto clear; + } + } + + dns_search_domain_unlink_marked(l->search_domains); + return sd_bus_reply_method_return(message, NULL); + +clear: + dns_search_domain_unlink_all(l->search_domains); + return r; +} + +int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + ResolveSupport mode; + const char *llmnr; + int r; + + assert(message); + assert(l); + + r = sd_bus_message_read(message, "s", &llmnr); + if (r < 0) + return r; + + if (isempty(llmnr)) + mode = RESOLVE_SUPPORT_YES; + else { + mode = resolve_support_from_string(llmnr); + if (mode < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid LLMNR setting: %s", llmnr); + } + + l->llmnr_support = mode; + link_allocate_scopes(l); + link_add_rrs(l, false); + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + ResolveSupport mode; + const char *mdns; + int r; + + assert(message); + assert(l); + + r = sd_bus_message_read(message, "s", &mdns); + if (r < 0) + return r; + + if (isempty(mdns)) + mode = RESOLVE_SUPPORT_NO; + else { + mode = resolve_support_from_string(mdns); + if (mode < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid MulticastDNS setting: %s", mdns); + } + + l->mdns_support = mode; + link_allocate_scopes(l); + link_add_rrs(l, false); + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + const char *dnssec; + DnssecMode mode; + int r; + + assert(message); + assert(l); + + r = sd_bus_message_read(message, "s", &dnssec); + if (r < 0) + return r; + + if (isempty(dnssec)) + mode = _DNSSEC_MODE_INVALID; + else { + mode = dnssec_mode_from_string(dnssec); + if (mode < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNSSEC setting: %s", dnssec); + } + + link_set_dnssec_mode(l, mode); + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_set_free_free_ Set *ns = NULL; + _cleanup_free_ char **ntas = NULL; + Link *l = userdata; + int r; + char **i; + + assert(message); + assert(l); + + r = sd_bus_message_read_strv(message, &ntas); + if (r < 0) + return r; + + STRV_FOREACH(i, ntas) { + r = dns_name_is_valid(*i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid search negative trust anchor domain: %s", *i); + } + + 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 sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_revert(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + + assert(message); + assert(l); + + link_flush_settings(l); + link_allocate_scopes(l); + link_add_rrs(l, false); + + return sd_bus_reply_method_return(message, NULL); +} + +const sd_bus_vtable link_vtable[] = { + SD_BUS_VTABLE_START(0), + + SD_BUS_PROPERTY("ScopesMask", "t", property_get_scopes_mask, 0, 0), + SD_BUS_PROPERTY("DNS", "a(iay)", property_get_dns, 0, 0), + SD_BUS_PROPERTY("Domains", "as", property_get_domains, 0, 0), + SD_BUS_PROPERTY("LLMNR", "s", property_get_resolve_support, offsetof(Link, llmnr_support), 0), + SD_BUS_PROPERTY("MulticastDNS", "s", property_get_resolve_support, offsetof(Link, mdns_support), 0), + SD_BUS_PROPERTY("DNSSEC", "s", property_get_dnssec_mode, offsetof(Link, dnssec_mode), 0), + SD_BUS_PROPERTY("DNSSECNegativeTrustAnchors", "as", property_get_ntas, 0, 0), + SD_BUS_PROPERTY("DNSSECSupport", "b", property_get_dnssec_supported, 0, 0), + + SD_BUS_METHOD("SetDNS", "a(iay)", NULL, bus_link_method_set_dns_servers, 0), + SD_BUS_METHOD("SetDomains", "as", NULL, bus_link_method_set_search_domains, 0), + SD_BUS_METHOD("SetLLMNR", "s", NULL, bus_link_method_set_llmnr, 0), + SD_BUS_METHOD("SetMulticastDNS", "s", NULL, bus_link_method_set_mdns, 0), + SD_BUS_METHOD("SetDNSSEC", "s", NULL, bus_link_method_set_dnssec, 0), + SD_BUS_METHOD("SetDNSSECNegativeTrustAnchors", "as", NULL, bus_link_method_set_dnssec_negative_trust_anchors, 0), + SD_BUS_METHOD("Revert", NULL, NULL, bus_link_method_revert, 0), + + SD_BUS_VTABLE_END +}; + +int link_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + _cleanup_free_ char *e = NULL; + Manager *m = userdata; + int ifindex; + Link *link; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + r = sd_bus_path_decode(path, "/org/freedesktop/resolve1/link", &e); + if (r <= 0) + return 0; + + r = parse_ifindex(e, &ifindex); + if (r < 0) + return 0; + + link = hashmap_get(m->links, INT_TO_PTR(ifindex)); + if (!link) + return 0; + + *found = link; + return 1; +} + +char *link_bus_path(Link *link) { + _cleanup_free_ char *ifindex = NULL; + char *p; + int r; + + assert(link); + + if (asprintf(&ifindex, "%i", link->ifindex) < 0) + return NULL; + + r = sd_bus_path_encode("/org/freedesktop/resolve1/link", ifindex, &p); + if (r < 0) + return NULL; + + return p; +} + +int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_strv_free_ char **l = NULL; + Manager *m = userdata; + Link *link; + Iterator i; + unsigned c = 0; + + assert(bus); + assert(path); + assert(m); + assert(nodes); + + l = new0(char*, hashmap_size(m->links) + 1); + if (!l) + return -ENOMEM; + + HASHMAP_FOREACH(link, m->links, i) { + char *p; + + p = link_bus_path(link); + if (!p) + return -ENOMEM; + + l[c++] = p; + } + + l[c] = NULL; + *nodes = l; + l = NULL; + + return 1; +} diff --git a/src/resolve/resolved-link-bus.h b/src/resolve/resolved-link-bus.h new file mode 100644 index 0000000000..d444957d1c --- /dev/null +++ b/src/resolve/resolved-link-bus.h @@ -0,0 +1,40 @@ +/*-*- 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 "sd-bus.h" + +#include "resolved-link.h" + +extern const sd_bus_vtable link_vtable[]; + +int link_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); +char *link_bus_path(Link *link); +int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); + +int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_search_domains(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_revert(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index 0fe2bb30bd..b203f19dbb 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -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) @@ -61,15 +63,27 @@ int link_new(Manager *m, Link **ret, int ifindex) { return 0; } +void link_flush_settings(Link *l) { + assert(l); + + l->llmnr_support = RESOLVE_SUPPORT_YES; + l->mdns_support = RESOLVE_SUPPORT_NO; + l->dnssec_mode = _DNSSEC_MODE_INVALID; + + dns_server_unlink_all(l->dns_servers); + dns_search_domain_unlink_all(l->search_domains); + + l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors); +} + Link *link_free(Link *l) { if (!l) return NULL; - dns_server_unlink_marked(l->dns_servers); - dns_search_domain_unlink_all(l->search_domains); + link_flush_settings(l); while (l->addresses) - link_address_free(l->addresses); + (void) link_address_free(l->addresses); if (l->manager) hashmap_remove(l->manager->links, INT_TO_PTR(l->ifindex)); @@ -84,12 +98,13 @@ Link *link_free(Link *l) { return NULL; } -static void link_allocate_scopes(Link *l) { +void link_allocate_scopes(Link *l) { int r; assert(l); - if (l->dns_servers) { + if (link_relevant(l, AF_UNSPEC, false) && + l->dns_servers) { if (!l->unicast_scope) { r = dns_scope_new(l->manager, &l->unicast_scope, l, DNS_PROTOCOL_DNS, AF_UNSPEC); if (r < 0) @@ -98,9 +113,9 @@ static void link_allocate_scopes(Link *l) { } else 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) { + if (link_relevant(l, AF_INET, true) && + 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) @@ -109,9 +124,9 @@ static void link_allocate_scopes(Link *l) { } else 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 && + if (link_relevant(l, AF_INET6, true) && + 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); @@ -121,9 +136,9 @@ static void link_allocate_scopes(Link *l) { } else 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) { + if (link_relevant(l, AF_INET, true) && + 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) @@ -132,9 +147,9 @@ static void link_allocate_scopes(Link *l) { } else 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) { + if (link_relevant(l, AF_INET6, true) && + 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 +248,130 @@ 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; + l->llmnr_support = resolve_support_from_string(b); + if (l->llmnr_support < 0) { + r = -EINVAL; + goto clear; + } + + return 0; + +clear: + 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; +} + +void link_set_dnssec_mode(Link *l, DnssecMode mode) { + + assert(l); + + if (l->dnssec_mode == mode) + return; + + if ((l->dnssec_mode == _DNSSEC_MODE_INVALID) || + (l->dnssec_mode == DNSSEC_NO && mode != DNSSEC_NO) || + (l->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE && mode == DNSSEC_YES)) { + + /* When switching from non-DNSSEC mode to DNSSEC mode, flush the cache. Also when switching from the + * allow-downgrade mode to full DNSSEC mode, flush it too. */ + if (l->unicast_scope) + dns_cache_flush(&l->unicast_scope->cache); + } + + l->dnssec_mode = mode; +} + +static int link_update_dnssec_mode(Link *l) { + _cleanup_free_ char *m = NULL; + DnssecMode mode; + 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; + + mode = dnssec_mode_from_string(m); + if (mode < 0) { + r = -EINVAL; + goto clear; + } + + link_set_dnssec_mode(l, mode); + + return 0; + +clear: + l->dnssec_mode = _DNSSEC_MODE_INVALID; + return r; +} - } else if (r > 0) - l->llmnr_support = SUPPORT_YES; - else - l->llmnr_support = SUPPORT_NO; +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->llmnr_support = SUPPORT_YES; + l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors); return r; } @@ -294,46 +417,109 @@ clear: return r; } -int link_update_monitor(Link *l) { +static int link_is_unmanaged(Link *l) { + _cleanup_free_ char *state = NULL; int r; assert(l); - link_update_dns_servers(l); - link_update_llmnr_support(l); - link_allocate_scopes(l); + r = sd_network_link_get_setup_state(l->ifindex, &state); + if (r == -ENODATA) + return 1; + if (r < 0) + return r; + + return STR_IN_SET(state, "pending", "unmanaged"); +} + +static void link_read_settings(Link *l) { + int r; + + assert(l); + + /* Read settings from networkd, except when networkd is not managing this interface. */ + + r = link_is_unmanaged(l); + if (r < 0) { + log_warning_errno(r, "Failed to determine whether interface %s is managed: %m", l->name); + return; + } + if (r > 0) { + + /* If this link used to be managed, but is now unmanaged, flush all our settings -- but only once. */ + if (l->is_managed) + link_flush_settings(l); + + l->is_managed = false; + return; + } + + l->is_managed = true; + + 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); +} +int link_update_monitor(Link *l) { + assert(l); + + link_read_settings(l); + link_allocate_scopes(l); link_add_rrs(l, false); return 0; } -bool link_relevant(Link *l, int family) { +bool link_relevant(Link *l, int family, bool multicast) { _cleanup_free_ char *state = NULL; LinkAddress *a; assert(l); - /* A link is relevant if it isn't a loopback or pointopoint - * device, has a link beat, can do multicast and has at least - * one relevant IP address */ + /* A link is relevant for multicast traffic if it isn't a loopback or pointopoint device, has a link beat, can + * do multicast and has at least one relevant IP address */ - if (l->flags & (IFF_LOOPBACK|IFF_POINTOPOINT|IFF_DORMANT)) + if (l->flags & (IFF_LOOPBACK|IFF_DORMANT)) return false; - if ((l->flags & (IFF_UP|IFF_LOWER_UP|IFF_MULTICAST)) != (IFF_UP|IFF_LOWER_UP|IFF_MULTICAST)) + if ((l->flags & (IFF_UP|IFF_LOWER_UP)) != (IFF_UP|IFF_LOWER_UP)) return false; + if (multicast) { + if (l->flags & IFF_POINTOPOINT) + return false; + + if ((l->flags & IFF_MULTICAST) != IFF_MULTICAST) + return false; + } + sd_network_link_get_operational_state(l->ifindex, &state); if (state && !STR_IN_SET(state, "unknown", "degraded", "routable")) return false; LIST_FOREACH(addresses, a, l->addresses) - if (a->family == family && link_address_relevant(a)) + if ((family == AF_UNSPEC || a->family == family) && link_address_relevant(a)) return true; return false; @@ -357,12 +543,8 @@ DnsServer* link_set_dns_server(Link *l, DnsServer *s) { if (l->current_dns_server == s) return s; - if (s) { - _cleanup_free_ char *ip = NULL; - - in_addr_to_string(s->family, &s->address, &ip); - log_info("Switching to DNS server %s for interface %s.", strna(ip), l->name); - } + if (s) + log_info("Switching to DNS server %s for interface %s.", dns_server_string(s), l->name); dns_server_unref(l->current_dns_server); l->current_dns_server = dns_server_ref(s); @@ -398,6 +580,30 @@ void link_next_dns_server(Link *l) { link_set_dns_server(l, l->dns_servers); } +DnssecMode link_get_dnssec_mode(Link *l) { + assert(l); + + if (l->dnssec_mode != _DNSSEC_MODE_INVALID) + return l->dnssec_mode; + + return manager_get_dnssec_mode(l->manager); +} + +bool link_dnssec_supported(Link *l) { + DnsServer *server; + + assert(l); + + if (link_get_dnssec_mode(l) == DNSSEC_NO) + return false; + + server = link_get_dns_server(l); + if (server) + return dns_server_dnssec_supported(server); + + return true; +} + int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr) { LinkAddress *a; @@ -459,8 +665,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 +722,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); diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h index a3b406bbc2..6544214b77 100644 --- a/src/resolve/resolved-link.h +++ b/src/resolve/resolved-link.h @@ -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; @@ -75,6 +78,8 @@ struct Link { DnsScope *mdns_ipv4_scope; DnsScope *mdns_ipv6_scope; + bool is_managed; + char name[IF_NAMESIZE]; uint32_t mtu; }; @@ -83,14 +88,21 @@ int link_new(Manager *m, Link **ret, int ifindex); Link *link_free(Link *l); int link_update_rtnl(Link *l, sd_netlink_message *m); int link_update_monitor(Link *l); -bool link_relevant(Link *l, int family); +bool link_relevant(Link *l, int family, bool multicast); LinkAddress* link_find_address(Link *l, int family, const union in_addr_union *in_addr); void link_add_rrs(Link *l, bool force_remove); +void link_flush_settings(Link *l); +void link_set_dnssec_mode(Link *l, DnssecMode mode); +void link_allocate_scopes(Link *l); + DnsServer* link_set_dns_server(Link *l, DnsServer *s); DnsServer* link_get_dns_server(Link *l); void link_next_dns_server(Link *l); +DnssecMode link_get_dnssec_mode(Link *l); +bool link_dnssec_supported(Link *l); + int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr); LinkAddress *link_address_free(LinkAddress *a); int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m); diff --git a/src/resolve/resolved-llmnr.c b/src/resolve/resolved-llmnr.c index 182c6e45ea..f52ab8f384 100644 --- a/src/resolve/resolved-llmnr.c +++ b/src/resolve/resolved-llmnr.c @@ -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; @@ -193,6 +193,8 @@ int manager_llmnr_ipv4_udp_fd(Manager *m) { if (r < 0) goto fail; + (void) sd_event_source_set_description(m->llmnr_ipv4_udp_event_source, "llmnr-ipv4-udp"); + return m->llmnr_ipv4_udp_fd; fail: @@ -267,10 +269,10 @@ int manager_llmnr_ipv6_udp_fd(Manager *m) { } r = sd_event_add_io(m->event, &m->llmnr_ipv6_udp_event_source, m->llmnr_ipv6_udp_fd, EPOLLIN, on_llmnr_packet, m); - if (r < 0) { - r = -errno; + if (r < 0) goto fail; - } + + (void) sd_event_source_set_description(m->llmnr_ipv6_udp_event_source, "llmnr-ipv6-udp"); return m->llmnr_ipv6_udp_fd; @@ -393,6 +395,8 @@ int manager_llmnr_ipv4_tcp_fd(Manager *m) { if (r < 0) goto fail; + (void) sd_event_source_set_description(m->llmnr_ipv4_tcp_event_source, "llmnr-ipv4-tcp"); + return m->llmnr_ipv4_tcp_fd; fail: @@ -464,6 +468,8 @@ int manager_llmnr_ipv6_tcp_fd(Manager *m) { if (r < 0) goto fail; + (void) sd_event_source_set_description(m->llmnr_ipv6_tcp_event_source, "llmnr-ipv6-tcp"); + return m->llmnr_ipv6_tcp_fd; fail: diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 20955b3f6b..d6d75a3f78 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -313,6 +313,8 @@ static int manager_network_monitor_listen(Manager *m) { if (r < 0) return r; + (void) sd_event_source_set_description(m->network_event_source, "network-monitor"); + return 0; } @@ -420,6 +422,8 @@ static int manager_watch_hostname(Manager *m) { return log_error_errno(r, "Failed to add hostname event source: %m"); } + (void) sd_event_source_set_description(m->hostname_event_source, "hostname"); + r = determine_hostname(&m->llmnr_hostname, &m->mdns_hostname); if (r < 0) { log_info("Defaulting to hostname 'linux'."); @@ -476,7 +480,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 +490,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; @@ -1164,9 +1174,32 @@ 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); +DnssecMode manager_get_dnssec_mode(Manager *m) { + assert(m); + + if (m->dnssec_mode != _DNSSEC_MODE_INVALID) + return m->dnssec_mode; + + return DNSSEC_NO; +} + +bool manager_dnssec_supported(Manager *m) { + DnsServer *server; + Iterator i; + Link *l; + + assert(m); + + if (manager_get_dnssec_mode(m) == DNSSEC_NO) + return false; + + server = manager_get_dns_server(m); + if (server && !dns_server_dnssec_supported(server)) + return false; + + HASHMAP_FOREACH(l, m->links, i) + if (!link_dnssec_supported(l)) + return false; + + return true; +} diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index dd85d3ba47..8b13074298 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -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; @@ -166,5 +159,5 @@ 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_; +DnssecMode manager_get_dnssec_mode(Manager *m); +bool manager_dnssec_supported(Manager *m); diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c index db23bc9d42..d5b253d4f5 100644 --- a/src/resolve/resolved-mdns.c +++ b/src/resolve/resolved-mdns.c @@ -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; @@ -122,7 +122,7 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us dns_transaction_process_reply(t, p); } - dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, 0, p->family, &p->sender); + dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender); } else if (dns_packet_validate_query(p) > 0) { log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c index 956f380f3c..7567f4c369 100644 --- a/src/resolve/resolved-resolv-conf.c +++ b/src/resolve/resolved-resolv-conf.c @@ -147,16 +147,14 @@ clear: } static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { - _cleanup_free_ char *t = NULL; - int r; - assert(s); assert(f); assert(count); - r = in_addr_to_string(s->family, &s->address, &t); - if (r < 0) { - log_warning_errno(r, "Invalid DNS address. Ignoring: %m"); + (void) dns_server_string(s); + + if (!s->server_string) { + log_warning("Our of memory, or invalid DNS address. Ignoring server."); return; } @@ -164,7 +162,7 @@ static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f); (*count) ++; - fprintf(f, "nameserver %s\n", t); + fprintf(f, "nameserver %s\n", s->server_string); } static void write_resolv_conf_search( diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c index be406b71fe..472bb32764 100644 --- a/src/resolve/resolved.c +++ b/src/resolve/resolved.c @@ -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"); diff --git a/src/resolve/test-dnssec-complex.c b/src/resolve/test-dnssec-complex.c new file mode 100644 index 0000000000..caac251e83 --- /dev/null +++ b/src/resolve/test-dnssec-complex.c @@ -0,0 +1,238 @@ +/*-*- 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 <netinet/ip.h> + +#include "sd-bus.h" + +#include "af-list.h" +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "dns-type.h" +#include "random-util.h" +#include "string-util.h" +#include "time-util.h" + +#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) + +static void prefix_random(const char *name, char **ret) { + uint64_t i, u; + char *m = NULL; + + u = 1 + (random_u64() & 3); + + for (i = 0; i < u; i++) { + _cleanup_free_ char *b = NULL; + char *x; + + assert_se(asprintf(&b, "x%" PRIu64 "x", random_u64())); + x = strjoin(b, ".", name, NULL); + assert_se(x); + + free(m); + m = x; + } + + *ret = m; + } + +static void test_rr_lookup(sd_bus *bus, const char *name, uint16_t type, const char *result) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *m = NULL; + int r; + + /* If the name starts with a dot, we prefix one to three random labels */ + if (startswith(name, ".")) { + prefix_random(name + 1, &m); + name = m; + } + + assert_se(sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveRecord") >= 0); + + assert_se(sd_bus_message_append(req, "isqqt", 0, name, DNS_CLASS_IN, type, UINT64_C(0)) >= 0); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + + if (r < 0) { + assert_se(result); + assert_se(sd_bus_error_has_name(&error, result)); + log_info("[OK] %s/%s resulted in <%s>.", name, dns_type_to_string(type), error.name); + } else { + assert_se(!result); + log_info("[OK] %s/%s succeeded.", name, dns_type_to_string(type)); + } +} + +static void test_hostname_lookup(sd_bus *bus, const char *name, int family, const char *result) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *m = NULL; + const char *af; + int r; + + af = family == AF_UNSPEC ? "AF_UNSPEC" : af_to_name(family); + + /* If the name starts with a dot, we prefix one to three random labels */ + if (startswith(name, ".")) { + prefix_random(name + 1, &m); + name = m; + } + + assert_se(sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveHostname") >= 0); + + assert_se(sd_bus_message_append(req, "isit", 0, name, family, UINT64_C(0)) >= 0); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + + if (r < 0) { + assert_se(result); + assert_se(sd_bus_error_has_name(&error, result)); + log_info("[OK] %s/%s resulted in <%s>.", name, af, error.name); + } else { + assert_se(!result); + log_info("[OK] %s/%s succeeded.", name, af); + } + +} + +int main(int argc, char* argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + + /* Note that this is a manual test as it requires: + * + * Full network access + * A DNSSEC capable DNS server + * That zones contacted are still set up as they were when I wrote this. + */ + + assert_se(sd_bus_open_system(&bus) >= 0); + + /* Normally signed */ + test_rr_lookup(bus, "www.eurid.eu", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, "www.eurid.eu", AF_UNSPEC, NULL); + + test_rr_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, "sigok.verteiltesysteme.net", AF_UNSPEC, NULL); + + /* Normally signed, NODATA */ + test_rr_lookup(bus, "www.eurid.eu", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + test_rr_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + + /* Invalid signature */ + test_rr_lookup(bus, "sigfail.verteiltesysteme.net", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); + test_hostname_lookup(bus, "sigfail.verteiltesysteme.net", AF_INET, BUS_ERROR_DNSSEC_FAILED); + + /* Invalid signature, RSA, wildcard */ + test_rr_lookup(bus, ".wilda.rhybar.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); + test_hostname_lookup(bus, ".wilda.rhybar.0skar.cz", AF_INET, BUS_ERROR_DNSSEC_FAILED); + + /* Invalid signature, ECDSA, wildcard */ + test_rr_lookup(bus, ".wilda.rhybar.ecdsa.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); + test_hostname_lookup(bus, ".wilda.rhybar.ecdsa.0skar.cz", AF_INET, BUS_ERROR_DNSSEC_FAILED); + + /* NXDOMAIN in NSEC domain */ + test_rr_lookup(bus, "hhh.nasa.gov", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "hhh.nasa.gov", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); + + /* wildcard, NSEC zone */ + test_rr_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, ".wilda.nsec.0skar.cz", AF_INET, NULL); + + /* wildcard, NSEC zone, NODATA */ + test_rr_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + + /* wildcard, NSEC3 zone */ + test_rr_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, ".wilda.0skar.cz", AF_INET, NULL); + + /* wildcard, NSEC3 zone, NODATA */ + test_rr_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + + /* wildcard, NSEC zone, CNAME */ + test_rr_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, ".wild.nsec.0skar.cz", AF_UNSPEC, NULL); + test_hostname_lookup(bus, ".wild.nsec.0skar.cz", AF_INET, NULL); + + /* wildcard, NSEC zone, NODATA, CNAME */ + test_rr_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + + /* wildcard, NSEC3 zone, CNAME */ + test_rr_lookup(bus, ".wild.0skar.cz", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, ".wild.0skar.cz", AF_UNSPEC, NULL); + test_hostname_lookup(bus, ".wild.0skar.cz", AF_INET, NULL); + + /* wildcard, NSEC3 zone, NODATA, CNAME */ + test_rr_lookup(bus, ".wild.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + + /* NODATA due to empty non-terminal in NSEC domain */ + test_rr_lookup(bus, "herndon.nasa.gov", DNS_TYPE_A, BUS_ERROR_NO_SUCH_RR); + test_hostname_lookup(bus, "herndon.nasa.gov", AF_UNSPEC, BUS_ERROR_NO_SUCH_RR); + test_hostname_lookup(bus, "herndon.nasa.gov", AF_INET, BUS_ERROR_NO_SUCH_RR); + test_hostname_lookup(bus, "herndon.nasa.gov", AF_INET6, BUS_ERROR_NO_SUCH_RR); + + /* NXDOMAIN in NSEC root zone: */ + test_rr_lookup(bus, "jasdhjas.kjkfgjhfjg", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); + + /* NXDOMAIN in NSEC3 .com zone: */ + test_rr_lookup(bus, "kjkfgjhfjgsdfdsfd.com", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); + + /* Unsigned A */ + test_rr_lookup(bus, "poettering.de", DNS_TYPE_A, NULL); + test_rr_lookup(bus, "poettering.de", DNS_TYPE_AAAA, NULL); + test_hostname_lookup(bus, "poettering.de", AF_UNSPEC, NULL); + test_hostname_lookup(bus, "poettering.de", AF_INET, NULL); + test_hostname_lookup(bus, "poettering.de", AF_INET6, NULL); + +#if HAVE_LIBIDN + /* Unsigned A with IDNA conversion necessary */ + test_hostname_lookup(bus, "pöttering.de", AF_UNSPEC, NULL); + test_hostname_lookup(bus, "pöttering.de", AF_INET, NULL); + test_hostname_lookup(bus, "pöttering.de", AF_INET6, NULL); +#endif + + /* DNAME, pointing to NXDOMAIN */ + test_rr_lookup(bus, ".ireallyhpoethisdoesnexist.xn--kprw13d.", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); + test_rr_lookup(bus, ".ireallyhpoethisdoesnexist.xn--kprw13d.", DNS_TYPE_RP, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); + + return 0; +} diff --git a/src/resolve/test-dnssec.c b/src/resolve/test-dnssec.c index 6104d8b4c0..45fe1997e2 100644 --- a/src/resolve/test-dnssec.c +++ b/src/resolve/test-dnssec.c @@ -107,10 +107,10 @@ static void test_dnssec_verify_rrset2(void) { assert_se(dnskey->dnskey.key); log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); - log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey)); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); assert_se(dnssec_key_match_rrsig(nsec->key, rrsig) > 0); - assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey) > 0); + assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0); answer = dns_answer_new(1); assert_se(answer); @@ -186,10 +186,10 @@ static void test_dnssec_verify_rrset(void) { assert_se(dnskey->dnskey.key); log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); - log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey)); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); assert_se(dnssec_key_match_rrsig(a->key, rrsig) > 0); - assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey) > 0); + assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0); answer = dns_answer_new(1); assert_se(answer); @@ -268,10 +268,10 @@ static void test_dnssec_verify_dns_key(void) { assert_se(dnskey->dnskey.key); log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); - log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey)); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); - assert_se(dnssec_verify_dnskey(dnskey, ds1) > 0); - assert_se(dnssec_verify_dnskey(dnskey, ds2) > 0); + assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds1, false) > 0); + assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds2, false) > 0); } static void test_dnssec_canonicalize_one(const char *original, const char *canonical, int r) { diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c index bc12f89f02..8de1445a96 100644 --- a/src/shared/ask-password-api.c +++ b/src/shared/ask-password-api.c @@ -71,7 +71,7 @@ static int lookup_key(const char *keyname, key_serial_t *ret) { serial = request_key("user", keyname, NULL, 0); if (serial == -1) - return -errno; + return negative_errno(); *ret = serial; return 0; diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c index 5c6dc34700..b9a8ee4074 100644 --- a/src/shared/bus-util.c +++ b/src/shared/bus-util.c @@ -2041,13 +2041,21 @@ static const struct { { "start-limit", "start of the service was attempted too often" } }; -static void log_job_error_with_service_result(const char* service, const char *result) { - _cleanup_free_ char *service_shell_quoted = NULL; +static void log_job_error_with_service_result(const char* service, const char *result, const char *extra_args) { + _cleanup_free_ char *service_shell_quoted = NULL, *systemctl_extra_args = NULL; assert(service); service_shell_quoted = shell_maybe_quote(service); + systemctl_extra_args = strjoin("systemctl ", extra_args, " ", NULL); + if (!systemctl_extra_args) { + log_oom(); + return; + } + + systemctl_extra_args = strstrip(systemctl_extra_args); + if (!isempty(result)) { unsigned i; @@ -2056,27 +2064,30 @@ static void log_job_error_with_service_result(const char* service, const char *r break; if (i < ELEMENTSOF(explanations)) { - log_error("Job for %s failed because %s. See \"systemctl status %s\" and \"journalctl -xe\" for details.\n", + log_error("Job for %s failed because %s. See \"%s status %s\" and \"journalctl -xe\" for details.\n", service, explanations[i].explanation, + systemctl_extra_args, strna(service_shell_quoted)); goto finish; } } - log_error("Job for %s failed. See \"systemctl status %s\" and \"journalctl -xe\" for details.\n", + log_error("Job for %s failed. See \"%s status %s\" and \"journalctl -xe\" for details.\n", service, + systemctl_extra_args, strna(service_shell_quoted)); finish: /* For some results maybe additional explanation is required */ if (streq_ptr(result, "start-limit")) - log_info("To force a start use \"systemctl reset-failed %1$s\" followed by \"systemctl start %1$s\" again.", + log_info("To force a start use \"%1$s reset-failed %2$s\" followed by \"%1$s start %2$s\" again.", + systemctl_extra_args, strna(service_shell_quoted)); } -static int check_wait_response(BusWaitForJobs *d, bool quiet) { +static int check_wait_response(BusWaitForJobs *d, bool quiet, const char *extra_args) { int r = 0; assert(d->result); @@ -2103,7 +2114,7 @@ static int check_wait_response(BusWaitForJobs *d, bool quiet) { if (q < 0) log_debug_errno(q, "Failed to get Result property of service %s: %m", d->name); - log_job_error_with_service_result(d->name, result); + log_job_error_with_service_result(d->name, result, extra_args); } else log_error("Job failed. See \"journalctl -xe\" for details."); } @@ -2127,7 +2138,7 @@ static int check_wait_response(BusWaitForJobs *d, bool quiet) { return r; } -int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet) { +int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char *extra_args) { int r = 0; assert(d); @@ -2140,7 +2151,7 @@ int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet) { return log_error_errno(q, "Failed to wait for response: %m"); if (d->result) { - q = check_wait_response(d, quiet); + q = check_wait_response(d, quiet, extra_args); /* Return the first error as it is most likely to be * meaningful. */ if (q < 0 && r == 0) @@ -2175,7 +2186,7 @@ int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet) { if (r < 0) return log_oom(); - return bus_wait_for_jobs(d, quiet); + return bus_wait_for_jobs(d, quiet, NULL); } int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, UnitFileChange **changes, unsigned *n_changes) { diff --git a/src/shared/bus-util.h b/src/shared/bus-util.h index a5e3b6a0b5..18fc827754 100644 --- a/src/shared/bus-util.h +++ b/src/shared/bus-util.h @@ -182,7 +182,7 @@ typedef struct BusWaitForJobs BusWaitForJobs; int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret); void bus_wait_for_jobs_free(BusWaitForJobs *d); int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path); -int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet); +int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char *extra_args); int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet); DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForJobs*, bus_wait_for_jobs_free); diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index 68404ca9e5..3ad409fc29 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -263,7 +263,6 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) { *(q++) = '0' + (char) ((uint8_t) *p % 10); sz -= 4; - } p++; @@ -414,7 +413,6 @@ int dns_name_concat(const char *a, const char *b, char **_ret) { for (;;) { char label[DNS_LABEL_MAX]; - int k; r = dns_label_unescape(&p, label, sizeof(label)); if (r < 0) @@ -433,12 +431,6 @@ int dns_name_concat(const char *a, const char *b, char **_ret) { break; } - k = dns_label_undo_idna(label, r, label, sizeof(label)); - if (k < 0) - return k; - if (k > 0) - r = k; - if (_ret) { if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) return -ENOMEM; @@ -486,27 +478,18 @@ void dns_name_hash_func(const void *s, struct siphash *state) { assert(p); - while (*p) { + for (;;) { char label[DNS_LABEL_MAX+1]; - int k; r = dns_label_unescape(&p, label, sizeof(label)); if (r < 0) break; - - k = dns_label_undo_idna(label, r, label, sizeof(label)); - if (k < 0) - break; - if (k > 0) - r = k; - if (r == 0) break; - label[r] = 0; - ascii_strlower(label); - - string_hash_func(label, state); + ascii_strlower_n(label, r); + siphash24_compress(label, r, state); + siphash24_compress_byte(0, state); /* make sure foobar and foo.bar result in different hashes */ } /* enforce that all names are terminated by the empty label */ @@ -515,7 +498,7 @@ void dns_name_hash_func(const void *s, struct siphash *state) { int dns_name_compare_func(const void *a, const void *b) { const char *x, *y; - int r, q, k, w; + int r, q; assert(a); assert(b); @@ -524,7 +507,7 @@ int dns_name_compare_func(const void *a, const void *b) { y = (const char *) b + strlen(b); for (;;) { - char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; + char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; if (x == NULL && y == NULL) return 0; @@ -534,17 +517,7 @@ int dns_name_compare_func(const void *a, const void *b) { if (r < 0 || q < 0) return r - q; - k = dns_label_undo_idna(la, r, la, sizeof(la)); - w = dns_label_undo_idna(lb, q, lb, sizeof(lb)); - if (k < 0 || w < 0) - return k - w; - if (k > 0) - r = k; - if (w > 0) - q = w; - - la[r] = lb[q] = 0; - r = strcasecmp(la, lb); + r = ascii_strcasecmp_nn(la, r, lb, q); if (r != 0) return r; } @@ -556,53 +529,35 @@ const struct hash_ops dns_name_hash_ops = { }; int dns_name_equal(const char *x, const char *y) { - int r, q, k, w; + int r, q; assert(x); assert(y); for (;;) { - char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; - - if (*x == 0 && *y == 0) - return true; + char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; r = dns_label_unescape(&x, la, sizeof(la)); if (r < 0) return r; - if (r > 0) { - k = dns_label_undo_idna(la, r, la, sizeof(la)); - if (k < 0) - return k; - if (k > 0) - r = k; - } q = dns_label_unescape(&y, lb, sizeof(lb)); if (q < 0) return q; - if (q > 0) { - w = dns_label_undo_idna(lb, q, lb, sizeof(lb)); - if (w < 0) - return w; - if (w > 0) - q = w; - } - /* If one name had fewer labels than the other, this - * will show up as empty label here, which the - * strcasecmp() below will properly consider different - * from a non-empty label. */ + if (r != q) + return false; + if (r == 0) + return true; - la[r] = lb[q] = 0; - if (strcasecmp(la, lb) != 0) + if (ascii_strcasecmp_n(la, lb, r) != 0) return false; } } int dns_name_endswith(const char *name, const char *suffix) { const char *n, *s, *saved_n = NULL; - int r, q, k, w; + int r, q; assert(name); assert(suffix); @@ -611,18 +566,11 @@ int dns_name_endswith(const char *name, const char *suffix) { s = suffix; for (;;) { - char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1]; + char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; r = dns_label_unescape(&n, ln, sizeof(ln)); if (r < 0) return r; - if (r > 0) { - k = dns_label_undo_idna(ln, r, ln, sizeof(ln)); - if (k < 0) - return k; - if (k > 0) - r = k; - } if (!saved_n) saved_n = n; @@ -630,22 +578,13 @@ int dns_name_endswith(const char *name, const char *suffix) { q = dns_label_unescape(&s, ls, sizeof(ls)); if (q < 0) return q; - if (q > 0) { - w = dns_label_undo_idna(ls, q, ls, sizeof(ls)); - if (w < 0) - return w; - if (w > 0) - q = w; - } if (r == 0 && q == 0) return true; if (r == 0 && saved_n == n) return false; - ln[r] = ls[q] = 0; - - if (r != q || strcasecmp(ln, ls)) { + if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) { /* Not the same, let's jump back, and try with the next label again */ s = suffix; @@ -655,9 +594,39 @@ int dns_name_endswith(const char *name, const char *suffix) { } } +int dns_name_startswith(const char *name, const char *prefix) { + const char *n, *p; + int r, q; + + assert(name); + assert(prefix); + + n = name; + p = prefix; + + for (;;) { + char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX]; + + r = dns_label_unescape(&p, lp, sizeof(lp)); + if (r < 0) + return r; + if (r == 0) + return true; + + q = dns_label_unescape(&n, ln, sizeof(ln)); + if (q < 0) + return q; + + if (r != q) + return false; + if (ascii_strcasecmp_n(ln, lp, r) != 0) + return false; + } +} + int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret) { const char *n, *s, *saved_before = NULL, *saved_after = NULL, *prefix; - int r, q, k, w; + int r, q; assert(name); assert(old_suffix); @@ -668,7 +637,7 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char s = old_suffix; for (;;) { - char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1]; + char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; if (!saved_before) saved_before = n; @@ -676,13 +645,6 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char r = dns_label_unescape(&n, ln, sizeof(ln)); if (r < 0) return r; - if (r > 0) { - k = dns_label_undo_idna(ln, r, ln, sizeof(ln)); - if (k < 0) - return k; - if (k > 0) - r = k; - } if (!saved_after) saved_after = n; @@ -690,13 +652,6 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char q = dns_label_unescape(&s, ls, sizeof(ls)); if (q < 0) return q; - if (q > 0) { - w = dns_label_undo_idna(ls, q, ls, sizeof(ls)); - if (w < 0) - return w; - if (w > 0) - q = w; - } if (r == 0 && q == 0) break; @@ -705,9 +660,7 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char return 0; } - ln[r] = ls[q] = 0; - - if (r != q || strcasecmp(ln, ls)) { + if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) { /* Not the same, let's jump back, and try with the next label again */ s = old_suffix; @@ -875,12 +828,11 @@ bool dns_name_is_root(const char *name) { } bool dns_name_is_single_label(const char *name) { - char label[DNS_LABEL_MAX+1]; int r; assert(name); - r = dns_label_unescape(&name, label, sizeof(label)); + r = dns_name_parent(&name); if (r <= 0) return false; @@ -913,19 +865,11 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, boo if (r < 0) return r; - if (canonical) { - size_t i; - - /* Optionally, output the name in DNSSEC - * canonical format, as described in RFC 4034, - * section 6.2. Or in other words: in - * lower-case. */ - - for (i = 0; i < (size_t) r; i++) { - if (out[i] >= 'A' && out[i] <= 'Z') - out[i] = out[i] - 'A' + 'a'; - } - } + /* Optionally, output the name in DNSSEC canonical + * format, as described in RFC 4034, section 6.2. Or + * in other words: in lower-case. */ + if (canonical) + ascii_strlower_n((char*) out, (size_t) r); /* Fill label length, move forward */ *label_length = r; @@ -1109,17 +1053,15 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do if (x >= 3 && srv_type_label_is_valid(c, cn)) { if (dns_service_name_label_is_valid(a, an)) { - /* OK, got <name> . <type> . <type2> . <domain> */ name = strndup(a, an); if (!name) return -ENOMEM; - type = new(char, bn+1+cn+1); + type = strjoin(b, ".", c, NULL); if (!type) return -ENOMEM; - strcpy(stpcpy(stpcpy(type, b), "."), c); d = p; goto finish; @@ -1131,10 +1073,9 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do name = NULL; - type = new(char, an+1+bn+1); + type = strjoin(a, ".", b, NULL); if (!type) return -ENOMEM; - strcpy(stpcpy(stpcpy(type, a), "."), b); d = q; goto finish; @@ -1168,22 +1109,20 @@ finish: return 0; } -int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) { - const char* labels[DNS_N_LABELS_MAX+1]; - unsigned n = 0; +static int dns_name_build_suffix_table(const char *name, const char*table[]) { const char *p; + unsigned n = 0; int r; assert(name); - assert(ret); + assert(table); p = name; for (;;) { if (n > DNS_N_LABELS_MAX) return -EINVAL; - labels[n] = p; - + table[n] = p; r = dns_name_parent(&p); if (r < 0) return r; @@ -1193,13 +1132,47 @@ int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) { n++; } - if (n < n_labels) + return (int) n; +} + +int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) { + const char* labels[DNS_N_LABELS_MAX+1]; + int n; + + assert(name); + assert(ret); + + n = dns_name_build_suffix_table(name, labels); + if (n < 0) + return n; + + if ((unsigned) n < n_labels) return -EINVAL; *ret = labels[n - n_labels]; return (int) (n - n_labels); } +int dns_name_skip(const char *a, unsigned n_labels, const char **ret) { + int r; + + assert(a); + assert(ret); + + for (; n_labels > 0; n_labels --) { + r = dns_name_parent(&a); + if (r < 0) + return r; + if (r == 0) { + *ret = ""; + return 0; + } + } + + *ret = a; + return 1; +} + int dns_name_count_labels(const char *name) { unsigned n = 0; const char *p; @@ -1230,14 +1203,107 @@ int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) { assert(a); assert(b); - while (n_labels > 0) { + r = dns_name_skip(a, n_labels, &a); + if (r <= 0) + return r; - r = dns_name_parent(&a); - if (r <= 0) + return dns_name_equal(a, b); +} + +int dns_name_common_suffix(const char *a, const char *b, const char **ret) { + const char *a_labels[DNS_N_LABELS_MAX+1], *b_labels[DNS_N_LABELS_MAX+1]; + int n = 0, m = 0, k = 0, r, q; + + assert(a); + assert(b); + assert(ret); + + /* Determines the common suffix of domain names a and b */ + + n = dns_name_build_suffix_table(a, a_labels); + if (n < 0) + return n; + + m = dns_name_build_suffix_table(b, b_labels); + if (m < 0) + return m; + + for (;;) { + char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + const char *x, *y; + + if (k >= n || k >= m) { + *ret = a_labels[n - k]; + return 0; + } + + x = a_labels[n - 1 - k]; + r = dns_label_unescape(&x, la, sizeof(la)); + if (r < 0) + return r; + + y = b_labels[m - 1 - k]; + q = dns_label_unescape(&y, lb, sizeof(lb)); + if (q < 0) + return q; + + if (r != q || ascii_strcasecmp_n(la, lb, r) != 0) { + *ret = a_labels[n - k]; + return 0; + } + + k++; + } +} + +int dns_name_apply_idna(const char *name, char **ret) { + _cleanup_free_ char *buf = NULL; + size_t n = 0, allocated = 0; + bool first = true; + int r, q; + + assert(name); + assert(ret); + + for (;;) { + char label[DNS_LABEL_MAX]; + + r = dns_label_unescape(&name, label, sizeof(label)); + if (r < 0) + return r; + if (r == 0) + break; + + q = dns_label_apply_idna(label, r, label, sizeof(label)); + if (q < 0) + return q; + if (q > 0) + r = q; + + if (!GREEDY_REALLOC(buf, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + r = dns_label_escape(label, r, buf + n + !first, DNS_LABEL_ESCAPED_MAX); + if (r < 0) return r; - n_labels --; + if (first) + first = false; + else + buf[n++] = '.'; + + n +=r; } - return dns_name_equal(a, b); + if (n > DNS_HOSTNAME_MAX) + return -EINVAL; + + if (!GREEDY_REALLOC(buf, allocated, n + 1)) + return -ENOMEM; + + buf[n] = 0; + *ret = buf; + buf = NULL; + + return (int) n; } diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h index dd8ae3ac98..40c9ee5f27 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -83,6 +83,7 @@ extern const struct hash_ops dns_name_hash_ops; int dns_name_between(const char *a, const char *b, const char *c); int dns_name_equal(const char *x, const char *y); int dns_name_endswith(const char *name, const char *suffix); +int dns_name_startswith(const char *name, const char *prefix); int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret); @@ -103,4 +104,9 @@ int dns_service_split(const char *joined, char **name, char **type, char **domai int dns_name_suffix(const char *name, unsigned n_labels, const char **ret); int dns_name_count_labels(const char *name); +int dns_name_skip(const char *a, unsigned n_labels, const char **ret); int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b); + +int dns_name_common_suffix(const char *a, const char *b, const char **ret); + +int dns_name_apply_idna(const char *name, char **ret); diff --git a/src/shared/dropin.c b/src/shared/dropin.c index 692e8b8338..073a8396c5 100644 --- a/src/shared/dropin.c +++ b/src/shared/dropin.c @@ -156,7 +156,7 @@ static int iterate_dir( errno = 0; de = readdir(d); - if (!de && errno != 0) + if (!de && errno > 0) return log_error_errno(errno, "Failed to read directory %s: %m", path); if (!de) diff --git a/src/shared/generator.c b/src/shared/generator.c index 37de3f7cb1..76808cbdd5 100644 --- a/src/shared/generator.c +++ b/src/shared/generator.c @@ -190,7 +190,6 @@ int generator_write_timeouts( return write_drop_in_format(dir, unit, 50, "device-timeout", "# Automatically generated by %s\n\n" - "[Unit]\nJobTimeoutSec=" USEC_FMT, - program_invocation_short_name, - u / USEC_PER_SEC); + "[Unit]\nJobTimeoutSec=%s", + program_invocation_short_name, timeout); } diff --git a/src/shared/resolve-util.c b/src/shared/resolve-util.c new file mode 100644 index 0000000000..bf6fc26841 --- /dev/null +++ b/src/shared/resolve-util.c @@ -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); diff --git a/src/shared/resolve-util.h b/src/shared/resolve-util.h new file mode 100644 index 0000000000..fd93a13f73 --- /dev/null +++ b/src/shared/resolve-util.h @@ -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_; diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index b1bbbdaadd..bf0739e5fa 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -35,6 +35,7 @@ #include "mkdir.h" #include "path-util.h" #include "rm-rf.h" +#include "stdio-util.h" #include "string-util.h" #include "switch-root.h" #include "user-util.h" @@ -77,7 +78,7 @@ int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, char new_mount[PATH_MAX]; struct stat sb; - snprintf(new_mount, sizeof(new_mount), "%s%s", new_root, i); + xsprintf(new_mount, "%s%s", new_root, i); mkdir_p_label(new_mount, 0755); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 1448d974bd..d021d30f78 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -247,7 +247,7 @@ static OutputFlags get_output_flags(void) { arg_all * OUTPUT_SHOW_ALL | arg_full * OUTPUT_FULL_WIDTH | (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | - on_tty() * OUTPUT_COLOR | + colors_enabled() * OUTPUT_COLOR | !arg_quiet * OUTPUT_WARN_CUTOFF; } @@ -1979,7 +1979,7 @@ static void dump_unit_file_changes(const UnitFileChange *changes, unsigned n_cha for (i = 0; i < n_changes; i++) { if (changes[i].type == UNIT_FILE_SYMLINK) - log_info("Created symlink from %s to %s.", changes[i].path, changes[i].source); + log_info("Created symlink %s, pointing to %s.", changes[i].path, changes[i].source); else log_info("Removed symlink %s.", changes[i].path); } @@ -2631,7 +2631,13 @@ static int start_unit_one( verb = method_to_verb(method); - return log_error_errno(r, "Failed to %s %s: %s", verb, name, bus_error_message(error, r)); + log_error("Failed to %s %s: %s", verb, name, bus_error_message(error, r)); + + if (!sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) && + !sd_bus_error_has_name(error, BUS_ERROR_UNIT_MASKED)) + log_error("See system logs and 'systemctl status %s' for details.", name); + + return r; } r = sd_bus_message_read(reply, "o", &path); @@ -2794,7 +2800,7 @@ static int start_unit(int argc, char *argv[], void *userdata) { if (!arg_no_block) { int q; - q = bus_wait_for_jobs(w, arg_quiet); + q = bus_wait_for_jobs(w, arg_quiet, arg_scope != UNIT_FILE_SYSTEM ? "--user" : NULL); if (q < 0) return q; diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h index bc658f62b0..1183df6105 100644 --- a/src/systemd/sd-messages.h +++ b/src/systemd/sd-messages.h @@ -87,6 +87,7 @@ _SD_BEGIN_DECLARATIONS; #define SD_MESSAGE_BOOTCHART SD_ID128_MAKE(9f,26,aa,56,2c,f4,40,c2,b1,6c,77,3d,04,79,b5,18) #define SD_MESSAGE_DNSSEC_FAILURE SD_ID128_MAKE(16,75,d7,f1,72,17,40,98,b1,10,8b,f8,c7,dc,8f,5d) +#define SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED SD_ID128_MAKE(4d,44,08,cf,d0,d1,44,85,91,84,d1,e6,5d,7c,8a,65) _SD_END_DECLARATIONS; diff --git a/src/systemd/sd-network.h b/src/systemd/sd-network.h index 79b4bf9ea3..653c61a162 100644 --- a/src/systemd/sd-network.h +++ b/src/systemd/sd-network.h @@ -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. */ diff --git a/src/systemd/sd-resolve.h b/src/systemd/sd-resolve.h index 241b51084d..eb4548a2dc 100644 --- a/src/systemd/sd-resolve.h +++ b/src/systemd/sd-resolve.h @@ -44,9 +44,9 @@ typedef int (*sd_resolve_getaddrinfo_handler_t)(sd_resolve_query *q, int ret, co typedef int (*sd_resolve_getnameinfo_handler_t)(sd_resolve_query *q, int ret, const char *host, const char *serv, void *userdata); enum { - SD_RESOLVE_GET_HOST = 1ULL, - SD_RESOLVE_GET_SERVICE = 2ULL, - SD_RESOLVE_GET_BOTH = 3ULL + SD_RESOLVE_GET_HOST = UINT64_C(1), + SD_RESOLVE_GET_SERVICE = UINT64_C(2), + SD_RESOLVE_GET_BOTH = UINT64_C(3), }; int sd_resolve_default(sd_resolve **ret); diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 675f94906b..a7e8187f4f 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -280,7 +280,7 @@ static int putgrent_with_members(const struct group *gr, FILE *group) { errno = 0; if (putgrent(&t, group) != 0) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; return 1; } @@ -288,7 +288,7 @@ static int putgrent_with_members(const struct group *gr, FILE *group) { errno = 0; if (putgrent(gr, group) != 0) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; return 0; } @@ -330,7 +330,7 @@ static int putsgent_with_members(const struct sgrp *sg, FILE *gshadow) { errno = 0; if (putsgent(&t, gshadow) != 0) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; return 1; } @@ -338,7 +338,7 @@ static int putsgent_with_members(const struct sgrp *sg, FILE *gshadow) { errno = 0; if (putsgent(sg, gshadow) != 0) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; return 0; } diff --git a/src/test/test-capability.c b/src/test/test-capability.c index fc8d3ffe0d..629bb63c81 100644 --- a/src/test/test-capability.c +++ b/src/test/test-capability.c @@ -20,6 +20,7 @@ #include <netinet/in.h> #include <pwd.h> #include <sys/capability.h> +#include <sys/prctl.h> #include <sys/socket.h> #include <sys/wait.h> #include <unistd.h> @@ -66,8 +67,9 @@ static void show_capabilities(void) { cap_free(text); } -static int setup_tests(void) { +static int setup_tests(bool *run_ambient) { struct passwd *nobody; + int r; nobody = getpwnam("nobody"); if (!nobody) { @@ -77,6 +79,18 @@ static int setup_tests(void) { test_uid = nobody->pw_uid; test_gid = nobody->pw_gid; + *run_ambient = false; + + r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0); + + /* There's support for PR_CAP_AMBIENT if the prctl() call + * succeeded or error code was something else than EINVAL. The + * EINVAL check should be good enough to rule out false + * positives. */ + + if (r >= 0 || errno != EINVAL) + *run_ambient = true; + return 0; } @@ -140,8 +154,53 @@ static void test_have_effective_cap(void) { assert_se(!have_effective_cap(CAP_CHOWN)); } +static void test_update_inherited_set(void) { + cap_t caps; + uint64_t set = 0; + cap_flag_value_t fv; + + caps = cap_get_proc(); + assert_se(caps); + assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); + assert(fv == CAP_CLEAR); + + set = (UINT64_C(1) << CAP_CHOWN); + + assert_se(!capability_update_inherited_set(caps, set)); + assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); + assert(fv == CAP_SET); + + cap_free(caps); +} + +static void test_set_ambient_caps(void) { + cap_t caps; + uint64_t set = 0; + cap_flag_value_t fv; + + caps = cap_get_proc(); + assert_se(caps); + assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); + assert(fv == CAP_CLEAR); + cap_free(caps); + + assert_se(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0) == 0); + + set = (UINT64_C(1) << CAP_CHOWN); + + assert_se(!capability_ambient_set_apply(set, true)); + + caps = cap_get_proc(); + assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); + assert(fv == CAP_SET); + cap_free(caps); + + assert_se(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0) == 1); +} + int main(int argc, char *argv[]) { int r; + bool run_ambient; log_parse_environment(); log_open(); @@ -149,14 +208,19 @@ int main(int argc, char *argv[]) { if (getuid() != 0) return EXIT_TEST_SKIP; - r = setup_tests(); + r = setup_tests(&run_ambient); if (r < 0) return -r; show_capabilities(); test_drop_privileges(); + test_update_inherited_set(); + fork_test(test_have_effective_cap); + if (run_ambient) + fork_test(test_set_ambient_caps); + return 0; } diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index 6c3c49908f..3b260ee75d 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -276,6 +276,25 @@ static void test_dns_name_endswith(void) { test_dns_name_endswith_one("x.y\001.z", "waldo", -EINVAL); } +static void test_dns_name_startswith_one(const char *a, const char *b, int ret) { + assert_se(dns_name_startswith(a, b) == ret); +} + +static void test_dns_name_startswith(void) { + test_dns_name_startswith_one("", "", true); + test_dns_name_startswith_one("", "xxx", false); + test_dns_name_startswith_one("xxx", "", true); + test_dns_name_startswith_one("x", "x", true); + test_dns_name_startswith_one("x", "y", false); + test_dns_name_startswith_one("x.y", "x.y", true); + test_dns_name_startswith_one("x.y", "y.x", false); + test_dns_name_startswith_one("x.y", "x", true); + test_dns_name_startswith_one("x.y", "X", true); + test_dns_name_startswith_one("x.y", "y", false); + test_dns_name_startswith_one("x.y", "", true); + test_dns_name_startswith_one("x.y", "X", true); +} + static void test_dns_name_is_root(void) { assert_se(dns_name_is_root("")); assert_se(dns_name_is_root(".")); @@ -559,6 +578,46 @@ static void test_dns_name_compare_func(void) { assert_se(dns_name_compare_func("de.", "heise.de") != 0); } +static void test_dns_name_common_suffix_one(const char *a, const char *b, const char *result) { + const char *c; + + assert_se(dns_name_common_suffix(a, b, &c) >= 0); + assert_se(streq(c, result)); +} + +static void test_dns_name_common_suffix(void) { + test_dns_name_common_suffix_one("", "", ""); + test_dns_name_common_suffix_one("foo", "", ""); + test_dns_name_common_suffix_one("", "foo", ""); + test_dns_name_common_suffix_one("foo", "bar", ""); + test_dns_name_common_suffix_one("bar", "foo", ""); + test_dns_name_common_suffix_one("foo", "foo", "foo"); + test_dns_name_common_suffix_one("quux.foo", "foo", "foo"); + test_dns_name_common_suffix_one("foo", "quux.foo", "foo"); + test_dns_name_common_suffix_one("this.is.a.short.sentence", "this.is.another.short.sentence", "short.sentence"); + test_dns_name_common_suffix_one("FOO.BAR", "tEST.bAR", "BAR"); +} + +static void test_dns_name_apply_idna_one(const char *s, const char *result) { +#ifdef HAVE_LIBIDN + _cleanup_free_ char *buf = NULL; + assert_se(dns_name_apply_idna(s, &buf) >= 0); + assert_se(dns_name_equal(buf, result) > 0); +#endif +} + +static void test_dns_name_apply_idna(void) { + test_dns_name_apply_idna_one("", ""); + test_dns_name_apply_idna_one("foo", "foo"); + test_dns_name_apply_idna_one("foo.", "foo"); + test_dns_name_apply_idna_one("foo.bar", "foo.bar"); + test_dns_name_apply_idna_one("foo.bar.", "foo.bar"); + test_dns_name_apply_idna_one("föö", "xn--f-1gaa"); + test_dns_name_apply_idna_one("föö.", "xn--f-1gaa"); + test_dns_name_apply_idna_one("föö.bär", "xn--f-1gaa.xn--br-via"); + test_dns_name_apply_idna_one("föö.bär.", "xn--f-1gaa.xn--br-via"); +} + int main(int argc, char *argv[]) { test_dns_label_unescape(); @@ -567,6 +626,7 @@ int main(int argc, char *argv[]) { test_dns_name_normalize(); test_dns_name_equal(); test_dns_name_endswith(); + test_dns_name_startswith(); test_dns_name_between(); test_dns_name_is_root(); test_dns_name_is_single_label(); @@ -583,6 +643,8 @@ int main(int argc, char *argv[]) { test_dns_name_count_labels(); test_dns_name_equal_skip(); test_dns_name_compare_func(); + test_dns_name_common_suffix(); + test_dns_name_apply_idna(); return 0; } diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 753afadb0a..92857cb5e2 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -20,6 +20,7 @@ #include <grp.h> #include <pwd.h> #include <stdio.h> +#include <sys/prctl.h> #include <sys/types.h> #include "fileio.h" @@ -224,6 +225,20 @@ static void test_exec_capabilityboundingset(Manager *m) { test(m, "exec-capabilityboundingset-invert.service", 0, CLD_EXITED); } +static void test_exec_capabilityambientset(Manager *m) { + int r; + + /* Check if the kernel has support for ambient capabilities. Run + * the tests only if that's the case. Clearing all ambient + * capabilities is fine, since we are expecting them to be unset + * in the first place for the tests. */ + r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0); + if (r >= 0 || errno != EINVAL) { + test(m, "exec-capabilityambientset.service", 0, CLD_EXITED); + test(m, "exec-capabilityambientset-merge.service", 0, CLD_EXITED); + } +} + static void test_exec_privatenetwork(Manager *m) { int r; @@ -266,6 +281,7 @@ int main(int argc, char *argv[]) { test_exec_umask, test_exec_runtimedirectory, test_exec_capabilityboundingset, + test_exec_capabilityambientset, test_exec_oomscoreadjust, test_exec_ioschedulingclass, NULL, diff --git a/src/test/test-libudev.c b/src/test/test-libudev.c index 350eaf734d..94d852b3b0 100644 --- a/src/test/test-libudev.c +++ b/src/test/test-libudev.c @@ -25,6 +25,7 @@ #include "libudev.h" +#include "stdio-util.h" #include "string-util.h" #include "udev-util.h" #include "util.h" @@ -460,7 +461,7 @@ int main(int argc, char *argv[]) { /* add sys path if needed */ if (!startswith(syspath, "/sys")) { - snprintf(path, sizeof(path), "/sys/%s", syspath); + xsprintf(path, "/sys/%s", syspath); syspath = path; } diff --git a/src/test/test-rlimit-util.c b/src/test/test-rlimit-util.c index 00d3ecc0de..24bfe7a60e 100644 --- a/src/test/test-rlimit-util.c +++ b/src/test/test-rlimit-util.c @@ -37,7 +37,7 @@ int main(int argc, char *argv[]) { assert_se(getrlimit(RLIMIT_NOFILE, &old) == 0); new.rlim_cur = MIN(5U, old.rlim_max); - new.rlim_max = MIN(10U, old.rlim_max); + new.rlim_max = old.rlim_max; assert_se(setrlimit(RLIMIT_NOFILE, &new) >= 0); assert_se(rlimit_from_string("LimitNOFILE") == RLIMIT_NOFILE); @@ -53,7 +53,7 @@ int main(int argc, char *argv[]) { assert_se(old.rlim_max == new.rlim_max); assert_se(getrlimit(RLIMIT_NOFILE, &old) == 0); - high = RLIMIT_MAKE_CONST(old.rlim_max + 1); + high = RLIMIT_MAKE_CONST(old.rlim_max == RLIM_INFINITY ? old.rlim_max : old.rlim_max + 1); assert_se(setrlimit_closest(RLIMIT_NOFILE, &high) == 0); assert_se(getrlimit(RLIMIT_NOFILE, &new) == 0); assert_se(new.rlim_max == old.rlim_max); diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index 25444c794a..12889ce873 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -55,7 +55,53 @@ static void test_string_erase(void) { assert_se(streq(string_erase(x), "xxxxxxxxx")); } +static void test_ascii_strcasecmp_n(void) { + + assert_se(ascii_strcasecmp_n("", "", 0) == 0); + assert_se(ascii_strcasecmp_n("", "", 1) == 0); + assert_se(ascii_strcasecmp_n("", "a", 1) < 0); + assert_se(ascii_strcasecmp_n("", "a", 2) < 0); + assert_se(ascii_strcasecmp_n("a", "", 1) > 0); + assert_se(ascii_strcasecmp_n("a", "", 2) > 0); + assert_se(ascii_strcasecmp_n("a", "a", 1) == 0); + assert_se(ascii_strcasecmp_n("a", "a", 2) == 0); + assert_se(ascii_strcasecmp_n("a", "b", 1) < 0); + assert_se(ascii_strcasecmp_n("a", "b", 2) < 0); + assert_se(ascii_strcasecmp_n("b", "a", 1) > 0); + assert_se(ascii_strcasecmp_n("b", "a", 2) > 0); + assert_se(ascii_strcasecmp_n("xxxxyxxxx", "xxxxYxxxx", 9) == 0); + assert_se(ascii_strcasecmp_n("xxxxxxxxx", "xxxxyxxxx", 9) < 0); + assert_se(ascii_strcasecmp_n("xxxxXxxxx", "xxxxyxxxx", 9) < 0); + assert_se(ascii_strcasecmp_n("xxxxxxxxx", "xxxxYxxxx", 9) < 0); + assert_se(ascii_strcasecmp_n("xxxxXxxxx", "xxxxYxxxx", 9) < 0); + + assert_se(ascii_strcasecmp_n("xxxxYxxxx", "xxxxYxxxx", 9) == 0); + assert_se(ascii_strcasecmp_n("xxxxyxxxx", "xxxxxxxxx", 9) > 0); + assert_se(ascii_strcasecmp_n("xxxxyxxxx", "xxxxXxxxx", 9) > 0); + assert_se(ascii_strcasecmp_n("xxxxYxxxx", "xxxxxxxxx", 9) > 0); + assert_se(ascii_strcasecmp_n("xxxxYxxxx", "xxxxXxxxx", 9) > 0); +} + +static void test_ascii_strcasecmp_nn(void) { + assert_se(ascii_strcasecmp_nn("", 0, "", 0) == 0); + assert_se(ascii_strcasecmp_nn("", 0, "", 1) < 0); + assert_se(ascii_strcasecmp_nn("", 1, "", 0) > 0); + assert_se(ascii_strcasecmp_nn("", 1, "", 1) == 0); + + assert_se(ascii_strcasecmp_nn("aaaa", 4, "aaAa", 4) == 0); + assert_se(ascii_strcasecmp_nn("aaa", 3, "aaAa", 4) < 0); + assert_se(ascii_strcasecmp_nn("aaa", 4, "aaAa", 4) < 0); + assert_se(ascii_strcasecmp_nn("aaaa", 4, "aaA", 3) > 0); + assert_se(ascii_strcasecmp_nn("aaaa", 4, "AAA", 4) > 0); + + assert_se(ascii_strcasecmp_nn("aaaa", 4, "bbbb", 4) < 0); + assert_se(ascii_strcasecmp_nn("aaAA", 4, "BBbb", 4) < 0); + assert_se(ascii_strcasecmp_nn("BBbb", 4, "aaaa", 4) > 0); +} + int main(int argc, char *argv[]) { test_string_erase(); + test_ascii_strcasecmp_n(); + test_ascii_strcasecmp_nn(); return 0; } diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c index 0b3630f77c..cd1e4e4698 100644 --- a/src/test/test-unit-file.c +++ b/src/test/test-unit-file.c @@ -28,6 +28,7 @@ #include <unistd.h> #include "alloc-util.h" +#include "capability-util.h" #include "fd-util.h" #include "fileio.h" #include "hashmap.h" @@ -625,8 +626,8 @@ static uint64_t make_cap(int cap) { return ((uint64_t) 1ULL << (uint64_t) cap); } -static void test_config_parse_bounding_set(void) { - /* int config_parse_bounding_set( +static void test_config_parse_capability_set(void) { + /* int config_parse_capability_set( const char *unit, const char *filename, unsigned line, @@ -638,38 +639,38 @@ static void test_config_parse_bounding_set(void) { void *data, void *userdata) */ int r; - uint64_t capability_bounding_set_drop = 0; + uint64_t capability_bounding_set = 0; - r = config_parse_bounding_set(NULL, "fake", 1, "section", 1, + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, "CapabilityBoundingSet", 0, "CAP_NET_RAW", - &capability_bounding_set_drop, NULL); + &capability_bounding_set, NULL); assert_se(r >= 0); - assert_se(capability_bounding_set_drop == ~make_cap(CAP_NET_RAW)); + assert_se(capability_bounding_set == make_cap(CAP_NET_RAW)); - r = config_parse_bounding_set(NULL, "fake", 1, "section", 1, + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, "CapabilityBoundingSet", 0, "CAP_NET_ADMIN", - &capability_bounding_set_drop, NULL); + &capability_bounding_set, NULL); assert_se(r >= 0); - assert_se(capability_bounding_set_drop == ~(make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN))); + assert_se(capability_bounding_set == (make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN))); - r = config_parse_bounding_set(NULL, "fake", 1, "section", 1, + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, "CapabilityBoundingSet", 0, "", - &capability_bounding_set_drop, NULL); + &capability_bounding_set, NULL); assert_se(r >= 0); - assert_se(capability_bounding_set_drop == ~((uint64_t) 0ULL)); + assert_se(capability_bounding_set == UINT64_C(0)); - r = config_parse_bounding_set(NULL, "fake", 1, "section", 1, + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, "CapabilityBoundingSet", 0, "~", - &capability_bounding_set_drop, NULL); + &capability_bounding_set, NULL); assert_se(r >= 0); - assert_se(capability_bounding_set_drop == (uint64_t) 0ULL); + assert_se(cap_test_all(capability_bounding_set)); - capability_bounding_set_drop = 0; - r = config_parse_bounding_set(NULL, "fake", 1, "section", 1, + capability_bounding_set = 0; + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, "CapabilityBoundingSet", 0, " 'CAP_NET_RAW' WAT_CAP??? CAP_NET_ADMIN CAP'_trailing_garbage", - &capability_bounding_set_drop, NULL); + &capability_bounding_set, NULL); assert_se(r >= 0); - assert_se(capability_bounding_set_drop == ~(make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN))); + assert_se(capability_bounding_set == (make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN))); } static void test_config_parse_rlimit(void) { @@ -829,7 +830,7 @@ int main(int argc, char *argv[]) { r = test_unit_file_get_set(); test_config_parse_exec(); - test_config_parse_bounding_set(); + test_config_parse_capability_set(); test_config_parse_rlimit(); test_config_parse_pass_environ(); test_load_env_file_1(); diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index f9a759e223..bb81ff5e3a 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -1075,7 +1075,7 @@ static int item_do_children(Item *i, const char *path, action_t action) { errno = 0; de = readdir(d); if (!de) { - if (errno != 0 && r == 0) + if (errno > 0 && r == 0) r = -errno; break; diff --git a/src/udev/collect/collect.c b/src/udev/collect/collect.c index b6c95cd452..349585b634 100644 --- a/src/udev/collect/collect.c +++ b/src/udev/collect/collect.c @@ -27,6 +27,7 @@ #include "alloc-util.h" #include "libudev-private.h" #include "macro.h" +#include "stdio-util.h" #include "string-util.h" #define BUFSIZE 16 @@ -91,7 +92,7 @@ static int prepare(char *dir, char *filename) if (r < 0 && errno != EEXIST) return -errno; - snprintf(buf, sizeof(buf), "%s/%s", dir, filename); + xsprintf(buf, "%s/%s", dir, filename); fd = open(buf,O_RDWR|O_CREAT|O_CLOEXEC, S_IRUSR|S_IWUSR); if (fd < 0) diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index 0b1ae706e7..018b4dc596 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -124,7 +124,7 @@ static int find_gpt_root(struct udev_device *dev, blkid_probe pr, bool test) { errno = 0; pl = blkid_probe_get_partitions(pr); if (!pl) - return errno ? -errno : -ENOMEM; + return errno > 0 ? -errno : -ENOMEM; nvals = blkid_partlist_numof_partitions(pl); for (i = 0; i < nvals; i++) { diff --git a/src/udev/udev-builtin-input_id.c b/src/udev/udev-builtin-input_id.c index a7aac78def..691ef5656d 100644 --- a/src/udev/udev-builtin-input_id.c +++ b/src/udev/udev-builtin-input_id.c @@ -33,6 +33,7 @@ #include <linux/input.h> #include "fd-util.h" +#include "stdio-util.h" #include "string-util.h" #include "udev.h" #include "util.h" @@ -66,8 +67,8 @@ static void extract_info(struct udev_device *dev, const char *devpath, bool test if (xabsinfo.resolution <= 0 || yabsinfo.resolution <= 0) return; - snprintf(width, sizeof(width), "%d", abs_size_mm(&xabsinfo)); - snprintf(height, sizeof(height), "%d", abs_size_mm(&yabsinfo)); + xsprintf(width, "%d", abs_size_mm(&xabsinfo)); + xsprintf(height, "%d", abs_size_mm(&yabsinfo)); udev_builtin_add_property(dev, test, "ID_INPUT_WIDTH_MM", width); udev_builtin_add_property(dev, test, "ID_INPUT_HEIGHT_MM", height); @@ -93,7 +94,7 @@ static void get_cap_mask(struct udev_device *dev, if (!v) v = ""; - snprintf(text, sizeof(text), "%s", v); + xsprintf(text, "%s", v); log_debug("%s raw kernel attribute: %s", attr, text); memzero(bitmask, bitmask_size); @@ -115,7 +116,8 @@ static void get_cap_mask(struct udev_device *dev, if (test) { /* printf pattern with the right unsigned long number of hex chars */ - snprintf(text, sizeof(text), " bit %%4u: %%0%zulX\n", 2 * sizeof(unsigned long)); + xsprintf(text, " bit %%4u: %%0%zulX\n", + 2 * sizeof(unsigned long)); log_debug("%s decoded bit map:", attr); val = bitmask_size / sizeof (unsigned long); /* skip over leading zeros */ diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c index e549fdbee9..e83b8b1c12 100644 --- a/src/udev/udev-builtin-net_id.c +++ b/src/udev/udev-builtin-net_id.c @@ -102,6 +102,7 @@ #include "fd-util.h" #include "fileio.h" +#include "stdio-util.h" #include "string-util.h" #include "udev.h" @@ -228,7 +229,7 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) { err = -ENOENT; goto out; } - snprintf(slots, sizeof(slots), "%s/slots", udev_device_get_syspath(pci)); + xsprintf(slots, "%s/slots", udev_device_get_syspath(pci)); dir = opendir(slots); if (!dir) { err = -errno; @@ -247,7 +248,7 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) { continue; if (i < 1) continue; - snprintf(str, sizeof(str), "%s/%s/address", slots, dent->d_name); + xsprintf(str, "%s/%s/address", slots, dent->d_name); if (read_one_line_file(str, &address) >= 0) { /* match slot address with device by stripping the function */ if (strneq(address, udev_device_get_sysname(names->pcidev), strlen(address))) @@ -380,7 +381,7 @@ static int names_bcma(struct udev_device *dev, struct netnames *names) { return -EINVAL; /* suppress the common core == 0 */ if (core > 0) - snprintf(names->bcma_core, sizeof(names->bcma_core), "b%u", core); + xsprintf(names->bcma_core, "b%u", core); names->type = NET_BCMA; return 0; @@ -469,9 +470,9 @@ static int ieee_oui(struct udev_device *dev, struct netnames *names, bool test) /* skip commonly misused 00:00:00 (Xerox) prefix */ if (memcmp(names->mac, "\0\0\0", 3) == 0) return -EINVAL; - snprintf(str, sizeof(str), "OUI:%02X%02X%02X%02X%02X%02X", - names->mac[0], names->mac[1], names->mac[2], - names->mac[3], names->mac[4], names->mac[5]); + xsprintf(str, "OUI:%02X%02X%02X%02X%02X%02X", names->mac[0], + names->mac[1], names->mac[2], names->mac[3], names->mac[4], + names->mac[5]); udev_builtin_hwdb_lookup(dev, NULL, str, NULL, test); return 0; } @@ -523,7 +524,7 @@ static int builtin_net_id(struct udev_device *dev, int argc, char *argv[], bool if (err >= 0 && names.mac_valid) { char str[IFNAMSIZ]; - snprintf(str, sizeof(str), "%sx%02x%02x%02x%02x%02x%02x", prefix, + xsprintf(str, "%sx%02x%02x%02x%02x%02x%02x", prefix, names.mac[0], names.mac[1], names.mac[2], names.mac[3], names.mac[4], names.mac[5]); udev_builtin_add_property(dev, test, "ID_NET_NAME_MAC", str); diff --git a/src/udev/udev-node.c b/src/udev/udev-node.c index 39ae2cc1b1..fd7936c2dc 100644 --- a/src/udev/udev-node.c +++ b/src/udev/udev-node.c @@ -31,6 +31,7 @@ #include "fs-util.h" #include "selinux-util.h" #include "smack-util.h" +#include "stdio-util.h" #include "string-util.h" #include "udev.h" @@ -348,9 +349,10 @@ void udev_node_add(struct udev_device *dev, bool apply, return; /* always add /dev/{block,char}/$major:$minor */ - snprintf(filename, sizeof(filename), "/dev/%s/%u:%u", + xsprintf(filename, "/dev/%s/%u:%u", streq(udev_device_get_subsystem(dev), "block") ? "block" : "char", - major(udev_device_get_devnum(dev)), minor(udev_device_get_devnum(dev))); + major(udev_device_get_devnum(dev)), + minor(udev_device_get_devnum(dev))); node_symlink(dev, udev_device_get_devnode(dev), filename); /* create/update symlinks, add symlinks to name index */ @@ -367,8 +369,9 @@ void udev_node_remove(struct udev_device *dev) { link_update(dev, udev_list_entry_get_name(list_entry), false); /* remove /dev/{block,char}/$major:$minor */ - snprintf(filename, sizeof(filename), "/dev/%s/%u:%u", + xsprintf(filename, "/dev/%s/%u:%u", streq(udev_device_get_subsystem(dev), "block") ? "block" : "char", - major(udev_device_get_devnum(dev)), minor(udev_device_get_devnum(dev))); + major(udev_device_get_devnum(dev)), + minor(udev_device_get_devnum(dev))); unlink(filename); } diff --git a/src/udev/udev-watch.c b/src/udev/udev-watch.c index 60de703706..c0f4973f93 100644 --- a/src/udev/udev-watch.c +++ b/src/udev/udev-watch.c @@ -26,6 +26,7 @@ #include <sys/inotify.h> #include <unistd.h> +#include "stdio-util.h" #include "udev.h" static int inotify_fd = -1; @@ -105,7 +106,7 @@ void udev_watch_begin(struct udev *udev, struct udev_device *dev) { return; } - snprintf(filename, sizeof(filename), "/run/udev/watch/%d", wd); + xsprintf(filename, "/run/udev/watch/%d", wd); mkdir_parents(filename, 0755); unlink(filename); r = symlink(udev_device_get_id_filename(dev), filename); @@ -129,7 +130,7 @@ void udev_watch_end(struct udev *udev, struct udev_device *dev) { log_debug("removing watch on '%s'", udev_device_get_devnode(dev)); inotify_rm_watch(inotify_fd, wd); - snprintf(filename, sizeof(filename), "/run/udev/watch/%d", wd); + xsprintf(filename, "/run/udev/watch/%d", wd); unlink(filename); udev_device_set_watch_handle(dev, -1); @@ -143,7 +144,7 @@ struct udev_device *udev_watch_lookup(struct udev *udev, int wd) { if (inotify_fd < 0 || wd < 0) return NULL; - snprintf(filename, sizeof(filename), "/run/udev/watch/%d", wd); + xsprintf(filename, "/run/udev/watch/%d", wd); len = readlink(filename, device, sizeof(device)); if (len <= 0 || (size_t)len == sizeof(device)) return NULL; diff --git a/src/udev/udevd.c b/src/udev/udevd.c index 366e7fbb87..8627a81ec2 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -1652,7 +1652,8 @@ exit: int main(int argc, char *argv[]) { _cleanup_free_ char *cgroup = NULL; - int r, fd_ctrl, fd_uevent; + _cleanup_close_ int fd_ctrl = -1, fd_uevent = -1; + int r; log_set_target(LOG_TARGET_AUTO); log_parse_environment(); diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c index a5f4529cfd..622fbe9a6d 100644 --- a/src/vconsole/vconsole-setup.c +++ b/src/vconsole/vconsole-setup.c @@ -39,6 +39,7 @@ #include "log.h" #include "process-util.h" #include "signal-util.h" +#include "stdio-util.h" #include "string-util.h" #include "terminal-util.h" #include "util.h" @@ -215,11 +216,11 @@ static void font_copy_to_all_vcs(int fd) { continue; /* skip non-allocated ttys */ - snprintf(vcname, sizeof(vcname), "/dev/vcs%i", i); + xsprintf(vcname, "/dev/vcs%i", i); if (access(vcname, F_OK) < 0) continue; - snprintf(vcname, sizeof(vcname), "/dev/tty%i", i); + xsprintf(vcname, "/dev/tty%i", i); vcfd = open_terminal(vcname, O_RDWR|O_CLOEXEC); if (vcfd < 0) continue; diff --git a/test/TEST-02-CRYPTSETUP/test.sh b/test/TEST-02-CRYPTSETUP/test.sh index dada99df59..242090c761 100755 --- a/test/TEST-02-CRYPTSETUP/test.sh +++ b/test/TEST-02-CRYPTSETUP/test.sh @@ -77,7 +77,6 @@ EOF /dev/mapper/varcrypt /var ext3 defaults 0 1 EOF ) || return 1 - setup_nspawn_root ddebug "umount $TESTDIR/root/var" umount $TESTDIR/root/var diff --git a/test/TEST-03-JOBS/test-jobs.sh b/test/TEST-03-JOBS/test-jobs.sh index 42d475fe2f..4252a9a75d 100755 --- a/test/TEST-03-JOBS/test-jobs.sh +++ b/test/TEST-03-JOBS/test-jobs.sh @@ -4,9 +4,12 @@ # installed job. systemctl start --no-block hello-after-sleep.target -# sleep is now running, hello/start is waiting. Verify that: + systemctl list-jobs > /root/list-jobs.txt -grep 'sleep\.service.*running' /root/list-jobs.txt || exit 1 +while ! grep 'sleep\.service.*running' /root/list-jobs.txt; do + systemctl list-jobs > /root/list-jobs.txt +done + grep 'hello\.service.*waiting' /root/list-jobs.txt || exit 1 # This is supposed to finish quickly, not wait for sleep to finish. diff --git a/test/TEST-04-JOURNAL/test-journal.sh b/test/TEST-04-JOURNAL/test-journal.sh index 1ee39df432..3a05619ad5 100755 --- a/test/TEST-04-JOURNAL/test-journal.sh +++ b/test/TEST-04-JOURNAL/test-journal.sh @@ -51,5 +51,13 @@ journalctl --sync journalctl -b -o cat -t "$ID" >/output cmp /expected /output +# Don't lose streams on restart +systemctl start forever-print-hola +sleep 3 +systemctl restart systemd-journald +sleep 3 +systemctl stop forever-print-hola +[[ ! -f "/i-lose-my-logs" ]] + touch /testok exit 0 diff --git a/test/TEST-04-JOURNAL/test.sh b/test/TEST-04-JOURNAL/test.sh index 6c5b5cf34e..1a14f76060 100755 --- a/test/TEST-04-JOURNAL/test.sh +++ b/test/TEST-04-JOURNAL/test.sh @@ -57,6 +57,15 @@ ExecStart=/test-journal.sh Type=oneshot EOF + cat >$initdir/etc/systemd/system/forever-print-hola.service <<EOF +[Unit] +Description=ForeverPrintHola service + +[Service] +Type=simple +ExecStart=/bin/sh -x -c 'while :; do printf "Hola\n" || touch /i-lose-my-logs; sleep 1; done' +EOF + cp test-journal.sh $initdir/ setup_testsuite diff --git a/test/test-execute/exec-capabilityambientset-merge.service b/test/test-execute/exec-capabilityambientset-merge.service new file mode 100644 index 0000000000..64964380e2 --- /dev/null +++ b/test/test-execute/exec-capabilityambientset-merge.service @@ -0,0 +1,9 @@ +[Unit] +Description=Test for AmbientCapabilities + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000003000"' +Type=oneshot +User=nobody +AmbientCapabilities=CAP_NET_ADMIN +AmbientCapabilities=CAP_NET_RAW diff --git a/test/test-execute/exec-capabilityambientset.service b/test/test-execute/exec-capabilityambientset.service new file mode 100644 index 0000000000..d63f884ef8 --- /dev/null +++ b/test/test-execute/exec-capabilityambientset.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for AmbientCapabilities + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000003000"' +Type=oneshot +User=nobody +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW diff --git a/test/test-functions b/test/test-functions index 9288200717..961a6254d8 100644 --- a/test/test-functions +++ b/test/test-functions @@ -92,7 +92,7 @@ $KERNEL_APPEND \ run_nspawn() { set -x - ../../systemd-nspawn --register=no --boot --directory=$TESTDIR/nspawn-root $ROOTLIBDIR/systemd $KERNEL_APPEND + ../../systemd-nspawn --register=no --directory=$TESTDIR/nspawn-root $ROOTLIBDIR/systemd $KERNEL_APPEND } setup_basic_environment() { @@ -111,21 +111,61 @@ setup_basic_environment() { install_keymaps install_terminfo install_execs + install_fsck install_plymouth install_debug_tools install_ld_so_conf strip_binaries install_depmod_files generate_module_dependencies - # softlink mtab - ln -fs /proc/self/mounts $initdir/etc/mtab +} + +install_valgrind() { + if ! type -p valgrind; then + dfatal "Failed to install valgrind" + exit 1 + fi + + local _valgrind_bins=$(strace -e execve valgrind /bin/true 2>&1 >/dev/null | perl -lne 'print $1 if /^execve\("([^"]+)"/') + dracut_install $_valgrind_bins + + local _valgrind_libs=$(LD_DEBUG=files valgrind /bin/true 2>&1 >/dev/null | perl -lne 'print $1 if m{calling init: (/.*vgpreload_.*)}') + dracut_install $_valgrind_libs + + local _valgrind_dbg_and_supp=$( + strace -e open valgrind /bin/true 2>&1 >/dev/null | + perl -lne 'if (my ($fname) = /^open\("([^"]+).*= (?!-)\d+/) { print $fname if $fname =~ /debug|\.supp$/ }' + ) + dracut_install $_valgrind_dbg_and_supp +} + +create_valgrind_wrapper() { + local _valgrind_wrapper=$initdir/$ROOTLIBDIR/systemd-under-valgrind + ddebug "Create $_valgrind_wrapper" + cat >$_valgrind_wrapper <<EOF +#!/bin/bash + +exec valgrind --leak-check=full --log-file=/valgrind.out $ROOTLIBDIR/systemd "\$@" +EOF + chmod 0755 $_valgrind_wrapper +} + +install_fsck() { + dracut_install /sbin/fsck* + dracut_install -o /bin/fsck* } install_dmevent() { instmods dm_crypt =crypto type -P dmeventd >/dev/null && dracut_install dmeventd inst_libdir_file "libdevmapper-event.so*" - inst_rules 10-dm.rules 13-dm-disk.rules 95-dm-notify.rules + if [[ "$LOOKS_LIKE_DEBIAN" ]]; then + # dmsetup installs 55-dm and 60-persistent-storage-dm on Debian/Ubuntu + # see https://anonscm.debian.org/cgit/pkg-lvm/lvm2.git/tree/debian/patches/0007-udev.patch + inst_rules 55-dm.rules 60-persistent-storage-dm.rules + else + inst_rules 10-dm.rules 13-dm-disk.rules 95-dm-notify.rules + fi } install_systemd() { @@ -172,6 +212,10 @@ check_result_nspawn() { } strip_binaries() { + if [[ "$STRIP_BINARIES" = "no" ]]; then + ddebug "Don't strip binaries" + return 0 + fi ddebug "Strip binaries" find "$initdir" -executable -not -path '*/lib/modules/*.ko' -type f | xargs strip --strip-unneeded | ddebug } diff --git a/units/console-shell.service.m4.in b/units/console-shell.service.m4.in index 5c80722829..a345ec25d4 100644 --- a/units/console-shell.service.m4.in +++ b/units/console-shell.service.m4.in @@ -16,7 +16,7 @@ Before=getty.target [Service] Environment=HOME=/root -WorkingDirectory=/root +WorkingDirectory=-/root ExecStart=-@SULOGIN@ ExecStopPost=-@SYSTEMCTL@ poweroff Type=idle diff --git a/units/emergency.service.in b/units/emergency.service.in index 8dc3cbdede..fb390eacfe 100644 --- a/units/emergency.service.in +++ b/units/emergency.service.in @@ -15,7 +15,7 @@ Before=shutdown.target [Service] Environment=HOME=/root -WorkingDirectory=/root +WorkingDirectory=-/root ExecStartPre=-/bin/plymouth --wait quit ExecStartPre=-/bin/echo -e 'Welcome to emergency mode! After logging in, type "journalctl -xb" to view\\nsystem logs, "systemctl reboot" to reboot, "systemctl default" or ^D to\\ntry again to boot into default mode.' ExecStart=-/bin/sh -c "@SULOGIN@; @SYSTEMCTL@ --job-mode=fail --no-block default" diff --git a/units/kmod-static-nodes.service.in b/units/kmod-static-nodes.service.in index 0934a8751f..a9c8df1184 100644 --- a/units/kmod-static-nodes.service.in +++ b/units/kmod-static-nodes.service.in @@ -10,7 +10,7 @@ Description=Create list of required static device nodes for the current kernel DefaultDependencies=no Before=sysinit.target systemd-tmpfiles-setup-dev.service ConditionCapability=CAP_SYS_MODULE -ConditionPathExists=/lib/modules/%v/modules.devname +ConditionFileNotEmpty=/lib/modules/%v/modules.devname [Service] Type=oneshot diff --git a/units/rescue.service.in b/units/rescue.service.in index 432e4f3c84..6c202174d3 100644 --- a/units/rescue.service.in +++ b/units/rescue.service.in @@ -15,7 +15,7 @@ Before=shutdown.target [Service] Environment=HOME=/root -WorkingDirectory=/root +WorkingDirectory=-/root ExecStartPre=-/bin/plymouth quit ExecStartPre=-/bin/echo -e 'Welcome to emergency mode! After logging in, type "journalctl -xb" to view\\nsystem logs, "systemctl reboot" to reboot, "systemctl default" or ^D to\\nboot into default mode.' ExecStart=-/bin/sh -c "@SULOGIN@; @SYSTEMCTL@ --job-mode=fail --no-block default" |