diff options
123 files changed, 3401 insertions, 1204 deletions
diff --git a/Makefile.am b/Makefile.am index e895bc8cec..264a769f71 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 \ @@ -4883,7 +4876,7 @@ libnss_myhostname_la_LDFLAGS = \ -Wl,--version-script=$(top_srcdir)/src/nss-myhostname/nss-myhostname.sym libnss_myhostname_la_LIBADD = \ - libshared.la + libsystemd-internal.la lib_LTLIBRARIES += \ libnss_myhostname.la @@ -4983,7 +4976,7 @@ libnss_mymachines_la_LDFLAGS = \ -Wl,--version-script=$(top_srcdir)/src/nss-mymachines/nss-mymachines.sym libnss_mymachines_la_LIBADD = \ - libshared.la + libsystemd-internal.la lib_LTLIBRARIES += \ libnss_mymachines.la @@ -5178,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 \ @@ -5264,7 +5259,7 @@ libnss_resolve_la_LDFLAGS = \ -Wl,--version-script=$(top_srcdir)/src/nss-resolve/nss-resolve.sym libnss_resolve_la_LIBADD = \ - libshared.la \ + libsystemd-internal.la \ -ldl lib_LTLIBRARIES += \ @@ -5297,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 \ @@ -5315,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 @@ -6014,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" ?>' && \ @@ -6062,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 @@ -33,6 +33,12 @@ 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 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 4e846676e0..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> diff --git a/hwdb/60-keyboard.hwdb b/hwdb/60-keyboard.hwdb index 69a1e8fa37..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 ########################################################### 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/resolved.conf.xml b/man/resolved.conf.xml index 5da2d5488e..3ab7fc4a11 100644 --- a/man/resolved.conf.xml +++ b/man/resolved.conf.xml @@ -125,22 +125,6 @@ </varlistentry> <varlistentry> - <term><varname>MulticastDNS=</varname></term> - <listitem><para>Takes a boolean argument or - <literal>resolve</literal>. Controls Multicast DNS support - (<ulink url="https://tools.ietf.org/html/rfc6762">RFC - 6762</ulink>) on the local host. If true, enables full - Multicast DNS responder and resolver support. If false, - disables both. If set to <literal>resolve</literal>, only - resolution support is enabled, but responding is - disabled. Note that - <citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> - also maintains per-interface Multicast DNS settings. Multicast - DNS will be enabled on an interface only if the per-interface - and the global setting is on.</para></listitem> - </varlistentry> - - <varlistentry> <term><varname>DNSSEC=</varname></term> <listitem><para>Takes a boolean argument or <literal>allow-downgrade</literal>. If true all DNS lookups are 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.xml b/man/systemd.xml index b5570a4347..b8d91b8943 100644 --- a/man/systemd.xml +++ b/man/systemd.xml @@ -835,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> 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/cgroup-util.c b/src/basic/cgroup-util.c index 3945d37c8d..f873fb89d3 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -93,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) @@ -648,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; @@ -2091,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/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/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/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/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/core/dbus-execute.c b/src/core/dbus-execute.c index c2238c8c43..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; } @@ -1382,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 ac91568b63..0028730889 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -2319,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) { @@ -2683,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/manager.c b/src/core/manager.c index 39e3cbbfe1..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; diff --git a/src/core/transaction.c b/src/core/transaction.c index bc85cef266..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.", 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/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index 4e96fb0a4d..f5fe165fa3 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -700,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); @@ -840,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-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/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 7a5f6cda87..e93b6ac448 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -77,6 +77,8 @@ #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 b2d685855e..e7d6170eec 100644 --- a/src/libsystemd/sd-bus/bus-kernel.c +++ b/src/libsystemd/sd-bus/bus-kernel.c @@ -270,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; @@ -288,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); @@ -332,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; 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 aeb06ad9a8..11c7330b9b 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -661,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; } @@ -2751,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 @@ -2770,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-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/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-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/machinectl.c b/src/machine/machinectl.c index 38a75f85d5..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; } 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/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 10fec5e75f..bbda691c08 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -2039,9 +2039,9 @@ static int link_configure(Link *link) { assert(link->network); assert(link->state == LINK_STATE_PENDING); - /* Drop foreign config, but ignore loopback device. - * We do not want to remove loopback address. */ - if (!(link->flags & IFF_LOOPBACK)) { + /* Drop foreign config, but ignore loopback or critical devices. + * We do not want to remove loopback address or addresses used for root NFS. */ + if (!(link->flags & IFF_LOOPBACK) && !(link->network->dhcp_critical)) { r = link_drop_foreign_config(link); if (r < 0) return r; diff --git a/src/nss-mymachines/nss-mymachines.c b/src/nss-mymachines/nss-mymachines.c index 40c8ad3a19..dcdbc31a78 100644 --- a/src/nss-mymachines/nss-mymachines.c +++ b/src/nss-mymachines/nss-mymachines.c @@ -27,7 +27,6 @@ #include "alloc-util.h" #include "bus-common-errors.h" -#include "bus-util.h" #include "hostname-util.h" #include "in-addr-util.h" #include "macro.h" diff --git a/src/nss-resolve/nss-resolve.c b/src/nss-resolve/nss-resolve.c index bd8e27dc74..a268c3ac31 100644 --- a/src/nss-resolve/nss-resolve.c +++ b/src/nss-resolve/nss-resolve.c @@ -29,7 +29,6 @@ #include "sd-bus.h" #include "bus-common-errors.h" -#include "bus-util.h" #include "in-addr-util.h" #include "macro.h" #include "nss-util.h" 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 33f4dd9cb6..22004a00cd 100644 --- a/src/resolve/RFCs +++ b/src/resolve/RFCs @@ -13,14 +13,14 @@ Y https://tools.ietf.org/html/rfc1123 → Requirements for Internet Hosts -- App 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 @@ -31,26 +31,26 @@ Y https://tools.ietf.org/html/rfc4509 → Use of SHA-256 in DNSSEC Delegation Si ~ https://tools.ietf.org/html/rfc4697 → Observed DNS Resolution Misbehavior Y https://tools.ietf.org/html/rfc4795 → Link-Local Multicast Name Resolution (LLMNR) Y 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 +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 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 +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 diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c index 47a37fa0a7..058d14009a 100644 --- a/src/resolve/dns-type.c +++ b/src/resolve/dns-type.c @@ -135,6 +135,17 @@ bool dns_type_may_wildcard(uint16_t type) { 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, diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h index 747bc854e1..78ff71b06e 100644 --- a/src/resolve/dns-type.h +++ b/src/resolve/dns-type.h @@ -132,6 +132,7 @@ 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 41f90dedfd..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) { @@ -74,7 +63,7 @@ static int reply_query_state(DnsQuery *q) { _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 */ @@ -86,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); @@ -156,7 +145,7 @@ 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) @@ -177,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) @@ -195,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; } @@ -239,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; @@ -269,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_query_new(m, &q, question, ifindex, flags); + r = dns_question_new_address(&question_idna, family, hostname, true); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags); if (r < 0) return r; @@ -298,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; @@ -311,7 +309,7 @@ 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) @@ -327,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) { @@ -411,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; @@ -465,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); @@ -477,7 +478,7 @@ 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) @@ -493,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; } @@ -582,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; @@ -622,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) @@ -636,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) @@ -673,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) @@ -680,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) @@ -688,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) @@ -746,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); @@ -789,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; } @@ -810,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; } @@ -846,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); @@ -923,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; @@ -961,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); @@ -973,7 +975,7 @@ 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) @@ -981,53 +983,48 @@ static void bus_method_resolve_service_complete(DnsQuery *q) { 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; - - if (rr->key->type != DNS_TYPE_SRV) - 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 (dns_name_is_root(rr->srv.name)) { - has_root_domain = true; - continue; - } + if (rr->key->type != DNS_TYPE_SRV) + 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 (dns_name_is_root(rr->srv.name)) { + has_root_domain = true; + continue; + } - if (r < 0) - goto finish; - } + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + q->block_all_complete ++; + r = resolve_service_hostname(q, rr, ifindex); + q->block_all_complete --; - 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; } @@ -1045,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; @@ -1068,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; @@ -1091,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; @@ -1130,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; @@ -1175,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; @@ -1184,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++; @@ -1193,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; } @@ -1306,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; @@ -1322,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, }; @@ -1401,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-dns-answer.c b/src/resolve/resolved-dns-answer.c index c359432a7a..f74e440531 100644 --- a/src/resolve/resolved-dns-answer.c +++ b/src/resolve/resolved-dns-answer.c @@ -821,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 3eff21f8d0..1875fd6136 100644 --- a/src/resolve/resolved-dns-answer.h +++ b/src/resolve/resolved-dns-answer.h @@ -83,6 +83,8 @@ 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 301f383809..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 { diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index afff979b5a..1f48f588ce 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -35,17 +35,12 @@ * * TODO: * - * - wildcard zones compatibility (NSEC/NSEC3 wildcard check is missing) - * - multi-label zone compatibility - * - cname/dname compatibility - * - nxdomain on qname * - bus calls to override DNSEC setting per interface * - log all DNSSEC downgrades + * - log all RRs that failed validation * - enable by default - * - * - RFC 4035, Section 5.3.4 (When receiving a positive wildcard reply, use NSEC to ensure it actually really applies) - * - RFC 6840, Section 4.1 (ensure we don't get fed a glue NSEC from the parent zone) - * - RFC 6840, Section 4.3 (check for CNAME on NSEC too) + * - 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 @@ -430,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; @@ -499,6 +545,35 @@ 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, const DnsResourceKey *key, @@ -508,14 +583,14 @@ int dnssec_verify_rrset( 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; - const char *source; assert(key); assert(rrsig); @@ -536,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; @@ -544,8 +627,32 @@ 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(DNS_RESOURCE_KEY_NAME(key), rrsig->rrsig.labels, &source); + 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)) { @@ -556,11 +663,11 @@ int dnssec_verify_rrset( 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(DNS_RESOURCE_KEY_NAME(key), "*"); + r = dns_name_startswith(name, "*"); if (r < 0) return r; if (r > 0) - source = DNS_RESOURCE_KEY_NAME(key); + source = name; wildcard = r == 0; } else @@ -675,12 +782,17 @@ int dnssec_verify_rrset( if (r < 0) goto finish; - if (!r) + /* 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: @@ -719,8 +831,6 @@ int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnske } int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) { - int r; - assert(key); assert(rrsig); @@ -733,45 +843,9 @@ 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, const DnsResourceKey *key, @@ -841,10 +915,6 @@ int dnssec_verify_rrset_search( 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; @@ -932,16 +1002,6 @@ int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { 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; @@ -995,7 +1055,7 @@ static int digest_to_gcrypt_md(uint8_t algorithm) { } } -int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) { +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; @@ -1065,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; @@ -1082,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; @@ -1092,9 +1151,9 @@ int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ if (r == 0) continue; - r = dnssec_verify_dnskey(dnskey, ds, false); - if (r == -EKEYREJECTED) - return 0; /* The DNSKEY is revoked or otherwise invalid, we won't bless it */ + 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) @@ -1211,6 +1270,13 @@ static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) { 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; @@ -1244,6 +1310,7 @@ static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) { if (r == 0) return 0; + /* Make sure both have the same parent */ return dns_name_equal(a, b); } @@ -1535,10 +1602,158 @@ found_closest_encloser: return 0; } +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) { - DnsResourceRecord *rr; - bool have_nsec3 = false; + 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); @@ -1546,53 +1761,117 @@ int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *r /* 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; - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key)); + /* 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; + + /* 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; - - if (authenticated) - *authenticated = flags & DNS_ANSWER_AUTHENTICATED; - if (ttl) - *ttl = rr->ttl; - - 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; - if (ttl) - *ttl = rr->ttl; + if (authenticated) + *authenticated = flags & DNS_ANSWER_AUTHENTICATED; + if (ttl) + *ttl = rr->ttl; - return 0; - } - break; + return 0; + } - case DNS_TYPE_NSEC3: - have_nsec3 = true; - break; + /* 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. */ @@ -1621,15 +1900,17 @@ int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name if (rr->key->type != type && type != DNS_TYPE_ANY) continue; - r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), zone); - if (r < 0) - return r; - if (r == 0) - 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; @@ -1640,6 +1921,13 @@ int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name 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; diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h index b9d32db120..955017e8cb 100644 --- a/src/resolve/resolved-dns-dnssec.h +++ b/src/resolve/resolved-dns-dnssec.h @@ -61,8 +61,8 @@ int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig); 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, bool mask_revoke); -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); diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index a8a8632491..9a5223ef01 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -499,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)); @@ -519,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; @@ -1083,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; } @@ -2017,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; @@ -2064,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; @@ -2078,36 +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; } - /* Note that we accept the OPT RR in - * any section, not just in the - * additional section, as some routers - * (Belkin!) blindly copy the OPT RR - * from the query to the reply packet, - * and don't get the section right. */ + if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)) { + /* 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) | @@ -2116,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 1948d59fc4..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; + + r = dns_resource_key_to_string(key, &p); + if (r < 0) + return r; - for (i = 0; i < question->n_keys; i++) { - _cleanup_free_ char *p; + 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) @@ -1115,33 +1197,60 @@ 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; @@ -1150,15 +1259,16 @@ int dns_query_process_cname(DnsQuery *q) { 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 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) @@ -1219,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 4a0d265a2d..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); @@ -104,7 +111,7 @@ enum { 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); @@ -116,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 dbf840157f..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,88 @@ 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; diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index fe29a41566..d9c31e81c5 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -81,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()! */ }; @@ -108,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; @@ -284,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); @@ -296,6 +308,11 @@ 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); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index dd3609bd12..8a52d66fad 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -67,11 +67,9 @@ int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int * changes. */ if (l) - s->dnssec_mode = l->dnssec_mode; - if (s->dnssec_mode == _DNSSEC_MODE_INVALID) - s->dnssec_mode = m->dnssec_mode; - if (s->dnssec_mode == _DNSSEC_MODE_INVALID) - s->dnssec_mode = DNSSEC_NO; + s->dnssec_mode = link_get_dnssec_mode(l); + else + s->dnssec_mode = manager_get_dnssec_mode(m); } LIST_PREPEND(scopes, m->dns_scopes, s); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 0969e31e8a..5a86661807 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -232,7 +232,9 @@ static void dns_server_verified(DnsServer *s, DnsServerFeatureLevel level) { return; if (s->verified_feature_level != level) { - log_debug("Verified feature level %s.", dns_server_feature_level_to_string(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; } @@ -246,13 +248,18 @@ void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLeve 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) - /* 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. */ - dns_server_verified(s, DNS_SERVER_FEATURE_LEVEL_LARGE - 1); - else - /* A successful UDP reply, verifies UDP, ENDS0 and DO levels */ - dns_server_verified(s, level); + level = DNS_SERVER_FEATURE_LEVEL_LARGE - 1; } else if (protocol == IPPROTO_TCP) { @@ -260,9 +267,11 @@ void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLeve s->n_failed_tcp = 0; /* Successful TCP connections are only useful to verify the TCP feature level. */ - dns_server_verified(s, DNS_SERVER_FEATURE_LEVEL_TCP); + 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. */ @@ -294,7 +303,6 @@ void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel le 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. */ @@ -306,7 +314,6 @@ void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level) { void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level) { assert(s); - assert(s->manager); /* Invoked whenever we get a packet with TC bit set. */ @@ -316,13 +323,30 @@ void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level) { s->packet_truncated = true; } -void dns_server_packet_rrsig_missing(DnsServer *s) { +void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level) { assert(s); - assert(s->manager); - log_warning("DNS server %s does not augment replies with RRSIG records, DNSSEC not available.", dns_server_string(s)); + if (level < DNS_SERVER_FEATURE_LEVEL_DO) + return; + + /* 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->rrsig_missing = true; + 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) { @@ -352,6 +376,17 @@ static void dns_server_reset_counters(DnsServer *s) { 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) { @@ -361,11 +396,13 @@ DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { dns_server_grace_period_expired(s)) { s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; - s->rrsig_missing = false; dns_server_reset_counters(s); - log_info("Grace period over, resuming full feature set (%s) for DNS server %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)); @@ -375,46 +412,75 @@ DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { 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) + 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->n_failed_udp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && - s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_UDP) || - (s->packet_failed && - s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) || - (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && - s->packet_truncated && - s->possible_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP)) - - /* Downgrade the feature one level, maybe things will work better then. We do this under any of - * three conditions: - * - * 1. 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. - * - * 2. 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. - * - * 3. 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. - */ + } 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. */ + + log_debug("Lost too many UDP packets, downgrading feature level..."); + s->possible_feature_level--; + + } 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", + 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)); } @@ -468,7 +534,10 @@ bool dns_server_dnssec_supported(DnsServer *server) { if (server->possible_feature_level < DNS_SERVER_FEATURE_LEVEL_DO) return false; - if (server->rrsig_missing) + 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 */ diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index 323f702903..02bd3463a7 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -68,20 +68,20 @@ struct DnsServer { DnsServerFeatureLevel verified_feature_level; DnsServerFeatureLevel possible_feature_level; + size_t received_udp_packet_max; + 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; @@ -108,7 +108,8 @@ void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLeve 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_truncated(DnsServer *s, DnsServerFeatureLevel level); -void dns_server_packet_rrsig_missing(DnsServer *s); +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); diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index c7d2d82ecf..5640cd1d33 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -31,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); @@ -43,6 +45,17 @@ static void dns_transaction_reset_answer(DnsTransaction *t) { 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) { assert(t); @@ -95,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); @@ -127,6 +137,22 @@ bool dns_transaction_gc(DnsTransaction *t) { 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) { _cleanup_(dns_transaction_freep) DnsTransaction *t = NULL; int r; @@ -145,6 +171,9 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) 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; @@ -164,11 +193,7 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) 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) { @@ -195,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; @@ -354,6 +395,26 @@ static void dns_transaction_retry(DnsTransaction *t) { 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; @@ -529,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 @@ -675,8 +809,6 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { return; } else if (DNS_PACKET_TC(p)) dns_server_packet_truncated(t->server, t->current_feature_level); - else - dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, ts - t->start_usec, p->size); break; @@ -726,13 +858,30 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { 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 */ @@ -818,12 +967,12 @@ static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *use return 0; } if (r == 0) { - log_debug("Received inappropriate DNS packet as response, ignoring: %m"); + 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: %m"); + log_debug("Received packet with incorrect transaction ID, ignoring."); return 0; } @@ -961,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) { @@ -1774,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) @@ -1873,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; - } - - return; + /* 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. */ -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) { @@ -1950,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) @@ -2378,6 +2480,31 @@ static int dns_transaction_invalidate_revoked_keys(DnsTransaction *t) { 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; enum { @@ -2416,7 +2543,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { 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("Cannot validate response, server lacks DNSSEC support."); + log_debug("Not validating response, server lacks DNSSEC support."); return 0; } @@ -2428,13 +2555,18 @@ int dns_transaction_validate_dnssec(DnsTransaction *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; - /* Third, remove all DNSKEY and DS RRs again that our trust + /* 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. */ @@ -2537,11 +2669,9 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { * that no matching non-wildcard RR exists.*/ /* First step, determine the source of synthesis */ - r = dns_name_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rrsig->rrsig.labels, &source); + r = dns_resource_record_source(rrsig, &source); if (r < 0) return r; - if (r == 0) - return -EBADMSG; r = dnssec_test_positive_wildcard( validated, @@ -2592,7 +2722,7 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { /* 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); + dns_server_packet_rrsig_missing(t->server, t->current_feature_level); if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) { @@ -2668,17 +2798,30 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { 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; + + /* 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; + } + + 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. */ + /* 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; diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c index 9bee44b5c7..02d7ac91e1 100644 --- a/src/resolve/resolved-dns-trust-anchor.c +++ b/src/resolve/resolved-dns-trust-anchor.c @@ -665,7 +665,7 @@ static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor *d, DnsResourceReco * 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(revoked_dnskey, anchor, true); + r = dnssec_verify_dnskey_by_ds(revoked_dnskey, anchor, true); if (r < 0) return r; if (r == 0) diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf index c5ad04afd7..82f26215df 100644 --- a/src/resolve/resolved-gperf.gperf +++ b/src/resolve/resolved-gperf.gperf @@ -18,5 +18,4 @@ Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0 Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0 Resolve.Domains, config_parse_search_domains, 0, 0 Resolve.LLMNR, config_parse_resolve_support, 0, offsetof(Manager, llmnr_support) -Resolve.MulticastDNS, config_parse_resolve_support, 0, offsetof(Manager, mdns_support) Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode) 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 928307e004..b203f19dbb 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -63,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_all(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)); @@ -82,18 +94,17 @@ Link *link_free(Link *l) { dns_scope_free(l->mdns_ipv4_scope); dns_scope_free(l->mdns_ipv6_scope); - set_free_free(l->dnssec_negative_trust_anchors); - free(l); return NULL; } -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) @@ -102,7 +113,7 @@ static void link_allocate_scopes(Link *l) { } else l->unicast_scope = dns_scope_free(l->unicast_scope); - if (link_relevant(l, AF_INET) && + 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) { @@ -113,7 +124,7 @@ 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) && + if (link_relevant(l, AF_INET6, true) && l->llmnr_support != RESOLVE_SUPPORT_NO && l->manager->llmnr_support != RESOLVE_SUPPORT_NO && socket_ipv6_is_supported()) { @@ -125,7 +136,7 @@ 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) && + 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) { @@ -136,7 +147,7 @@ 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) && + 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) { @@ -277,8 +288,29 @@ clear: 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); @@ -291,12 +323,14 @@ static int link_update_dnssec_mode(Link *l) { if (r < 0) goto clear; - l->dnssec_mode = dnssec_mode_from_string(m); - if (l->dnssec_mode < 0) { + mode = dnssec_mode_from_string(m); + if (mode < 0) { r = -EINVAL; goto clear; } + link_set_dnssec_mode(l, mode); + return 0; clear: @@ -383,11 +417,45 @@ 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); + + 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); @@ -411,35 +479,47 @@ int link_update_monitor(Link *l) { 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; @@ -500,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; diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h index db0e51da04..6544214b77 100644 --- a/src/resolve/resolved-link.h +++ b/src/resolve/resolved-link.h @@ -78,6 +78,8 @@ struct Link { DnsScope *mdns_ipv4_scope; DnsScope *mdns_ipv6_scope; + bool is_managed; + char name[IF_NAMESIZE]; uint32_t mtu; }; @@ -86,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-manager.c b/src/resolve/resolved-manager.c index b17a19d331..d6d75a3f78 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -1173,3 +1173,33 @@ int manager_compile_search_domains(Manager *m, OrderedSet **domains) { return 0; } + +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 1907d2e1bc..8b13074298 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -158,3 +158,6 @@ 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); + +DnssecMode manager_get_dnssec_mode(Manager *m); +bool manager_dnssec_supported(Manager *m); diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in index 0ba572d113..efc9c6733a 100644 --- a/src/resolve/resolved.conf.in +++ b/src/resolve/resolved.conf.in @@ -16,5 +16,4 @@ #FallbackDNS=@DNS_SERVERS@ #Domains= #LLMNR=yes -#MulticastDNS=no #DNSSEC=no 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 0c9efde1fe..45fe1997e2 100644 --- a/src/resolve/test-dnssec.c +++ b/src/resolve/test-dnssec.c @@ -270,8 +270,8 @@ static void test_dnssec_verify_dns_key(void) { log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); - assert_se(dnssec_verify_dnskey(dnskey, ds1, false) > 0); - assert_se(dnssec_verify_dnskey(dnskey, ds2, false) > 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/dns-domain.c b/src/shared/dns-domain.c index d1fb97fe92..3ad409fc29 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -413,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) @@ -432,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; @@ -487,7 +480,6 @@ void dns_name_hash_func(const void *s, struct siphash *state) { for (;;) { char label[DNS_LABEL_MAX+1]; - int k; r = dns_label_unescape(&p, label, sizeof(label)); if (r < 0) @@ -495,12 +487,6 @@ void dns_name_hash_func(const void *s, struct siphash *state) { if (r == 0) break; - k = dns_label_undo_idna(label, r, label, sizeof(label)); - if (k < 0) - break; - if (k > 0) - r = k; - 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 */ @@ -512,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); @@ -531,22 +517,6 @@ int dns_name_compare_func(const void *a, const void *b) { if (r < 0 || q < 0) return r - q; - if (r > 0) - k = dns_label_undo_idna(la, r, la, sizeof(la)); - else - k = 0; - if (q > 0) - w = dns_label_undo_idna(lb, q, lb, sizeof(lb)); - else - w = 0; - - if (k < 0 || w < 0) - return k - w; - if (k > 0) - r = k; - if (w > 0) - q = w; - r = ascii_strcasecmp_nn(la, r, lb, q); if (r != 0) return r; @@ -558,24 +528,6 @@ const struct hash_ops dns_name_hash_ops = { .compare = dns_name_compare_func }; -static int dns_label_unescape_undo_idna(const char **name, char *dest, size_t sz) { - int r, k; - - /* Clobbers all arguments on failure... */ - - r = dns_label_unescape(name, dest, sz); - if (r <= 0) - return r; - - k = dns_label_undo_idna(dest, r, dest, sz); - if (k < 0) - return k; - if (k == 0) /* not an IDNA name */ - return r; - - return k; -} - int dns_name_equal(const char *x, const char *y) { int r, q; @@ -585,11 +537,11 @@ int dns_name_equal(const char *x, const char *y) { for (;;) { char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; - r = dns_label_unescape_undo_idna(&x, la, sizeof(la)); + r = dns_label_unescape(&x, la, sizeof(la)); if (r < 0) return r; - q = dns_label_unescape_undo_idna(&y, lb, sizeof(lb)); + q = dns_label_unescape(&y, lb, sizeof(lb)); if (q < 0) return q; @@ -616,14 +568,14 @@ int dns_name_endswith(const char *name, const char *suffix) { for (;;) { char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; - r = dns_label_unescape_undo_idna(&n, ln, sizeof(ln)); + r = dns_label_unescape(&n, ln, sizeof(ln)); if (r < 0) return r; if (!saved_n) saved_n = n; - q = dns_label_unescape_undo_idna(&s, ls, sizeof(ls)); + q = dns_label_unescape(&s, ls, sizeof(ls)); if (q < 0) return q; @@ -655,13 +607,13 @@ int dns_name_startswith(const char *name, const char *prefix) { for (;;) { char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX]; - r = dns_label_unescape_undo_idna(&p, lp, sizeof(lp)); + r = dns_label_unescape(&p, lp, sizeof(lp)); if (r < 0) return r; if (r == 0) return true; - q = dns_label_unescape_undo_idna(&n, ln, sizeof(ln)); + q = dns_label_unescape(&n, ln, sizeof(ln)); if (q < 0) return q; @@ -690,14 +642,14 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char if (!saved_before) saved_before = n; - r = dns_label_unescape_undo_idna(&n, ln, sizeof(ln)); + r = dns_label_unescape(&n, ln, sizeof(ln)); if (r < 0) return r; if (!saved_after) saved_after = n; - q = dns_label_unescape_undo_idna(&s, ls, sizeof(ls)); + q = dns_label_unescape(&s, ls, sizeof(ls)); if (q < 0) return q; @@ -1157,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; @@ -1182,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; @@ -1219,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; + + return dns_name_equal(a, b); +} - r = dns_name_parent(&a); - if (r <= 0) +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; - n_labels --; + 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++; } +} - return dns_name_equal(a, b); +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; + + if (first) + first = false; + else + buf[n++] = '.'; + + n +=r; + } + + 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 4fbe0a618f..40c9ee5f27 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -104,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/systemctl/systemctl.c b/src/systemctl/systemctl.c index 3f2c308b8f..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; } @@ -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); 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-dns-domain.c b/src/test/test-dns-domain.c index fe3ae45349..3b260ee75d 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -578,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(); @@ -603,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/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/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/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/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-functions b/test/test-functions index 03753db38a..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,6 +111,7 @@ setup_basic_environment() { install_keymaps install_terminfo install_execs + install_fsck install_plymouth install_debug_tools install_ld_so_conf @@ -149,11 +150,22 @@ 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() { @@ -200,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/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" |