diff options
425 files changed, 19860 insertions, 5940 deletions
diff --git a/.dir-locals.el b/.dir-locals.el index 9d9f8cd178..9388bd66c2 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -3,5 +3,12 @@ ; Mode can be nil, which gives default values. ((nil . ((indent-tabs-mode . nil) - (tab-width . 8))) -) + (tab-width . 8) + (fill-column . 119))) + (c-mode . ((c-basic-offset . 8) + (eval . (c-set-offset 'substatement-open 0)) + (eval . (c-set-offset 'statement-case-open 0)) + (eval . (c-set-offset 'case-label 0)) + (eval . (c-set-offset 'arglist-intro '++)) + (eval . (c-set-offset 'arglist-close 0)))) + (nxml-mode . ((nxml-child-indent . 2)))) diff --git a/.gitignore b/.gitignore index 4d9cbbe4a0..586b3796b1 100644 --- a/.gitignore +++ b/.gitignore @@ -109,7 +109,7 @@ /systemd-remount-api-vfs /systemd-remount-fs /systemd-reply-password -/systemd-resolve-host +/systemd-resolve /systemd-resolved /systemd-rfkill /systemd-run @@ -137,6 +137,7 @@ /test-af-list /test-architecture /test-arphrd-list +/test-ask-password-api /test-async /test-audit-type /test-barrier @@ -177,6 +178,7 @@ /test-daemon /test-date /test-device-nodes +/test-dnssec-complex /test-dhcp-client /test-dhcp-option /test-dhcp-server @@ -249,11 +251,13 @@ /test-rbtree /test-replace-var /test-resolve +/test-resolve-tables /test-ring /test-rlimit-util /test-sched-prio /test-set /test-sigbus +/test-signal-util /test-siphash24 /test-sleep /test-socket-util @@ -7,3 +7,4 @@ set tabstop=8 set shiftwidth=8 set expandtab set makeprg=GCC_COLORS=\ make +set tw=119 diff --git a/CODING_STYLE b/CODING_STYLE index 0064303203..46e366898e 100644 --- a/CODING_STYLE +++ b/CODING_STYLE @@ -7,7 +7,7 @@ - Don't break code lines too eagerly. We do *not* force line breaks at 80ch, all of today's screens should be much larger than that. But - then again, don't overdo it, ~140ch should be enough really. + then again, don't overdo it, ~119ch should be enough really. - Variables and functions *must* be static, unless they have a prototype, and are supposed to be exported. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..18081cbb48 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# Contributing + +We welcome contributions from everyone. However, please follow the following guidelines when posting a GitHub Pull +Request or filing a GitHub Issue on the systemd project: + +## Filing Issues + +* We use GitHub Issues **exclusively** for tracking **bugs** and **feature** **requests** of systemd. If you are + looking for help, please contact our [mailing list](http://lists.freedesktop.org/mailman/listinfo/systemd-devel) + instead. +* We only track bugs in the **two** **most** **recently** **released** **versions** of systemd in the GitHub Issue + tracker. If you are using an older version of systemd, please contact your distribution's bug tracker instead. +* When filing an issue, specify the **systemd** **version** you are experiencing the issue with. Also, indicate which + **distribution** you are using. +* Please include an explanation how to reproduce the issue you are pointing out. + +Following these guidelines makes it easier for us to process your issue, and ensures we won't close your issue +right-away for being misfiled. + +## Posting Pull Requests + +* Make sure to post PRs only relative to a very recent git master. +* Follow our [Coding Style](https://raw.githubusercontent.com/systemd/systemd/master/CODING_STYLE) when contributing + code. This is a requirement for all code we merge. +* Make sure to run "make check" locally, before posting your PR. We use a CI system, meaning we don't even look at your + PR, if the build and tests don't pass. +* If you need to update the code in an existing PR, please consider opening a new PR (mentioning in it which old PR it + replaces) and closing the old PR. This is much preferable over force-pushing a new patch set into the PR's branch, as + commit comments aren't lost that way. That said, we don't follow this rule ourselves quite often, hence this is + really just a say as we say, not say as we do... + +## Final Words + +We'd like to apologize in advance if we are not able to process and reply to your issue or PR right-away. We have a lot +of work to do, but we are trying our best! + +Thank you very much for your contributions! diff --git a/Makefile-man.am b/Makefile-man.am index e91ecfdfdf..28b5fb6adb 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -25,6 +25,7 @@ MANPAGES += \ man/machine-info.5 \ man/os-release.5 \ man/sd-bus-errors.3 \ + man/sd-bus.3 \ man/sd-daemon.3 \ man/sd-event.3 \ man/sd-id128.3 \ @@ -71,6 +72,7 @@ MANPAGES += \ man/sd_id128_to_string.3 \ man/sd_is_fifo.3 \ man/sd_journal_add_match.3 \ + man/sd_journal_enumerate_fields.3 \ man/sd_journal_get_catalog.3 \ man/sd_journal_get_cursor.3 \ man/sd_journal_get_cutoff_realtime_usec.3 \ @@ -78,6 +80,7 @@ MANPAGES += \ man/sd_journal_get_fd.3 \ man/sd_journal_get_realtime_usec.3 \ man/sd_journal_get_usage.3 \ + man/sd_journal_has_runtime_files.3 \ man/sd_journal_next.3 \ man/sd_journal_open.3 \ man/sd_journal_print.3 \ @@ -120,6 +123,7 @@ MANPAGES += \ man/systemd-nspawn.1 \ man/systemd-path.1 \ man/systemd-remount-fs.service.8 \ + man/systemd-resolve.1 \ man/systemd-run.1 \ man/systemd-sleep.conf.5 \ man/systemd-socket-proxyd.8 \ @@ -234,6 +238,7 @@ MANPAGES_ALIAS += \ man/SD_JOURNAL_FOREACH.3 \ man/SD_JOURNAL_FOREACH_BACKWARDS.3 \ man/SD_JOURNAL_FOREACH_DATA.3 \ + man/SD_JOURNAL_FOREACH_FIELD.3 \ man/SD_JOURNAL_FOREACH_UNIQUE.3 \ man/SD_JOURNAL_INVALIDATE.3 \ man/SD_JOURNAL_LOCAL_ONLY.3 \ @@ -382,6 +387,7 @@ MANPAGES_ALIAS += \ man/sd_journal_get_events.3 \ man/sd_journal_get_monotonic_usec.3 \ man/sd_journal_get_timeout.3 \ + man/sd_journal_has_persistent_files.3 \ man/sd_journal_next_skip.3 \ man/sd_journal_open_container.3 \ man/sd_journal_open_directory.3 \ @@ -393,6 +399,7 @@ MANPAGES_ALIAS += \ man/sd_journal_process.3 \ man/sd_journal_reliable_fd.3 \ man/sd_journal_restart_data.3 \ + man/sd_journal_restart_fields.3 \ man/sd_journal_restart_unique.3 \ man/sd_journal_seek_cursor.3 \ man/sd_journal_seek_monotonic_usec.3 \ @@ -561,6 +568,7 @@ man/SD_JOURNAL_CURRENT_USER.3: man/sd_journal_open.3 man/SD_JOURNAL_FOREACH.3: man/sd_journal_next.3 man/SD_JOURNAL_FOREACH_BACKWARDS.3: man/sd_journal_next.3 man/SD_JOURNAL_FOREACH_DATA.3: man/sd_journal_get_data.3 +man/SD_JOURNAL_FOREACH_FIELD.3: man/sd_journal_enumerate_fields.3 man/SD_JOURNAL_FOREACH_UNIQUE.3: man/sd_journal_query_unique.3 man/SD_JOURNAL_INVALIDATE.3: man/sd_journal_get_fd.3 man/SD_JOURNAL_LOCAL_ONLY.3: man/sd_journal_open.3 @@ -709,6 +717,7 @@ man/sd_journal_get_data_threshold.3: man/sd_journal_get_data.3 man/sd_journal_get_events.3: man/sd_journal_get_fd.3 man/sd_journal_get_monotonic_usec.3: man/sd_journal_get_realtime_usec.3 man/sd_journal_get_timeout.3: man/sd_journal_get_fd.3 +man/sd_journal_has_persistent_files.3: man/sd_journal_has_runtime_files.3 man/sd_journal_next_skip.3: man/sd_journal_next.3 man/sd_journal_open_container.3: man/sd_journal_open.3 man/sd_journal_open_directory.3: man/sd_journal_open.3 @@ -720,6 +729,7 @@ man/sd_journal_printv.3: man/sd_journal_print.3 man/sd_journal_process.3: man/sd_journal_get_fd.3 man/sd_journal_reliable_fd.3: man/sd_journal_get_fd.3 man/sd_journal_restart_data.3: man/sd_journal_get_data.3 +man/sd_journal_restart_fields.3: man/sd_journal_enumerate_fields.3 man/sd_journal_restart_unique.3: man/sd_journal_query_unique.3 man/sd_journal_seek_cursor.3: man/sd_journal_seek_head.3 man/sd_journal_seek_monotonic_usec.3: man/sd_journal_seek_head.3 @@ -1012,6 +1022,9 @@ man/SD_JOURNAL_FOREACH_BACKWARDS.html: man/sd_journal_next.html man/SD_JOURNAL_FOREACH_DATA.html: man/sd_journal_get_data.html $(html-alias) +man/SD_JOURNAL_FOREACH_FIELD.html: man/sd_journal_enumerate_fields.html + $(html-alias) + man/SD_JOURNAL_FOREACH_UNIQUE.html: man/sd_journal_query_unique.html $(html-alias) @@ -1456,6 +1469,9 @@ man/sd_journal_get_monotonic_usec.html: man/sd_journal_get_realtime_usec.html man/sd_journal_get_timeout.html: man/sd_journal_get_fd.html $(html-alias) +man/sd_journal_has_persistent_files.html: man/sd_journal_has_runtime_files.html + $(html-alias) + man/sd_journal_next_skip.html: man/sd_journal_next.html $(html-alias) @@ -1489,6 +1505,9 @@ man/sd_journal_reliable_fd.html: man/sd_journal_get_fd.html man/sd_journal_restart_data.html: man/sd_journal_get_data.html $(html-alias) +man/sd_journal_restart_fields.html: man/sd_journal_enumerate_fields.html + $(html-alias) + man/sd_journal_restart_unique.html: man/sd_journal_query_unique.html $(html-alias) @@ -1990,16 +2009,21 @@ endif if ENABLE_RESOLVED MANPAGES += \ + man/dnssec-trust-anchors.d.5 \ man/nss-resolve.8 \ man/resolved.conf.5 \ man/systemd-resolved.service.8 MANPAGES_ALIAS += \ man/libnss_resolve.so.2.8 \ man/resolved.conf.d.5 \ - man/systemd-resolved.8 + man/systemd-resolved.8 \ + man/systemd.negative.5 \ + man/systemd.positive.5 man/libnss_resolve.so.2.8: man/nss-resolve.8 man/resolved.conf.d.5: man/resolved.conf.5 man/systemd-resolved.8: man/systemd-resolved.service.8 +man/systemd.negative.5: man/dnssec-trust-anchors.d.5 +man/systemd.positive.5: man/dnssec-trust-anchors.d.5 man/libnss_resolve.so.2.html: man/nss-resolve.html $(html-alias) @@ -2009,6 +2033,12 @@ man/resolved.conf.d.html: man/resolved.conf.html man/systemd-resolved.html: man/systemd-resolved.service.html $(html-alias) +man/systemd.negative.html: man/dnssec-trust-anchors.d.html + $(html-alias) + +man/systemd.positive.html: man/dnssec-trust-anchors.d.html + $(html-alias) + endif if ENABLE_RFKILL @@ -2434,6 +2464,7 @@ EXTRA_DIST += \ man/coredumpctl.xml \ man/crypttab.xml \ man/daemon.xml \ + man/dnssec-trust-anchors.d.xml \ man/file-hierarchy.xml \ man/halt.xml \ man/hostname.xml \ @@ -2465,6 +2496,7 @@ EXTRA_DIST += \ man/resolved.conf.xml \ man/runlevel.xml \ man/sd-bus-errors.xml \ + man/sd-bus.xml \ man/sd-daemon.xml \ man/sd-event.xml \ man/sd-id128.xml \ @@ -2513,6 +2545,7 @@ EXTRA_DIST += \ man/sd_id128_to_string.xml \ man/sd_is_fifo.xml \ man/sd_journal_add_match.xml \ + man/sd_journal_enumerate_fields.xml \ man/sd_journal_get_catalog.xml \ man/sd_journal_get_cursor.xml \ man/sd_journal_get_cutoff_realtime_usec.xml \ @@ -2520,6 +2553,7 @@ EXTRA_DIST += \ man/sd_journal_get_fd.xml \ man/sd_journal_get_realtime_usec.xml \ man/sd_journal_get_usage.xml \ + man/sd_journal_has_runtime_files.xml \ man/sd_journal_next.xml \ man/sd_journal_open.xml \ man/sd_journal_print.xml \ @@ -2589,6 +2623,7 @@ EXTRA_DIST += \ man/systemd-quotacheck.service.xml \ man/systemd-random-seed.service.xml \ man/systemd-remount-fs.service.xml \ + man/systemd-resolve.xml \ man/systemd-resolved.service.xml \ man/systemd-rfkill.service.xml \ man/systemd-run.xml \ diff --git a/Makefile.am b/Makefile.am index f552f7a60b..f1f8315f30 100644 --- a/Makefile.am +++ b/Makefile.am @@ -694,29 +694,27 @@ man_MANS = \ noinst_DATA += \ $(HTML_FILES) \ - $(HTML_ALIAS) + $(HTML_ALIAS) \ + docs/html/man +endif CLEANFILES += \ $(man_MANS) \ $(HTML_FILES) \ - $(HTML_ALIAS) + $(HTML_ALIAS) \ + docs/html/man docs/html/man: $(AM_V_at)$(MKDIR_P) $(dir $@) $(AM_V_LN)$(LN_S) -f ../../man $@ -noinst_DATA += \ - docs/html/man - -CLEANFILES += \ - docs/html/man - -if HAVE_PYTHON man/index.html: man/systemd.index.html $(AM_V_LN)$(LN_S) -f systemd.index.html $@ +if HAVE_PYTHON noinst_DATA += \ man/index.html +endif CLEANFILES += \ man/index.html @@ -745,11 +743,6 @@ CLEANFILES += \ man/systemd.index.xml \ man/systemd.directives.xml - -endif - -endif - EXTRA_DIST += \ $(filter-out man/systemd.directives.xml man/systemd.index.xml,$(XML_FILES)) \ tools/make-man-index.py \ @@ -841,10 +834,13 @@ libbasic_la_SOURCES = \ src/basic/mempool.h \ src/basic/hashmap.c \ src/basic/hashmap.h \ + src/basic/hash-funcs.c \ + src/basic/hash-funcs.h \ src/basic/siphash24.c \ src/basic/siphash24.h \ src/basic/set.h \ src/basic/ordered-set.h \ + src/basic/ordered-set.c \ src/basic/bitmap.c \ src/basic/bitmap.h \ src/basic/fdset.c \ @@ -1054,7 +1050,9 @@ libshared_la_SOURCES = \ src/shared/machine-image.c \ src/shared/machine-image.h \ src/shared/machine-pool.c \ - src/shared/machine-pool.h + src/shared/machine-pool.h \ + src/shared/resolve-util.c \ + src/shared/resolve-util.h if HAVE_UTMP libshared_la_SOURCES += \ @@ -1420,7 +1418,8 @@ manual_tests += \ test-ipcrm \ test-btrfs \ test-acd \ - test-ipv4ll-manual + test-ipv4ll-manual \ + test-ask-password-api if HAVE_LIBIPTC manual_tests += \ @@ -1500,8 +1499,10 @@ tests += \ test-af-list \ test-arphrd-list \ test-dns-domain \ + test-resolve-tables \ test-install-root \ - test-rlimit-util + test-rlimit-util \ + test-signal-util if HAVE_ACL tests += \ @@ -1663,6 +1664,17 @@ test_dns_domain_LDADD = \ libsystemd-network.la \ libshared.la +test_resolve_tables_SOURCES = \ + src/resolve/test-resolve-tables.c \ + src/shared/test-tables.h \ + src/resolve/dns-type.c \ + src/resolve/dns-type.h \ + src/resolve/dns_type-from-name.h \ + src/resolve/dns_type-to-name.h + +test_resolve_tables_LDADD = \ + libshared.la + if ENABLE_EFI manual_tests += \ test-boot-timestamp @@ -1876,6 +1888,18 @@ test_rlimit_util_SOURCES = \ test_rlimit_util_LDADD = \ libshared.la +test_ask_password_api_SOURCES = \ + src/test/test-ask-password-api.c + +test_ask_password_api_LDADD = \ + libshared.la + +test_signal_util_SOURCES = \ + src/test/test-signal-util.c + +test_signal_util_LDADD = \ + libshared.la + BUILT_SOURCES += \ src/test/test-hashmap-ordered.c @@ -2920,6 +2944,8 @@ systemd_nspawn_SOURCES = \ src/nspawn/nspawn-register.h \ src/nspawn/nspawn-setuid.c \ src/nspawn/nspawn-setuid.h \ + src/nspawn/nspawn-stub-pid1.c \ + src/nspawn/nspawn-stub-pid1.h \ src/core/mount-setup.c \ src/core/mount-setup.h \ src/core/loopback-setup.c \ @@ -3744,6 +3770,7 @@ EXTRA_DIST += \ hwdb/sdio.ids # ------------------------------------------------------------------------------ +if ENABLE_TESTS TESTS += \ test/udev-test.pl @@ -3756,6 +3783,7 @@ TESTS += \ test/sysv-generator-test.py endif endif +endif manual_tests += \ test-libudev \ @@ -3775,8 +3803,10 @@ test_udev_LDADD = \ $(BLKID_LIBS) \ $(KMOD_LIBS) +if ENABLE_TESTS check_DATA += \ test/sys +endif # packed sysfs test tree test/sys: @@ -3999,7 +4029,8 @@ journalctl_SOURCES = \ src/journal/journalctl.c journalctl_LDADD = \ - libshared.la + libshared.la \ + libudev-core.la if HAVE_QRENCODE journalctl_SOURCES += \ @@ -4875,7 +4906,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 @@ -4975,7 +5006,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 @@ -5156,7 +5187,6 @@ EXTRA_DIST += \ # ------------------------------------------------------------------------------ if ENABLE_RESOLVED -if HAVE_GCRYPT systemd_resolved_SOURCES = \ src/resolve/resolved.c \ @@ -5170,6 +5200,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 \ @@ -5185,6 +5217,8 @@ systemd_resolved_SOURCES = \ src/resolve/resolved-dns-packet.c \ src/resolve/resolved-dns-query.h \ src/resolve/resolved-dns-query.c \ + src/resolve/resolved-dns-synthesize.h \ + src/resolve/resolved-dns-synthesize.c \ src/resolve/resolved-dns-transaction.h \ src/resolve/resolved-dns-transaction.c \ src/resolve/resolved-dns-scope.h \ @@ -5203,6 +5237,8 @@ systemd_resolved_SOURCES = \ src/resolve/resolved-dns-dnssec.c \ src/resolve/resolved-dns-trust-anchor.h \ src/resolve/resolved-dns-trust-anchor.c \ + src/resolve/resolved-etc-hosts.h \ + src/resolve/resolved-etc-hosts.c \ src/resolve/dns-type.c \ src/resolve/dns-type.h @@ -5256,14 +5292,16 @@ 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 += \ libnss_resolve.la -systemd_resolve_host_SOURCES = \ - src/resolve-host/resolve-host.c \ +systemd_resolve_SOURCES = \ + src/resolve/resolve-tool.c \ + src/resolve/resolved-dns-dnssec.c \ + src/resolve/resolved-dns-dnssec.h \ src/resolve/resolved-dns-packet.c \ src/resolve/resolved-dns-packet.h \ src/resolve/resolved-dns-rr.c \ @@ -5275,20 +5313,23 @@ systemd_resolve_host_SOURCES = \ src/resolve/dns-type.c \ src/resolve/dns-type.h -nodist_systemd_resolve_host_SOURCES = \ +nodist_systemd_resolve_SOURCES = \ src/resolve/dns_type-from-name.h \ src/resolve/dns_type-to-name.h -systemd_resolve_host_LDADD = \ +systemd_resolve_LDADD = \ libshared.la -rootlibexec_PROGRAMS += \ - systemd-resolve-host +bin_PROGRAMS += \ + systemd-resolve 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 \ @@ -5307,7 +5348,14 @@ test_dnssec_SOURCES = \ test_dnssec_LDADD = \ libshared.la -endif +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 gperf_txt_sources += \ @@ -5974,24 +6022,24 @@ src/%.c: src/%.gperf $(AM_V_at)$(MKDIR_P) $(dir $@) $(AM_V_GPERF)$(GPERF) < $< > $@ -src/%: src/%.m4 +src/%: src/%.m4 $(top_builddir)/config.status $(AM_V_at)$(MKDIR_P) $(dir $@) $(AM_V_M4)$(M4) -P $(M4_DEFINES) < $< > $@ -sysusers.d/%: sysusers.d/%.m4 +sysusers.d/%: sysusers.d/%.m4 $(top_builddir)/config.status $(AM_V_at)$(MKDIR_P) $(dir $@) $(AM_V_M4)$(M4) -P $(M4_DEFINES) < $< > $@ -tmpfiles.d/%: tmpfiles.d/%.m4 +tmpfiles.d/%: tmpfiles.d/%.m4 $(top_builddir)/config.status $(AM_V_at)$(MKDIR_P) $(dir $@) $(AM_V_M4)$(M4) -P $(M4_DEFINES) < $< > $@ -units/%: units/%.m4 +units/%: units/%.m4 $(top_builddir)/config.status $(AM_V_at)$(MKDIR_P) $(dir $@) $(AM_V_M4)$(M4) -P $(M4_DEFINES) -DFOR_SYSTEM=1 < $< > $@ -units/user/%: units/user/%.m4 +units/user/%: units/user/%.m4 $(top_builddir)/config.status $(AM_V_at)$(MKDIR_P) $(dir $@) $(AM_V_M4)$(M4) -P $(M4_DEFINES) -DFOR_USER=1 < $< > $@ @@ -6006,7 +6054,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" ?>' && \ @@ -6026,11 +6073,12 @@ XSLTPROC_FLAGS = \ --stringparam systemd.version $(VERSION) \ --path '$(builddir)/man:$(srcdir)/man' +XSLT = $(if $(XSLTPROC), $(XSLTPROC), xsltproc) XSLTPROC_PROCESS_MAN = \ - $(AM_V_XSLT)$(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) $(srcdir)/man/custom-man.xsl $< + $(AM_V_XSLT)$(XSLT) -o $@ $(XSLTPROC_FLAGS) $(srcdir)/man/custom-man.xsl $< XSLTPROC_PROCESS_HTML = \ - $(AM_V_XSLT)$(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) $(srcdir)/man/custom-html.xsl $< + $(AM_V_XSLT)$(XSLT) -o $@ $(XSLTPROC_FLAGS) $(srcdir)/man/custom-html.xsl $< man/%.1: man/%.xml man/custom-man.xsl man/custom-entities.ent $(XSLTPROC_PROCESS_MAN) @@ -6054,8 +6102,6 @@ define html-alias $(AM_V_LN)$(LN_S) -f $(notdir $<) $@ endef -endif - EXTRA_DIST += \ man/custom-html.xsl \ man/custom-man.xsl @@ -6281,14 +6327,14 @@ install-tree: all tree $(abs_srcdir)/install-tree # Let's run all tests of the test suite, but under valgrind. Let's -# exclude the one perl script we have in there +# exclude perl/python/shell scripts we have in there .PHONY: valgrind-tests valgrind-tests: $(TESTS) - $(AM_V_GEN)for f in $(filter-out %.pl, $^); do \ + $(AM_V_GEN)for f in $(filter-out %.pl %.py, $^); do \ if file $$f | grep -q shell; then \ echo -e "$${x}Skipping non-binary $$f"; else \ echo -e "$${x}Running $$f"; \ - libtool --mode=execute valgrind -q --leak-check=full --max-stackframe=5242880 --error-exitcode=55 $(builddir)/$$f ; fi; \ + $(LIBTOOL) --mode=execute valgrind -q --leak-check=full --max-stackframe=5242880 --error-exitcode=55 $(builddir)/$$f ; fi; \ x="\n\n"; \ done @@ -1,5 +1,18 @@ systemd System and Service Manager +CHANGES WITH 229: + + * Creation of the legacy /run/lock/lockdev/ directory was + dropped from tmpfiles.d/legacy.conf. Hardly any software uses + that any more, and better locking mechanisms like flock() have + been available for many years. If you still need this, you need to + create your own tmpfiles.d config file with: + d /run/lock/lockdev 0775 root lock - + + Contributions from: ... + + -- Berlin, 2016-MM-DD + CHANGES WITH 228: * A number of properties previously only settable in unit @@ -15,7 +15,6 @@ GITWEB: MAILING LIST: http://lists.freedesktop.org/mailman/listinfo/systemd-devel - http://lists.freedesktop.org/mailman/listinfo/systemd-commits IRC: #systemd on irc.freenode.org @@ -237,10 +236,6 @@ SYSV INIT.D SCRIPTS: needs to look like, and provide an implementation at the marked places. WARNINGS: - systemd will freeze execution during boot if /etc/mtab exists - but is not a symlink to /proc/mounts. Please ensure that - /etc/mtab is a proper symlink. - systemd will warn you during boot if /usr is on a different file system than /. While in systemd itself very little will break if /usr is on a separate partition, many of its @@ -5,5 +5,14 @@ ## Details - * General information about systemd can be found in the [systemd Wiki](http://www.freedesktop.org/wiki/Software/systemd) - * Information about build requirements are provided in the [README file](../master/README) +General information about systemd can be found in the [systemd Wiki](http://www.freedesktop.org/wiki/Software/systemd). + +Information about build requirements are provided in the [README file](../master/README). + +Consult our [NEWS file](../master/NEWS) for information about what's new in the most recent systemd versions. + +Please see our [Contribution Guidelines](../master/CONTRIBUTING.md) for more information about filing GitHub Issues and posting GitHub Pull Requests. + +When preparing patches for systemd, please follow our [Coding Style Guidelines](../master/CODING_STYLE). + +If you are looking for support, please contact our [mailing list](http://lists.freedesktop.org/mailman/listinfo/systemd-devel) or join our [IRC channel](irc://irc.freenode.org/%23systemd). @@ -33,6 +33,23 @@ Janitorial Clean-ups: Features: +* rework coredump tool to move actual processing into a socket activated service + +* cache sd_event_now() result from before the first iteration... + +* remove Capabilities=, after all AmbientCapabilities= and CapabilityBoundingSet= should be enough. + +* support for the new copy_file_range() syscall + +* add systemctl stop --job-mode=triggering that follows TRIGGERED_BY deps and adds them to the same transaction + +* coredump logic should use prlimit() to query RLIMIT_CORE of the dumpee and honour it + +* Add a MaxRuntimeSec= setting for service units (or units in general) to terminate units after they ran for a certain + amount of time + +* Maybe add a way how users can "pin" units into memory, so that they are not subject to automatic GC? + * PID1: find a way how we can reload unit file configuration for specific units only, without reloading the whole of systemd @@ -58,8 +75,6 @@ Features: * consider throwing a warning if a service declares it wants to be "Before=" a .device unit. -* "systemctl edit" should know a mode to create a new unit file - * there's probably something wrong with having user mounts below /sys, as we have for debugfs. for exmaple, src/core/mount.c handles mounts prefixed with /sys generally special. @@ -67,8 +82,6 @@ Features: * man: document that unless you use StandardError=null the shell >/dev/stderr won't work in shell scripts in services -* "systemctl daemon-reload" should result in /etc/systemd/system.conf being reloaded by systemd - * install: include generator dirs in unit file search paths * rework C11 utf8.[ch] to use char32_t instead of uint32_t when referring @@ -162,9 +175,9 @@ Features: - use equvalent of cat() to insert existing config as a comment, prepended with #. Upon editor exit, lines with one # are removed, lines with two # are left with one #, etc. -* exponential backoff in timesyncd and resolved when we cannot reach a server +* exponential backoff in timesyncd when we cannot reach a server -* timesyncd + resolved: add ugly bus calls to set NTP and DNS servers per-interface, for usage by NM +* timesyncd: add ugly bus calls to set NTP servers per-interface, for usage by NM * extract_many_words() should probably be used by a lot of code that currently uses FOREACH_WORD and friends. For example, most conf @@ -179,13 +192,7 @@ Features: (throughout the codebase, not only PID1) * resolved: - - put networkd events and rtnl events at a higher priority, so that - we always process them before we process client requests - - DNSSEC - - add display of private key types (http://tools.ietf.org/html/rfc4034#appendix-A.1.1)? - - synthesize negative cache entries from NSEC/NSEC3 and drop explicit negative caching of authenticated answers - mDNS/DNS-SD - - mDNS RR resolving - service registration - service/domain/types browsing - avahi compat @@ -193,7 +200,8 @@ Features: - resolved should optionally register additional per-interface LLMNR names, so that for the container case we can establish the same name (maybe "host") for referencing the server, everywhere. - - add API so NM can push DNS server info into resolved + - enable DNSSEC by default + - allow clients to request DNSSEC for a single lookup even if DNSSEC is off (?) * refcounting in sd-resolve is borked @@ -331,10 +339,6 @@ Features: - generate a failure of a default event loop is executed out-of-thread - maybe add support for inotify events -* in the final killing spree, detect processes from the root directory, and - complain loudly if they have argv[0][0] == '@' set. - https://bugzilla.redhat.com/show_bug.cgi?id=961044 - * investigate endianness issues of UUID vs. GUID * dbus: when a unit failed to load (i.e. is in UNIT_ERROR state), we @@ -483,10 +487,6 @@ Features: - journal-or-kmsg is currently broken? See reverted commit 4a01181e460686d8b4a543b1dfa7f77c9e3c5ab8. - man: document that corrupted journal files is nothing to act on - - systemd-journal-upload (or a new, related tool): allow pushing out - journal messages onto the network in BSD syslog protocol, - continuously. Default to some link-local IP mcast group, to make this - useful as a one-stop debugging tool. - rework journald sigbus stuff to use mutex - Set RLIMIT_NPROC for systemd-journal-xyz, and all other of our services that run under their own user ids, and use User= (but only diff --git a/catalog/systemd.catalog b/catalog/systemd.catalog index 4488c835a8..1025590681 100644 --- a/catalog/systemd.catalog +++ b/catalog/systemd.catalog @@ -1,3 +1,4 @@ +# -*- fill-column: 79; indent-tabs-mode: nil -*- # This file is part of systemd. # # Copyright 2012 Lennart Poettering @@ -38,6 +39,21 @@ Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel The system journal process has shut down and closed all currently active journal files. +-- ec387f577b844b8fa948f33cad9a75e6 +Subject: Disk space used by the journal +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@JOURNAL_NAME@ (@JOURNAL_PATH@) is currently using @CURRENT_USE_PRETTY@. +Maximum allowed usage is set to @MAX_USE_PRETTY@. +Leaving at least @DISK_KEEP_FREE_PRETTY@ free (of currently available @DISK_AVAILABLE_PRETTY@ of disk space). +Enforced usage limit is thus @LIMIT_PRETTY@, of which @AVAILABLE_PRETTY@ are still available. + +The limits controlling how much disk space is used by the journal may +be configured with SystemMaxUse=, SystemKeepFree=, SystemMaxFileSize=, +RuntimeMaxUse=, RuntimeKeepFree=, RuntimeMaxFileSize= settings in +/etc/systemd/journald.conf. See journald.conf(5) for details. + -- a596d6fe7bfa4994828e72309e95d61e Subject: Messages from a service have been suppressed Defined-By: systemd @@ -278,3 +294,42 @@ Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel The virtual machine @NAME@ with its leader PID @LEADER@ has been shut down. + +-- 36db2dfa5a9045e1bd4af5f93e1cf057 +Subject: DNSSEC mode has been turned off, as server doesn't support it +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) resolved.conf(5) + +The resolver service (systemd-resolved.service) has detected that the +configured DNS server does not support DNSSEC, and DNSSEC validation has been +turned off as result. + +This event will take place if DNSSEC=allow-downgrade is configured in +resolved.conf and the configured DNS server is incompatible with DNSSEC. Note +that using this mode permits DNSSEC downgrade attacks, as an attacker might be +able turn off DNSSEC validation on the system by inserting DNS replies in the +communication channel that result in a downgrade like this. + +This event might be indication that the DNS server is indeed incompatible with +DNSSEC or that an attacker has successfully managed to stage such a downgrade +attack. + +-- 1675d7f172174098b1108bf8c7dc8f5d +Subject: DNSSEC validation failed +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +A DNS query or resource record set failed DNSSEC validation. This is usually +indication that the communication channel used was tampered with. + +-- 4d4408cfd0d144859184d1e65d7c8a65 +Subject: A DNSSEC trust anchor has been revoked +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +A DNSSEC trust anchor has been revoked. A new trust anchor has to be +configured, or the operating system needs to be updated, to provide an updated +DNSSEC trust anchor. diff --git a/catalog/systemd.hu.catalog b/catalog/systemd.hu.catalog new file mode 100644 index 0000000000..30d76916cc --- /dev/null +++ b/catalog/systemd.hu.catalog @@ -0,0 +1,262 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2016 Gabor Kelemen +# +# 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/>. + +# Message catalog for systemd's own messages + +# The catalog format is documented on +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: A napló elindult +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszernapló folyamat elindult, megnyitotta Ãrásra a naplófájlokat, +és most készen áll kérések feldolgozására. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: A napló leállt +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszernapló folyamat leállt, és bezárt minden jelenleg aktÃv naplófájlt. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Egy szolgáltatás üzenetei elnémÃtva +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Egy szolgáltatás túl sok üzenetet naplózott adott idÅ‘ alatt. A +szolgáltatástól származó üzenetek eldobásra kerültek. + +Ne feledje, hogy csak a kérdéses szolgáltatás üzenetei kerültek eldobásra, + más szolgáltatások üzeneteit ez nem befolyásolja. + +Az üzenetek eldobását vezérlÅ‘ korlátok az /etc/systemd/journald.conf +RateLimitInterval= és RateLimitBurst= beállÃtásaival adhatók meg. +Részletekért lásd a journald.conf(5) man oldalt. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Naplóüzenetek vesztek el +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Kernelüzenetek vesztek el, mert a naplózó rendszer nem tudta elég gyorsan +feldolgozni azokat. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Egy folyamat összeomlott: @COREDUMP_PID@ (@COREDUMP_COMM@) +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Ez a folyamat: @COREDUMP_PID@ (@COREDUMP_COMM@) összeomlott, és core fájlt + Ãrt ki. + +Ez általában programozási hibát jelez az összeomló programban, és +a szállÃtója felé kell bejelenteni. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: Új munkamenet (@SESSION_ID@) létrehozva, felhasználója: @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Létrejött egy új munkamenet @SESSION_ID@ azonosÃtóval ezen felhasználóhoz: +@USER_ID@. + +A munkamenet vezetÅ‘ folyamata: @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Munkamenet (@SESSION_ID@) befejezve +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +A következÅ‘ azonosÃtójú munkamenet befejezÅ‘dött: @SESSION_ID@. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: ElérhetÅ‘ egy új munkaállomás: @SEAT_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +BeállÃtásra kerül és használható egy új munkaállomás: @SEAT_ID@. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: A munkaállomás eltávolÃtva: @SEAT_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +A munkaállomás el lett távolÃtva, és már nem érhetÅ‘ el: @SEAT_ID@ + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: IdÅ‘módosÃtás +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszeróra beállÃtva @REALTIME@ ezredmásodpercre 1970. január 1. után. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: IdÅ‘zóna-módosÃtás erre: @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszer idÅ‘zónája módosÃtva lett erre: @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: A rendszer indÃtása kész +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszerindÃtáskor szükséges indÃtáshoz sorba állÃtott összes +rendszerszolgáltatás elindult. Ne feledje, hogy ez nem jelenti, hogy a +gép üresjáratban van, mivel egyes szolgáltatások még az indÃtás +befejezésével lehetnek elfoglalva. + +A kernel indÃtása @KERNEL_USEC@ ezredmásodpercet igényelt. + +A kiinduló RAM lemez indÃtása @INITRD_USEC@ ezredmásodpercet igényelt. + +A felhasználói programok indÃtása @USERSPACE_USEC@ ezredmásodpercet igényelt. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: A rendszer „@SLEEP@†alvási állapotba lépett +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszer belépett ebbe az alvási állapotba: @SLEEP@. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: A rendszer „@SLEEP@†alvási állapotból kilépett +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszer kilépett ebbÅ‘l az alvási állapotból: @SLEEP@. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Rendszer leállÃtása kezdeményezve +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A systemd leállÃtása kezdeményezve. A leállÃtás megkezdÅ‘dött, minden +rendszerszolgáltatás befejezÅ‘dik, minden fájlrendszer leválasztásra kerül. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: A(z) @UNIT@ egység indÃtása megkezdÅ‘dött +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység megkezdte az indulást. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: A(z) @UNIT@ egység befejezte az indulást +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység befejezte az indulást + +Az indÃtás eredménye: @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: A(z) @UNIT@ egység megkezdte a leállást +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység megkezdte a leállást. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: A(z) @UNIT@ egység befejezte a leállást +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység befejezte a leállást. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: A(z) @UNIT@ egység hibát jelzett +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység hibát jelzett. + +Az eredmény: @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: A(z) @UNIT@ egység megkezdte a beállÃtásainak újratöltését +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység megkezdte a beállÃtásainak újratöltését. + +-- 7b05ebc668384222baa8881179cfda54 +Subject: A(z) @UNIT@ egység befejezte a beállÃtásainak újratöltését +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység befejezte a beállÃtásainak újratöltését. + +Az eredmény: @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: A folyamat végrehajtása sikertelen: @EXECUTABLE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A folyamat végrehajtása sikertelen volt, és hibát jelzett: @EXECUTABLE@. + +A folyamat által visszaadott hibaszám: @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: Legalább egy üzenet nem továbbÃtható a rendszernaplónak +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Legalább egy üzenet nem volt továbbÃtható a journald-vel párhuzamosan futó +syslog szolgáltatásnak. Ez általában azt jelenti, hogy a syslog +megvalósÃtás nem volt képes lépést tartani a sorba állÃtott +üzenetek sebességével. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: A csatolási pont nem üres +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A csatolási pontként megadott @WHERE@ könyvtár (második mezÅ‘ az /etc/fstab +fájlban, vagy a Where= sor a systemd egységfájlban) nem üres. Ez nem +akadályozza meg a csatolást, de a könyvtárban már meglévÅ‘ fájlok +elérhetetlenné válnak. A fájlok láthatóvá tételéhez csatolja +az azokat tartalmazó fájlrendszert egy másodlagos helyre. + +-- 24d8d4452573402496068381a6312df2 +Subject: Egy virtuális gép vagy konténer elindult +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @NAME@ nevű virtuális gép (vezetÅ‘ PID: @LEADER@) elindult, és +használatra kész. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Egy virtuális gép vagy konténer befejezÅ‘dött +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @NAME@ nevű virtuális gép (vezetÅ‘ PID: @LEADER@) leállt. diff --git a/catalog/systemd.ru.catalog b/catalog/systemd.ru.catalog index 03eea04c9f..eedbb8aa9c 100644 --- a/catalog/systemd.ru.catalog +++ b/catalog/systemd.ru.catalog @@ -1,7 +1,7 @@ # This file is part of systemd. # # Copyright 2012 Lennart Poettering -# Copyright 2013 Sergey Ptashnick +# Copyright 2013-2016 Sergey Ptashnick # # 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 @@ -43,6 +43,25 @@ Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel ПроцеÑÑ, отвечающий за журналирование ÑиÑтемных Ñобытий, завершил работу и закрыл вÑе Ñвои файлы. +# Subject: Disk space used by the journal +-- ec387f577b844b8fa948f33cad9a75e6 +Subject: МеÑто на диÑке, занÑтое журналом +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@JOURNAL_NAME@ (@JOURNAL_PATH@) ÑÐµÐ¹Ñ‡Ð°Ñ Ð·Ð°Ð½Ð¸Ð¼Ð°ÐµÑ‚ @CURRENT_USE_PRETTY@. +МакÑимальный разрешенный размер ÑоÑтавлÑет @MAX_USE_PRETTY@. +ОÑтавлÑем Ñвободными как минимум @DISK_KEEP_FREE_PRETTY@ (ÑÐµÐ¹Ñ‡Ð°Ñ Ð½Ð° диÑке +Ñвободно @DISK_AVAILABLE_PRETTY@). +Таким образом, предел иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ ÑоÑтавлÑет @LIMIT_PRETTY@, из которых +@AVAILABLE_PRETTY@ пока Ñвободно. + +ÐžÐ³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð½Ð° размер журнала наÑтраиваютÑÑ Ð¿Ñ€Ð¸ помощи параметров +SystemMaxUse=, SystemKeepFree=, SystemMaxFileSize=, RuntimeMaxUse=, +RuntimeKeepFree=, RuntimeMaxFileSize= в файле /etc/systemd/journald.conf. +Более подробные ÑÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ð²Ñ‹ можете получить на Ñправочной Ñтранице +journald.conf(5). + # Subject: Messages from a service have been suppressed -- a596d6fe7bfa4994828e72309e95d61e Subject: ЧаÑÑ‚ÑŒ Ñообщений от Ñлужбы пропущена @@ -292,3 +311,44 @@ Defined-By: systemd Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel Ð’Ð¸Ñ€Ñ‚ÑƒÐ°Ð»ÑŒÐ½Ð°Ñ Ð¼Ð°ÑˆÐ¸Ð½Ð° @NAME@ (идентификатор главного процеÑÑа: @LEADER@) выключена. + +# Subject: DNSSEC mode has been turned off, as server doesn't support it +-- 36db2dfa5a9045e1bd4af5f93e1cf057 +Subject: Механизм DNSSEC был отключен, так как DNS-Ñервер его не поддерживает +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) resolved.conf(5) + +Служба Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð¸Ð¼ÐµÐ½ хоÑтов (systemd-resolved.service) определила, что +указанный в наÑтойках DNS-Ñервер не поддерживает технологию DNSSEC, и +автоматичеÑки отключила DNSSEC-проверки. + +Данное Ñобытие возникает, еÑли в файле resolved.conf указан параметр +DNSSEC=allow-downgrade, и вышеÑтоÑщий DNS-Ñервер не поддерживает DNSSEC. +Обратите внимание, что режим allow-downgrade допуÑкает возможноÑÑ‚ÑŒ атаки +"DNSSEC downgrade", в ходе которой атакующий хакер блокирует проверки DNSSEC +путем отправки ложных Ñообщений от имени DNS-Ñервера. + +Возникновение данного ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð¼Ð¾Ð¶ÐµÑ‚ ÑвидетельÑтвовать как о том, что ваш +DNS-Ñервер не поддерживает DNSSEC, так и о том, что некий хакер уÑпешно провел +против Ð²Ð°Ñ Ð°Ñ‚Ð°ÐºÑƒ, направленную на блокировку DNSSEC-проверок. + +# Subject: DNSSEC validation failed +-- 1675d7f172174098b1108bf8c7dc8f5d +Subject: Проверка DNSSEC провалена +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +DNS-Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð¸Ð»Ð¸ Ð¾Ñ‚Ð´ÐµÐ»ÑŒÐ½Ð°Ñ Ñ€ÐµÑурÑÐ½Ð°Ñ Ð·Ð°Ð¿Ð¸ÑÑŒ не прошла проверку DNSSEC. +Как правило, Ñто ÑвидетельÑтвует о поÑтороннем вмешательÑтве в канал ÑвÑзи. + +# Subject: A DNSSEC trust anchor has been revoked +-- 4d4408cfd0d144859184d1e65d7c8a65 +Subject: Открытый ключ DNSSEC был отозван +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +Открытый ключ (trust ahcnor) DNSSEC был отозван. Ðеобходимо наÑтроить новый +открытый ключ, либо обновить ÑиÑтему, чтобы получить обновленный открытый ключ. diff --git a/coccinelle/xsprintf.cocci b/coccinelle/xsprintf.cocci new file mode 100644 index 0000000000..401216ad72 --- /dev/null +++ b/coccinelle/xsprintf.cocci @@ -0,0 +1,6 @@ +@@ +expression e, fmt; +expression list vaargs; +@@ +- snprintf(e, sizeof(e), fmt, vaargs); ++ xsprintf(e, fmt, vaargs); diff --git a/configure.ac b/configure.ac index c2222c376c..1517b4e197 100644 --- a/configure.ac +++ b/configure.ac @@ -297,7 +297,8 @@ AC_SUBST(CAP_LIBS) AC_CHECK_FUNCS([memfd_create]) AC_CHECK_FUNCS([__secure_getenv secure_getenv]) -AC_CHECK_DECLS([gettid, pivot_root, name_to_handle_at, setns, getrandom, renameat2, kcmp, keyctl, key_serial_t, LO_FLAGS_PARTSCAN], +AC_CHECK_DECLS([gettid, pivot_root, name_to_handle_at, setns, getrandom, renameat2, + kcmp, keyctl, key_serial_t, char16_t, char32_t, LO_FLAGS_PARTSCAN], [], [], [[ #include <sys/types.h> #include <unistd.h> @@ -668,11 +669,14 @@ AC_ARG_ENABLE([smack], AS_HELP_STRING([--disable-smack],[Disable optional SMACK esac], [have_smack=auto]) -if test "x${have_smack}" = xauto; then +if test "x${have_smack}" != xno; then + AC_DEFINE(HAVE_SMACK, 1, [Define if SMACK is available]) M4_DEFINES="$M4_DEFINES -DHAVE_SMACK" have_smack=yes fi +AM_CONDITIONAL([HAVE_SMACK], [test "x$have_smack" = "xyes"]) + have_smack_run_label=no AC_ARG_WITH(smack-run-label, AS_HELP_STRING([--with-smack-run-label=STRING], @@ -690,12 +694,6 @@ AS_HELP_STRING([--with-smack-default-process-label=STRING], [AC_DEFINE_UNQUOTED(SMACK_DEFAULT_PROCESS_LABEL, ["$withval"], [Default SMACK label for executed processes])], []) -if test "x${have_smack}" = xyes ; then - AC_DEFINE(HAVE_SMACK, 1, [Define if SMACK is available]) -fi - -AM_CONDITIONAL([HAVE_SMACK], [test "x$have_smack" = "xyes"]) - # ------------------------------------------------------------------------------ AC_ARG_ENABLE([gcrypt], AS_HELP_STRING([--disable-gcrypt],[Disable optional GCRYPT support]), @@ -1304,9 +1302,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/20-bluetooth-vendor-product.hwdb b/hwdb/20-bluetooth-vendor-product.hwdb index a825e744e1..516abad246 100644 --- a/hwdb/20-bluetooth-vendor-product.hwdb +++ b/hwdb/20-bluetooth-vendor-product.hwdb @@ -1654,7 +1654,7 @@ bluetooth:v0224* ID_VENDOR_FROM_DATABASE=Comarch SA bluetooth:v0225* - ID_VENDOR_FROM_DATABASE=Nestl Nespresso S.A. + ID_VENDOR_FROM_DATABASE=Nestlé Nespresso S.A. bluetooth:v0226* ID_VENDOR_FROM_DATABASE=Merlinia A/S @@ -1780,7 +1780,7 @@ bluetooth:v024E* ID_VENDOR_FROM_DATABASE=Microtronics Engineering GmbH bluetooth:v024F* - ID_VENDOR_FROM_DATABASE=Schneider Schreibgerte GmbH + ID_VENDOR_FROM_DATABASE=Schneider Schreibgeräte GmbH bluetooth:v0250* ID_VENDOR_FROM_DATABASE=Sapphire Circuits LLC @@ -1897,7 +1897,7 @@ bluetooth:v0275* ID_VENDOR_FROM_DATABASE=Geotab bluetooth:v0276* - ID_VENDOR_FROM_DATABASE=E.G.O. Elektro-Gertebau GmbH + ID_VENDOR_FROM_DATABASE=E.G.O. Elektro-Gerätebau GmbH bluetooth:v0277* ID_VENDOR_FROM_DATABASE=bewhere inc @@ -2066,3 +2066,168 @@ bluetooth:v02AD* bluetooth:v02AE* ID_VENDOR_FROM_DATABASE=WeatherFlow, Inc. + +bluetooth:v02AF* + ID_VENDOR_FROM_DATABASE=Technicolor USA Inc. + +bluetooth:v02B0* + ID_VENDOR_FROM_DATABASE=Bestechnic(Shanghai),Ltd + +bluetooth:v02B1* + ID_VENDOR_FROM_DATABASE=Raden Inc + +bluetooth:v02B2* + ID_VENDOR_FROM_DATABASE=JouZen Oy + +bluetooth:v02B3* + ID_VENDOR_FROM_DATABASE=CLABER S.P.A. + +bluetooth:v02B4* + ID_VENDOR_FROM_DATABASE=Hyginex, Inc. + +bluetooth:v02B5* + ID_VENDOR_FROM_DATABASE=HANSHIN ELECTRIC RAILWAY CO.,LTD. + +bluetooth:v02B6* + ID_VENDOR_FROM_DATABASE=Schneider Electric + +bluetooth:v02B7* + ID_VENDOR_FROM_DATABASE=Oort Technologies LLC + +bluetooth:v02B8* + ID_VENDOR_FROM_DATABASE=Chrono Therapeutics + +bluetooth:v02B9* + ID_VENDOR_FROM_DATABASE=Rinnai Corporation + +bluetooth:v02BA* + ID_VENDOR_FROM_DATABASE=Swissprime Technologies AG + +bluetooth:v02BB* + ID_VENDOR_FROM_DATABASE=Koha.,Co.Ltd + +bluetooth:v02BC* + ID_VENDOR_FROM_DATABASE=Genevac Ltd + +bluetooth:v02BD* + ID_VENDOR_FROM_DATABASE=Chemtronics + +bluetooth:v02BE* + ID_VENDOR_FROM_DATABASE=Seguro Technology Sp. z o.o. + +bluetooth:v02BF* + ID_VENDOR_FROM_DATABASE=Redbird Flight Simulations + +bluetooth:v02C0* + ID_VENDOR_FROM_DATABASE=Dash Robotics + +bluetooth:v02C1* + ID_VENDOR_FROM_DATABASE=LINE Corporation + +bluetooth:v02C2* + ID_VENDOR_FROM_DATABASE=Guillemot Corporation + +bluetooth:v02C3* + ID_VENDOR_FROM_DATABASE=Techtronic Power Tools Technology Limited + +bluetooth:v02C4* + ID_VENDOR_FROM_DATABASE=Wilson Sporting Goods + +bluetooth:v02C5* + ID_VENDOR_FROM_DATABASE=Lenovo (Singapore) Pte Ltd. ( è”æƒ³ï¼ˆæ–°åŠ å¡ï¼‰ ) + +bluetooth:v02C6* + ID_VENDOR_FROM_DATABASE=Ayatan Sensors + +bluetooth:v02C7* + ID_VENDOR_FROM_DATABASE=Electronics Tomorrow Limited + +bluetooth:v02C8* + ID_VENDOR_FROM_DATABASE=VASCO Data Security International, Inc. + +bluetooth:v02C9* + ID_VENDOR_FROM_DATABASE=PayRange Inc. + +bluetooth:v02CA* + ID_VENDOR_FROM_DATABASE=ABOV Semiconductor + +bluetooth:v02CB* + ID_VENDOR_FROM_DATABASE=AINA-Wireless Inc. + +bluetooth:v02CC* + ID_VENDOR_FROM_DATABASE=Eijkelkamp Soil & Water + +bluetooth:v02CD* + ID_VENDOR_FROM_DATABASE=BMA ergonomics b.v. + +bluetooth:v02CE* + ID_VENDOR_FROM_DATABASE=Teva Branded Pharmaceutical Products R&D, Inc. + +bluetooth:v02CF* + ID_VENDOR_FROM_DATABASE=Anima + +bluetooth:v02D0* + ID_VENDOR_FROM_DATABASE=3M + +bluetooth:v02D1* + ID_VENDOR_FROM_DATABASE=Empatica Srl + +bluetooth:v02D2* + ID_VENDOR_FROM_DATABASE=Afero, Inc. + +bluetooth:v02D3* + ID_VENDOR_FROM_DATABASE=Powercast Corporation + +bluetooth:v02D4* + ID_VENDOR_FROM_DATABASE=Secuyou ApS + +bluetooth:v02D5* + ID_VENDOR_FROM_DATABASE=OMRON Corporation + +bluetooth:v02D6* + ID_VENDOR_FROM_DATABASE=Send Solutions + +bluetooth:v02D7* + ID_VENDOR_FROM_DATABASE=NIPPON SYSTEMWARE CO.,LTD. + +bluetooth:v02D8* + ID_VENDOR_FROM_DATABASE=Neosfar + +bluetooth:v02D9* + ID_VENDOR_FROM_DATABASE=Fliegl Agrartechnik GmbH + +bluetooth:v02DA* + ID_VENDOR_FROM_DATABASE=Gilvader + +bluetooth:v02DB* + ID_VENDOR_FROM_DATABASE=Digi International Inc (R) + +bluetooth:v02DC* + ID_VENDOR_FROM_DATABASE=DeWalch Technologies, Inc. + +bluetooth:v02DD* + ID_VENDOR_FROM_DATABASE=Flint Rehabilitation Devices, LLC + +bluetooth:v02DE* + ID_VENDOR_FROM_DATABASE=Samsung SDS Co., Ltd. + +bluetooth:v02DF* + ID_VENDOR_FROM_DATABASE=Blur Product Development + +bluetooth:v02E0* + ID_VENDOR_FROM_DATABASE=University of Michigan + +bluetooth:v02E1* + ID_VENDOR_FROM_DATABASE=Victron Energy BV + +bluetooth:v02E2* + ID_VENDOR_FROM_DATABASE=NTT docomo + +bluetooth:v02E3* + ID_VENDOR_FROM_DATABASE=Carmanah Technologies Corp. + +bluetooth:v02E4* + ID_VENDOR_FROM_DATABASE=Bytestorm Ltd. + +bluetooth:v02E5* + ID_VENDOR_FROM_DATABASE=Espressif Incorporated ( ä¹é‘«ä¿¡æ¯ç§‘技(上海)有é™å…¬å¸ ) diff --git a/hwdb/60-evdev.hwdb b/hwdb/60-evdev.hwdb index f7a82ee26c..d060d81f61 100644 --- a/hwdb/60-evdev.hwdb +++ b/hwdb/60-evdev.hwdb @@ -25,8 +25,7 @@ # https://github.com/systemd/systemd # or create a bug report on https://github.com/systemd/systemd/issues and # include your new rules, a description of the device, and the output of -# udevadm info /dev/input/eventXX -# (or /dev/input/event*). +# udevadm info /dev/input/eventXX. # # Allowed properties are: # EVDEV_ABS_<axis>=<min>:<max>:<res>:<fuzz>:<flat> @@ -115,6 +114,13 @@ evdev:name:AlpsPS/2 ALPS DualPoint TouchPad:dmi:bvn*:bvr*:bd*:svnDellInc.:pnInsp EVDEV_ABS_35=25:2000:22 EVDEV_ABS_36=0:1351:28 +# Dell Latitude E6220 +evdev:name:AlpsPS/2 ALPS DualPoint TouchPad:dmi:bvn*:bvr*:bd*:svnDellInc.:pnLatitudeE6220* + EVDEV_ABS_00=76:1815:22 + EVDEV_ABS_01=131:1330:30 + EVDEV_ABS_35=76:1815:22 + EVDEV_ABS_36=131:1330:30 + ######################################### # Google ######################################### @@ -127,6 +133,17 @@ evdev:name:Atmel maXTouch Touch*:dmi:bvn*:bvr*:bd*:svnGOOGLE:pnSamus* EVDEV_ABS_36=::10 ######################################### +# HP +######################################### + +# HP Pavilion dm4 +evdev:name:SynPS/2 Synaptics TouchPad*:dmi:*svnHewlett-Packard:pnHPPaviliondm4* + EVDEV_ABS_00=1360:5563:47 + EVDEV_ABS_01=1269:4618:61 + EVDEV_ABS_35=1360:5563:47 + EVDEV_ABS_36=1269:4618:61 + +######################################### # Lenovo ######################################### @@ -141,3 +158,14 @@ evdev:name:SynPS/2 Synaptics TouchPad:dmi:*svnLENOVO*:pn*ThinkPad*T510* EVDEV_ABS_01=841:5330:100 EVDEV_ABS_35=778:6239:72 EVDEV_ABS_36=841:5330:100 + +######################################### +# Samsung +######################################### + +# Samsung 305V4 +evdev:name:ETPS/2 Elantech Touchpad:dmi:*svnSAMSUNGELECTRONICSCO.,LTD.:pn305V4A/305V5A* + EVDEV_ABS_00=0:2480:28 + EVDEV_ABS_01=0:1116:24 + EVDEV_ABS_35=0:2480:28 + EVDEV_ABS_36=0:1116:24 diff --git a/hwdb/60-keyboard.hwdb b/hwdb/60-keyboard.hwdb index 94906abcbf..01213b6069 100644 --- a/hwdb/60-keyboard.hwdb +++ b/hwdb/60-keyboard.hwdb @@ -56,8 +56,7 @@ # https://github.com/systemd/systemd # or create a bug report on https://github.com/systemd/systemd/issues and # include your new rules, a description of the device, and the output of -# udevadm info /dev/input/eventXX -# (or /dev/input/event*). +# udevadm info /dev/input/eventXX. ########################################## # Acer @@ -499,6 +498,13 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svnHewlett-Packard*:pnHPProBook450G0:pvr* evdev:atkbd:dmi:bvn*:bvr*:bd*:svnHewlett-Packard:pnHPProBook6555b:* KEYBOARD_KEY_b2=www # Earth +# HP ProBook 440 G3 +evdev:atkbd:dmi:bvn*:bvr*:svnHP*:pnHP*ProBook*440*G3* + KEYBOARD_KEY_92=brightnessdown + KEYBOARD_KEY_97=brightnessup + KEYBOARD_KEY_ee=switchvideomode + KEYBOARD_KEY_81=f20 # micmute + ########################################################### # IBM ########################################################### @@ -652,6 +658,11 @@ evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*IdeaPad*Z370*:pvr* evdev:atkbd:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn*Lenovo*V480*:pvr* KEYBOARD_KEY_f1=f21 +# Lenovo Thinkcentre M800z AIO machine +# key_scancode 00 is KEY_MICMUTE +keyboard:name:Microphone Mute Button:dmi:bvn*:bvr*:bd*:svnLENOVO*:pn* + KEYBOARD_KEY_00=f20 + # enhanced USB keyboard evdev:input:b0003v04B3p301B* KEYBOARD_KEY_90001=prog1 # ThinkVantage diff --git a/hwdb/70-mouse.hwdb b/hwdb/70-mouse.hwdb index 2383d586a3..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 @@ -309,6 +308,8 @@ mouse:usb:v046dpc046:name:Logitech USB Optical Mouse: mouse:usb:v046dpc05a:name:Logitech USB Optical Mouse: # Logitech USB Laser Mouse M-U0011-O rebranded as "terra Laser" mouse:usb:v046dpc065:name:Logitech USB Laser Mouse: +# Logitech USB Laser Mouse M-U0007 [M500] +mouse:usb:v046dpc069:name:Logitech USB Laser Mouse: # Logitech V500 Cordless Notebook Mouse mouse:usb:v046dpc510:name:Logitech USB Receiver: # Logitech M560 Wireless Mouse @@ -340,8 +341,6 @@ mouse:usb:v046dp1024:name:Logitech M310: # Logitech USB Laser Mouse M-UAS144 [LS1 Laser Mouse] mouse:usb:v046dpc062:name:Logitech USB Laser Mouse: -# Logitech USB Laser Mouse M-U0007 -mouse:usb:v046dpc069:name:Logitech USB Laser Mouse: MOUSE_DPI=1200@125 # Logitech T620 (or, the soap) 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/busctl.xml b/man/busctl.xml index d8c1085021..26d778d4dd 100644 --- a/man/busctl.xml +++ b/man/busctl.xml @@ -448,7 +448,7 @@ ARRAY "s" { <example> <title>Invoking a Method</title> - <para>The following command invokes a the + <para>The following command invokes the <literal>StartUnit</literal> method on the <literal>org.freedesktop.systemd1.Manager</literal> interface of the diff --git a/man/dnssec-trust-anchors.d.xml b/man/dnssec-trust-anchors.d.xml new file mode 100644 index 0000000000..4bdc167f79 --- /dev/null +++ b/man/dnssec-trust-anchors.d.xml @@ -0,0 +1,200 @@ +<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<!-- + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +--> + +<refentry id="dnssec-trust-anchors.d" conditional='ENABLE_RESOLVED' + xmlns:xi="http://www.w3.org/2001/XInclude"> + <refentryinfo> + <title>dnssec-trust-anchors.d</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Developer</contrib> + <firstname>Lennart</firstname> + <surname>Poettering</surname> + <email>lennart@poettering.net</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>dnssec-trust-anchors.d</refentrytitle> + <manvolnum>5</manvolnum> + </refmeta> + + <refnamediv> + <refname>dnssec-trust-anchors.d</refname> + <refname>systemd.positive</refname> + <refname>systemd.negative</refname> + <refpurpose>DNSSEC trust anchor configuration files</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <para><filename>/etc/dnssec-trust-anchors.d/*.positive</filename></para> + <para><filename>/run/dnssec-trust-anchors.d/*.positive</filename></para> + <para><filename>/usr/lib/dnssec-trust-anchors.d/*.positive</filename></para> + <para><filename>/etc/dnssec-trust-anchors.d/*.negative</filename></para> + <para><filename>/run/dnssec-trust-anchors.d/*.negative</filename></para> + <para><filename>/usr/lib/dnssec-trust-anchors.d/*.negative</filename></para> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The DNSSEC trust anchor configuration files define positive + and negative trust anchors + <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> + bases DNSSEC integrity proofs on.</para> + </refsect1> + + <refsect1> + <title>Positive Trust Anchors</title> + + <para>Positive trust anchor configuration files contain DNSKEY and + DS resource record definitions to use as base for DNSSEC integrity + proofs. See <ulink + url="https://tools.ietf.org/html/rfc4035#section-4.4">RFC 4035, + Section 4.4</ulink> for more information about DNSSEC trust + anchors.</para> + + <para>Positive trust anchors are read from files with the suffix + <filename>.positive</filename> located in + <filename>/etc/dnssec-trust-anchors.d/</filename>, + <filename>/run/dnssec-trust-anchors.d/</filename> and + <filename>/usr/lib/dnssec-trust-anchors.d/</filename>. These + directories are searched in the specified order, and a trust + anchor file of the same name in an earlier path overrides a trust + anchor files in a later path. To disable a trust anchor file + shipped in <filename>/usr/lib/dnssec-trust-anchors.d/</filename> + it is sufficient to provide an identically-named file in + <filename>/etc/dnssec-trust-anchors.d/</filename> or + <filename>/run/dnssec-trust-anchors.d/</filename> that is either + empty or a symlink to <filename>/dev/null</filename> ("masked").</para> + + <para>Positive trust anchor files are simple text files resembling + DNS zone files, as documented in <ulink + url="https://tools.ietf.org/html/rfc1035#section-5">RFC 1035, Section + 5</ulink>. One DS or DNSKEY resource record may be listed per + line. Empty lines and lines starting with a semicolon + (<literal>;</literal>) are ignored and considered comments. A DS + resource record is specified like in the following example:</para> + + <programlisting>. IN DS 19036 8 2 49aac11d7b6f6446702e54a1607371607a1a41855200fd2ce1cdde32f24e8fb5</programlisting> + + <para>The first word specifies the domain, use + <literal>.</literal> for the root domain. The domain may be + specified with or without trailing dot, which is considered + equivalent. The second word must be <literal>IN</literal> the + third word <literal>DS</literal>. The following words specify the + key tag, signature algorithm, digest algorithm, followed by the + hex-encoded key fingerprint. See <ulink + url="https://tools.ietf.org/html/rfc4034#section-5">RFC 4034, + Section 5</ulink> for details about the precise syntax and meaning + of these fields.</para> + + <para>Alternatively, DNSKEY resource records may be used to define + trust anchors, like in the following example:</para> + + <programlisting>. IN DNSKEY 257 3 8 AwEAAagAIKlVZrpC6Ia7gEzahOR+9W29euxhJhVVLOyQbSEW0O8gcCjFFVQUTf6v58fLjwBd0YI0EzrAcQqBGCzh/RStIoO8g0NfnfL2MTJRkxoXbfDaUeVPQuYEhg37NZWAJQ9VnMVDxP/VHL496M/QZxkjf5/Efucp2gaDX6RS6CXpoY68LsvPVjR0ZSwzz1apAzvN9dlzEheX7ICJBBtuA6G3LQpzW5hOA2hzCTMjJPJ8LbqF6dsV6DoBQzgul0sGIcGOYl7OyQdXfZ57relSQageu+ipAdTTJ25AsRTAoub8ONGcLmqrAmRLKBP1dfwhYB4N7knNnulqQxA+Uk1ihz0=</programlisting> + + <para>The first word specifies the domain again, the second word + must be <literal>IN</literal>, followed by + <literal>DNSKEY</literal>. The subsequent words encode the DNSKEY + flags, protocol and algorithm fields, followed by the key data + encoded in Base64. See <ulink + url="https://tools.ietf.org/html/rfc4034#section-2">RFC 4034, + Section 2</ulink> for details about the precise syntax and meaning + of these fields.</para> + + <para>If multiple DS or DNSKEY records are defined for the same + domain (possibly even in different trust anchor files), all keys + are used and are considered equivalent as base for DNSSEC + proofs.</para> + + <para>Note that <filename>systemd-resolved</filename> will + automatically use a built-in trust anchor key for the Internet + root domain if no positive trust anchors are defined for the root + domain. In most cases it is hence unnecessary to define an + explicit key with trust anchor files. The built-in key is disabled + as soon as at least one trust anchor key for the root domain is + defined in trust anchor files.</para> + + <para>It is generally recommended to encode trust anchors in DS + resource records, rather than DNSKEY resource records.</para> + + <para>If a trust anchor specified via a DS record is found revoked + it is automatically removed from the trust anchor database for the + runtime. See <ulink url="https://tools.ietf.org/html/rfc5011">RFC + 5011</ulink> for details about revoked trust anchors. Note that + <filename>systemd-resolved</filename> will not update its trust + anchor database from DNS servers automatically. Instead, it is + recommended to update the resolver software or update the new + trust anchor via adding in new trust anchor files.</para> + + <para>The current DNSSEC trust anchor for the Internet's root + domain is available at the <ulink + url="https://data.iana.org/root-anchors/root-anchors.xml">IANA + Trust Anchor and Keys</ulink> page.</para> + </refsect1> + + <refsect1> + <title>Negative Trust Anchors</title> + + <para>Negative trust anchors define domains where DNSSEC + validation shall be turned off. Negative trust anchor files are + found at the same location as positive trust anchor files, and + follow the same overriding rules. They are text files with the + <filename>.negative</filename> suffix. Empty lines and lines whose + first character is <literal>;</literal> are ignored. Each line + specifies one domain name where DNSSEC validation shall be + disabled on.</para> + + <para>Negative trust anchors are useful to support private DNS + subtrees that are not referenced from the Internet DNS hierarchy, + and not signed.</para> + + <para><ulink url="https://tools.ietf.org/html/rfc7646">RFC + 7646</ulink> for details on negative trust anchors.</para> + + <para>If no negative trust anchor files are configured a built-in + set of well-known private DNS zone domains is used as negative + trust anchors.</para> + + <para>It is also possibly to define per-interface negative trust + anchors using the <varname>DNSSECNegativeTrustAnchors=</varname> + setting in + <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry> + files.</para> + </refsect1> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry> + </para> + </refsect1> + +</refentry> diff --git a/man/file-hierarchy.xml b/man/file-hierarchy.xml index 345c56cefa..538a592f8d 100644 --- a/man/file-hierarchy.xml +++ b/man/file-hierarchy.xml @@ -63,7 +63,7 @@ and restrictions systemd makes on the file system hierarchy.</para> - <para>Many of the paths described here are queriable + <para>Many of the paths described here can be queried with the <citerefentry><refentrytitle>systemd-path</refentrytitle><manvolnum>1</manvolnum></citerefentry> tool.</para> diff --git a/man/journal-remote.conf.xml b/man/journal-remote.conf.xml index fc60258d0b..2d345963d9 100644 --- a/man/journal-remote.conf.xml +++ b/man/journal-remote.conf.xml @@ -72,6 +72,13 @@ <literal>[Remote]</literal> section:</para> <variablelist> + <varlistentry> + <term><varname>Seal=</varname></term> + + <listitem><para>Periodically sign the data in the journal using Forward Secure Sealing. + </para></listitem> + </varlistentry> + <varlistentry> <term><varname>SplitMode=</varname></term> @@ -105,7 +112,7 @@ <refsect1> <title>See Also</title> <para> - <citerefentry><refentrytitle>systemd-journal-remote</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-journal-remote</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> </para> diff --git a/man/journalctl.xml b/man/journalctl.xml index b57afb6ebf..b281f26b45 100644 --- a/man/journalctl.xml +++ b/man/journalctl.xml @@ -91,8 +91,14 @@ paths may be specified. If a file path refers to an executable file, this is equivalent to an <literal>_EXE=</literal> match for the canonicalized binary path. Similarly, if a path refers - to a device node, this is equivalent to a - <literal>_KERNEL_DEVICE=</literal> match for the device.</para> + to a device node then match is added for the kernel name of the + device (<literal>_KERNEL_DEVICE=</literal>). Also, matches for the + kernel names of all the parent devices are added automatically. + Device node paths are not stable across reboots, therefore match + for the current boot id (<literal>_BOOT_ID=</literal>) is + always added as well. Note that only the log entries for + the existing device nodes maybe queried by providing path to + the device node.</para> <para>Additional constraints may be added using options <option>--boot</option>, <option>--unit=</option>, etc., to @@ -572,6 +578,13 @@ </varlistentry> <varlistentry> + <term><option>-N</option></term> + <term><option>--fields</option></term> + + <listitem><para>Print all field names currently used in all entries of the journal.</para></listitem> + </varlistentry> + + <varlistentry> <term><option>--system</option></term> <term><option>--user</option></term> diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 309220632e..42d5e006bb 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -91,6 +91,7 @@ <term><varname>systemd.default_standard_output=</varname></term> <term><varname>systemd.default_standard_error=</varname></term> <term><varname>systemd.setenv=</varname></term> + <term><varname>systemd.machine_id=</varname></term> <listitem> <para>Parameters understood by the system and service manager to control system behavior. For details, see diff --git a/man/logind.conf.xml b/man/logind.conf.xml index 94376656d5..597759e33a 100644 --- a/man/logind.conf.xml +++ b/man/logind.conf.xml @@ -288,21 +288,17 @@ <varname>TasksMax=</varname> setting of the per-user slice unit, see <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry> - for details. Defaults to 4096.</para></listitem> + for details. Defaults to 12288 (12K).</para></listitem> </varlistentry> <varlistentry> <term><varname>RemoveIPC=</varname></term> - <listitem><para>Controls whether System V and POSIX IPC - objects belonging to the user shall be removed when the user - fully logs out. Takes a boolean argument. If enabled, the user - may not consume IPC resources after the last of the user's - sessions terminated. This covers System V semaphores, shared - memory and message queues, as well as POSIX shared memory and - message queues. Note that IPC objects of the root user are - excluded from the effect of this setting. Defaults to - <literal>yes</literal>.</para></listitem> + <listitem><para>Controls whether System V and POSIX IPC objects belonging to the user shall be removed when the + user fully logs out. Takes a boolean argument. If enabled, the user may not consume IPC resources after the + last of the user's sessions terminated. This covers System V semaphores, shared memory and message queues, as + well as POSIX shared memory and message queues. Note that IPC objects of the root user and other system users + are excluded from the effect of this setting. Defaults to <literal>yes</literal>.</para></listitem> </varlistentry> </variablelist> diff --git a/man/machine-id.xml b/man/machine-id.xml index db72c2a01c..d318ec54ec 100644 --- a/man/machine-id.xml +++ b/man/machine-id.xml @@ -84,6 +84,12 @@ at install time. Use <citerefentry><refentrytitle>systemd-firstboot</refentrytitle><manvolnum>1</manvolnum></citerefentry> to initialize it on mounted (but not booted) system images.</para> + + <para>The machine-id may also be set, for example when network + booting, by setting the <varname>systemd.machine_id=</varname> + kernel command line parameter or passing the option + <option>--machine-id=</option> to systemd. A machine-id may not + be set to all zeros.</para> </refsect1> <refsect1> diff --git a/man/machinectl.xml b/man/machinectl.xml index a7288c249b..0e07c201bd 100644 --- a/man/machinectl.xml +++ b/man/machinectl.xml @@ -247,7 +247,7 @@ <literal>checksum</literal> is specified, the download is checked for integrity after the transfer is complete, but no signatures are verified. If <literal>signature</literal> is - specified, the checksum is verified and the images's signature + specified, the checksum is verified and the image's signature is checked against a local keyring of trustable vendors. It is strongly recommended to set this option to <literal>signature</literal> if the server and protocol @@ -768,10 +768,9 @@ image is read from standard input, in which case the second argument is mandatory.</para> - <para>Similar as with <command>pull-tar</command>, - <command>pull-raw</command> the file system - <filename>/var/lib/machines.raw</filename> is increased in - size of necessary and appropriate. Optionally, the + <para>Both <command>pull-tar</command> and <command>pull-raw</command> + will resize <filename>/var/lib/machines.raw</filename> and the + filesystem therein as necessary. Optionally, the <option>--read-only</option> switch may be used to create a read-only container or VM image. No cryptographic validation is done when importing the images.</para> diff --git a/man/resolved.conf.xml b/man/resolved.conf.xml index 4680b6a4e5..920ce9e89b 100644 --- a/man/resolved.conf.xml +++ b/man/resolved.conf.xml @@ -68,44 +68,46 @@ <refsect1> <title>Options</title> + <para>The following options are available in the <literal>[Resolve]</literal> section:</para> + <variablelist class='network-directives'> <varlistentry> <term><varname>DNS=</varname></term> - <listitem><para>A space-separated list of IPv4 and IPv6 - addresses to be used as system DNS servers. DNS requests are - sent to one of the listed DNS servers in parallel to any - per-interface DNS servers acquired from - <citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>. - For compatibility reasons, if this setting is not specified, - the DNS servers listed in - <filename>/etc/resolv.conf</filename> are used instead, if - that file exists and any servers are configured in it. This - setting defaults to the empty list.</para></listitem> + <listitem><para>A space-separated list of IPv4 and IPv6 addresses to use as system DNS servers. DNS requests + are sent to one of the listed DNS servers in parallel to suitable per-link DNS servers acquired from + <citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> or + set at runtime by external applications. For compatibility reasons, if this setting is not specified, the DNS + servers listed in <filename>/etc/resolv.conf</filename> are used instead, if that file exists and any servers + are configured in it. This setting defaults to the empty list.</para></listitem> </varlistentry> <varlistentry> <term><varname>FallbackDNS=</varname></term> - <listitem><para>A space-separated list of IPv4 and IPv6 - addresses to be used as the fallback DNS servers. Any - per-interface DNS servers obtained from + <listitem><para>A space-separated list of IPv4 and IPv6 addresses to use as the fallback DNS servers. Any + per-link DNS servers obtained from <citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> - take precedence over this setting, as do any servers set via - <varname>DNS=</varname> above or - <filename>/etc/resolv.conf</filename>. This setting is hence - only used if no other DNS server information is known. If this - option is not given, a compiled-in list of DNS servers is used - instead.</para></listitem> + take precedence over this setting, as do any servers set via <varname>DNS=</varname> above or + <filename>/etc/resolv.conf</filename>. This setting is hence only used if no other DNS server information is + known. If this option is not given, a compiled-in list of DNS servers is used instead.</para></listitem> </varlistentry> <varlistentry> <term><varname>Domains=</varname></term> - <listitem><para>A space-separated list of search domains. For - compatibility reasons, if this setting is not specified, the - search domains listed in <filename>/etc/resolv.conf</filename> - are used instead, if that file exists and any domains are - configured in it. This setting defaults to the empty - list.</para></listitem> + <listitem><para>A space-separated list of domains. These domains are used as search suffixes when resolving + single-label host names (domain names which contain no dot), in order to qualify them into fully-qualified + domain names (FQDNs). Search domains are strictly processed in the order they are specified, until the name + with the suffix appended is found. For compatibility reasons, if this setting is not specified, the search + domains listed in <filename>/etc/resolv.conf</filename> are used instead, if that file exists and any domains + are configured in it. This setting defaults to the empty list.</para> + + <para>Specified domain names may optionally be prefixed with <literal>~</literal>. In this case they do not + define a search path, but preferably direct DNS queries for the indicated domains to the DNS servers configured + with the system <varname>DNS=</varname> setting (see above), in case additional, suitable per-link DNS servers + are known. If no per-link DNS servers are known using the <literal>~</literal> syntax has no effect. Use the + construct <literal>~.</literal> (which is composed of <literal>~</literal> to indicate a routing domain and + <literal>.</literal> to indicate the DNS root domain that is the implied suffix of all DNS domains) to use the + system DNS server defined with <varname>DNS=</varname> preferably for all domains.</para></listitem> </varlistentry> <varlistentry> @@ -119,11 +121,87 @@ <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 LLMNR settings. LLMNR will be - enabled on an interface only if the per-interface and the + also maintains per-link LLMNR settings. LLMNR will be + enabled on a link only if the per-link 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 + DNSSEC-validated locally (excluding LLMNR and Multicast + DNS). If the response to a lookup request is detected to be invalid + a lookup failure is returned to applications. Note that + this mode requires a DNS server that supports DNSSEC. If the + DNS server does not properly support DNSSEC all validations + will fail. If set to <literal>allow-downgrade</literal> DNSSEC + validation is attempted, but if the server does not support + DNSSEC properly, DNSSEC mode is automatically disabled. Note + that this mode makes DNSSEC validation vulnerable to + "downgrade" attacks, where an attacker might be able to + trigger a downgrade to non-DNSSEC mode by synthesizing a DNS + response that suggests DNSSEC was not supported. If set to + false, DNS lookups are not DNSSEC validated.</para> + + <para>Note that DNSSEC validation requires retrieval of + additional DNS data, and thus results in a small DNS look-up + time penalty.</para> + + <para>DNSSEC requires knowledge of "trust anchors" to prove + data integrity. The trust anchor for the Internet root domain + is built into the resolver, additional trust anchors may be + defined with + <citerefentry><refentrytitle>dnssec-trust-anchors.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>. + Trust anchors may change at regular intervals, and old trust + anchors may be revoked. In such a case DNSSEC validation is + not possible until new trust anchors are configured locally or + the resolver software package is updated with the new root + trust anchor. In effect, when the built-in trust anchor is + revoked and <varname>DNSSEC=</varname> is true, all further + lookups will fail, as it cannot be proved anymore whether + lookups are correctly signed, or validly unsigned. If + <varname>DNSSEC=</varname> is set to + <literal>allow-downgrade</literal> the resolver will + automatically turn off DNSSEC validation in such a case.</para> + + <para>Client programs looking up DNS data will be informed + whether lookups could be verified using DNSSEC, or whether the + returned data could not be verified (either because the data + was found unsigned in the DNS, or the DNS server did not + support DNSSEC or no appropriate trust anchors were known). In + the latter case it is assumed that client programs employ a + secondary scheme to validate the returned DNS data, should + this be required.</para> + + <para>It is recommended to set <varname>DNSSEC=</varname> to + true on systems where it is known that the DNS server supports + DNSSEC correctly, and where software or trust anchor updates + happen regularly. On other systems it is recommended to set + <varname>DNSSEC=</varname> to + <literal>allow-downgrade</literal>.</para> + + <para>In addition to this global DNSSEC setting + <citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> + also maintains per-link DNSSEC settings. For system DNS + servers (see above), only the global DNSSEC setting is in + effect. For per-link DNS servers the per-link + setting is in effect, unless it is unset in which case the + global setting is used instead.</para> + + <para>Site-private DNS zones generally conflict with DNSSEC + operation, unless a negative (if the private zone is not + signed) or positive (if the private zone is signed) trust + anchor is configured for them. If + <literal>allow-downgrade</literal> mode is selected, it is + attempted to detect site-private DNS zones using top-level + domains (TLDs) that are not known by the DNS root server. This + logic does not work in all private zone setups.</para> + + <para>Defaults to off.</para> + </listitem> + </varlistentry> + </variablelist> </refsect1> @@ -133,7 +211,8 @@ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>, - <citerefentry><refentrytitle>resolv.conf</refentrytitle><manvolnum>4</manvolnum></citerefentry> + <citerefentry><refentrytitle>dnssec-trust-anchors.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry project='man-pages'><refentrytitle>resolv.conf</refentrytitle><manvolnum>4</manvolnum></citerefentry> </para> </refsect1> diff --git a/man/sd-bus.xml b/man/sd-bus.xml new file mode 100644 index 0000000000..336dd33ea0 --- /dev/null +++ b/man/sd-bus.xml @@ -0,0 +1,123 @@ +<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<!-- + This file is part of systemd. + + Copyright 2016 Zbigniew JÄ™drzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +--> + +<refentry id="sd-bus" xmlns:xi="http://www.w3.org/2001/XInclude"> + + <refentryinfo> + <title>sd-bus</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Documentation</contrib> + <firstname>Zbigniew</firstname> + <surname>JÄ™drzejewski-Szmek</surname> + <email>zbyszek@in.waw.pl</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>sd-bus</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname>sd-bus</refname> + <refpurpose>A lightweight D-Bus and kdbus client library</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <funcsynopsis> + <funcsynopsisinfo>#include <systemd/sd-bus.h></funcsynopsisinfo> + </funcsynopsis> + + <cmdsynopsis> + <command>pkg-config --cflags --libs libsystemd</command> + </cmdsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><filename>sd-bus.h</filename> provides an implementation + of a D-Bus client. It can interoperate both with the traditional + <citerefentry project='man-pages'><refentrytitle>dbus-daemon</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + and with kdbus. See + <ulink url="http://www.freedesktop.org/software/dbus/" /> + for more information about the big picture. + </para> + + <important> + <para>Interfaces described here have not been declared stable yet, + and are not accessible from <filename>libsystemd.so</filename>. + This documentation is provided in hope it might be useful for + developers, without any guarantees of availability or stability. + </para> + </important> + + <para>See + <citerefentry><refentrytitle>sd_bus_default</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_new</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_request_name</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_start</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_message_append</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_message_append_basic</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_message_append_array</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_message_append_string_memfd</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_message_append_strv</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_message_can_send</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_message_get_cookie</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_message_get_monotonic_usec</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_send</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_set_address</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_set_description</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_set_prepare</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_creds_get_pid</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_creds_new_from_pid</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_get_name_creds</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_get_owner_creds</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_negotiate_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_path_encode</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd-bus-errors</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_error</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_error_add_map</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_bus_set_allow_interactive_authorization</refentrytitle><manvolnum>3</manvolnum></citerefentry> + for more information about the functions available.</para> + </refsect1> + + <xi:include href="libsystemd-pkgconfig.xml" /> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd-event</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry project='man-pages'><refentrytitle>dbus-daemon</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry project='man-pages'><refentrytitle>dbus-send</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <ulink url="https://developer.gnome.org/gio/stable/gdbus.html">gdbus</ulink> + </para> + </refsect1> + +</refentry> diff --git a/man/sd-event.xml b/man/sd-event.xml index 47989f4421..fc615f0906 100644 --- a/man/sd-event.xml +++ b/man/sd-event.xml @@ -136,7 +136,7 @@ <listitem><para>Event sources may be assigned a 64bit priority value, that controls the order in which event sources are - dispatched if multiple are pending simultanously. See + dispatched if multiple are pending simultaneously. See <citerefentry><refentrytitle>sd_event_source_set_priority</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para></listitem> <listitem><para>The event loop may automatically send watchdog diff --git a/man/sd-journal.xml b/man/sd-journal.xml index 9b1a52207f..09747a480c 100644 --- a/man/sd-journal.xml +++ b/man/sd-journal.xml @@ -77,13 +77,17 @@ <citerefentry><refentrytitle>sd_journal_get_realtime_usec</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_add_match</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_seek_head</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_journal_query_enumerate</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_journal_enumerate_fields</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_get_cursor</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_get_cutoff_realtime_usec</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_get_cutoff_monotonic_usec</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_get_usage</refentrytitle><manvolnum>3</manvolnum></citerefentry>, - <citerefentry><refentrytitle>sd_journal_get_catalog</refentrytitle><manvolnum>3</manvolnum></citerefentry> + <citerefentry><refentrytitle>sd_journal_get_catalog</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_journal_get_fd</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_journal_has_runtime_files</refentrytitle><manvolnum>3</manvolnum></citerefentry> and - <citerefentry><refentrytitle>sd_journal_get_fd</refentrytitle><manvolnum>3</manvolnum></citerefentry> + <citerefentry><refentrytitle>sd_journal_has_persistent_files</refentrytitle><manvolnum>3</manvolnum></citerefentry> for more information about the functions implemented.</para> <para>Command line access for submitting entries to the journal is @@ -109,6 +113,8 @@ <citerefentry><refentrytitle>sd_journal_get_realtime_usec</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_add_match</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_seek_head</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_journal_query_enumerate</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_journal_enumerate_fields</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_get_cursor</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_get_cutoff_realtime_usec</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_get_cutoff_monotonic_usec</refentrytitle><manvolnum>3</manvolnum></citerefentry>, @@ -116,6 +122,8 @@ <citerefentry><refentrytitle>sd_journal_get_fd</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_query_unique</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_get_catalog</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_journal_has_runtime_files</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_journal_has_persistent_files</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd-id128</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry project='die-net'><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry> diff --git a/man/sd_bus_creds_get_pid.xml b/man/sd_bus_creds_get_pid.xml index aec12bda16..3bcda46656 100644 --- a/man/sd_bus_creds_get_pid.xml +++ b/man/sd_bus_creds_get_pid.xml @@ -470,7 +470,7 @@ modified by the caller.</para> <para>All functions that take a <parameter>char***</parameter> - parameter will store the answer there as an address of a an array + parameter will store the answer there as an address of an array of strings. Each individual string is NUL-terminated, and the array is NULL-terminated as a whole. It will be valid as long as <parameter>c</parameter> remains valid, and should not be freed or diff --git a/man/sd_event_add_child.xml b/man/sd_event_add_child.xml index d4b180cf03..bc732db7fa 100644 --- a/man/sd_event_add_child.xml +++ b/man/sd_event_add_child.xml @@ -127,7 +127,7 @@ <constant>SD_EVENT_OFF</constant> with <citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para> - <para>If the the second parameter of + <para>If the second parameter of <function>sd_event_add_child()</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 diff --git a/man/sd_event_add_defer.xml b/man/sd_event_add_defer.xml index 6a13ede76e..d9ebd3b179 100644 --- a/man/sd_event_add_defer.xml +++ b/man/sd_event_add_defer.xml @@ -108,7 +108,7 @@ handler will be called once (<constant>SD_EVENT_ONESHOT</constant>). Note that if the event source is set to <constant>SD_EVENT_ON</constant> the event loop - will never go to sleep again, but continously call the handler, + will never go to sleep again, but continuously call the handler, possibly interleaved with other event sources.</para> <para><function>sd_event_add_post()</function> adds a new event diff --git a/man/sd_event_add_io.xml b/man/sd_event_add_io.xml index 4cc0428e29..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 - continously (<constant>SD_EVENT_ON</constant>), but this may be + 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 continously 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 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> + <para>If the second parameter of + <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 b5312735d2..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> - - <para>If the 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> + 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 + <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 df38f52fc9..a2c0d54b56 100644 --- a/man/sd_event_add_time.xml +++ b/man/sd_event_add_time.xml @@ -114,41 +114,28 @@ <refsect1> <title>Description</title> - <para><function>sd_event_add_time()</function> adds a new timer - event source to an event loop. The event loop object is specified - in the <parameter>event</parameter> parameter, the event source - object is returned in the <parameter>source</parameter> - parameter. The <parameter>clock</parameter> parameter takes a - clock identifier, one of <constant>CLOCK_REALTIME</constant>, - <constant>CLOCK_MONOTONIC</constant>, - <constant>CLOCK_BOOTTIME</constant>, - <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 - delayed substantially. Picking higher accuracy values allows the - system to coalesce timer events more aggressively, thus 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, - the kernel timer slack (see - <citerefentry><refentrytitle>prctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>) - and additional scheduling latencies. To query the actual time the - handler was called use + <para><function>sd_event_add_time()</function> adds a new timer event source to an event loop. The event loop + object is specified in the <parameter>event</parameter> parameter, the event source object is returned in the + <parameter>source</parameter> parameter. The <parameter>clock</parameter> parameter takes a clock identifier, one + of <constant>CLOCK_REALTIME</constant>, <constant>CLOCK_MONOTONIC</constant>, <constant>CLOCK_BOOTTIME</constant>, + <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 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. If the paramater is specified as <constant>UINT64_MAX</constant> the timer event will never elapse, + which may be used as an alternative to explicitly disabling a timer event source with + <citerefentry><refentrytitle>sd_event_source_set_enabled</refentrytitle><manvolnum>3</manvolnum></citerefentry>. 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, + 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 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>), 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> <para>By default, the timer will elapse once @@ -159,7 +146,7 @@ disabled after the invocation, even if the <constant>SD_EVENT_ON</constant> mode was requested before. Note that a timer event set to <constant>SD_EVENT_ON</constant> will - fire continously unless its configured time is updated using + fire continuously unless its configured time is updated using <function>sd_event_source_set_time()</function>. </para> @@ -167,22 +154,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> - - <para>If the 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> + 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 + <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 +181,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 +201,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 +241,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_exit.xml b/man/sd_event_exit.xml index 4f34f3b122..9846a3eaf4 100644 --- a/man/sd_event_exit.xml +++ b/man/sd_event_exit.xml @@ -76,7 +76,7 @@ exit. The <parameter>code</parameter> parameter may be any integer value and is returned as-is by <citerefentry><refentrytitle>sd_event_loop</refentrytitle><manvolnum>3</manvolnum></citerefentry> - after the last event loop iteration. It may also be be queried + after the last event loop iteration. It may also be queried using <function>sd_event_get_exit_code()</function>, see below. </para> diff --git a/man/sd_event_now.xml b/man/sd_event_now.xml index f577e44c0e..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 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/sd_event_source_get_pending.xml b/man/sd_event_source_get_pending.xml index 1c06e81fe0..7f88bd1b87 100644 --- a/man/sd_event_source_get_pending.xml +++ b/man/sd_event_source_get_pending.xml @@ -87,7 +87,7 @@ <para>For I/O event sources, as created with <citerefentry><refentrytitle>sd_event_add_io</refentrytitle><manvolnum>3</manvolnum></citerefentry>, the call - <citerefentry><refentrytitle>sd_event_get_io_revents</refentrytitle><manvolnum>3</manvolnum></citerefentry> + <citerefentry><refentrytitle>sd_event_source_get_io_revents</refentrytitle><manvolnum>3</manvolnum></citerefentry> may be used to query the type of event pending in more detail.</para> diff --git a/man/sd_event_source_set_enabled.xml b/man/sd_event_source_set_enabled.xml index 74c02e87bb..6844f29a49 100644 --- a/man/sd_event_source_set_enabled.xml +++ b/man/sd_event_source_set_enabled.xml @@ -105,7 +105,7 @@ with calls such as <citerefentry><refentrytitle>sd_event_add_io</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_event_add_time</refentrytitle><manvolnum>3</manvolnum></citerefentry>. However, - depending on the event source type they are enabled continously + depending on the event source type they are enabled continuously (<constant>SD_EVENT_ON</constant>) or only for a single invocation of the event source handler (<constant>SD_EVENT_ONESHOT</constant>). For details see the diff --git a/man/sd_event_source_set_prepare.xml b/man/sd_event_source_set_prepare.xml index 7066a55306..24861d01d9 100644 --- a/man/sd_event_source_set_prepare.xml +++ b/man/sd_event_source_set_prepare.xml @@ -71,7 +71,7 @@ <title>Description</title> <para><function>sd_event_source_set_prepare()</function> may be - used to set a prepartion callback for the event source object + used to set a preparation callback for the event source object specified as <parameter>source</parameter>. The callback function specified as <parameter>callback</parameter> will be invoked immediately before the event loop goes to sleep to wait for diff --git a/man/sd_event_source_set_priority.xml b/man/sd_event_source_set_priority.xml index cc0f5a0103..9234f4233e 100644 --- a/man/sd_event_source_set_priority.xml +++ b/man/sd_event_source_set_priority.xml @@ -111,7 +111,7 @@ dispatched is undefined, but the event loop generally tries to dispatch them in the order it learnt about events on them. As the backing kernel primitives do not provide accurate information - about the order in which events occured this is not necessarily + about the order in which events occurred this is not necessarily reliable. However, it is guaranteed that if events are seen on multiple same-priority event sources at the same time, each one is not dispatched again until all others have been dispatched diff --git a/man/sd_event_wait.xml b/man/sd_event_wait.xml index 1eefa80700..f2aea00e98 100644 --- a/man/sd_event_wait.xml +++ b/man/sd_event_wait.xml @@ -107,7 +107,7 @@ and <citerefentry><refentrytitle>sd_event_loop</refentrytitle><manvolnum>3</manvolnum></citerefentry> for higher-level functions that execute individual but complete - iterations of an event loop or run it continously.</para> + iterations of an event loop or run it continuously.</para> <para><function>sd_event_prepare()</function> checks for pending events and arms necessary timers. If any events are ready to be @@ -169,7 +169,7 @@ <term><constant>SD_EVENT_PREPARING</constant></term> <listitem><para>An event source is currently being prepared, - i.e. the preparation handler is currently being excuted, as + i.e. the preparation handler is currently being executed, as set with <citerefentry><refentrytitle>sd_event_set_prepare</refentrytitle><manvolnum>3</manvolnum></citerefentry>. This state is only seen in the event source preparation handler diff --git a/man/sd_journal_enumerate_fields.xml b/man/sd_journal_enumerate_fields.xml new file mode 100644 index 0000000000..fa5884106b --- /dev/null +++ b/man/sd_journal_enumerate_fields.xml @@ -0,0 +1,161 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<!-- + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +--> + +<refentry id="sd_journal_enumerate_fields"> + + <refentryinfo> + <title>sd_journal_enumerate_fields</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Developer</contrib> + <firstname>Lennart</firstname> + <surname>Poettering</surname> + <email>lennart@poettering.net</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>sd_journal_enumerate_fields</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname>sd_journal_enumerate_fields</refname> + <refname>sd_journal_restart_fields</refname> + <refname>SD_JOURNAL_FOREACH_FIELD</refname> + <refpurpose>Read used field names from the journal</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <funcsynopsis> + <funcsynopsisinfo>#include <systemd/sd-journal.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>int <function>sd_journal_enumerate_fields</function></funcdef> + <paramdef>sd_journal *<parameter>j</parameter></paramdef> + <paramdef>const char **<parameter>field</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>void <function>sd_journal_restart_fields</function></funcdef> + <paramdef>sd_journal *<parameter>j</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef><function>SD_JOURNAL_FOREACH_FIELD</function></funcdef> + <paramdef>sd_journal *<parameter>j</parameter></paramdef> + <paramdef>const char *<parameter>field</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><function>sd_journal_enumerate_fields()</function> may be used to iterate through all field names used in the + opened journal files. On each invocation the next field name is returned. The order of the returned field names is + not defined. It takes two arguments: the journal context object, plus a pointer to a constant string pointer where + the field name is stored in. The returned data is in a read-only memory map and is only valid until the next + invocation of <function>sd_journal_enumerate_fields()</function>. Note that this call is subject to the data field + size threshold as controlled by <function>sd_journal_set_data_threshold()</function>.</para> + + <para><function>sd_journal_restart_fields()</function> resets the field name enumeration index to the beginning of + the list. The next invocation of <function>sd_journal_enumerate_fields()</function> will return the first field + name again.</para> + + <para>The <function>SD_JOURNAL_FOREACH_FIELD()</function> macro may be used as a handy wrapper around + <function>sd_journal_restart_fields()</function> and <function>sd_journal_enumerate_fields()</function>.</para> + + <para>These functions currently are not influenced by matches set with <function>sd_journal_add_match()</function> + but this might change in a later version of this software.</para> + + <para>To retrieve the possible values a specific field can take use + <citerefentry><refentrytitle>sd_journal_query_unique</refentrytitle><manvolnum>3</manvolnum></citerefentry>.</para> + </refsect1> + + <refsect1> + <title>Return Value</title> + + <para><function>sd_journal_enumerate_fields()</function> returns a + positive integer if the next field name has been read, 0 when no + more field names are known, or a negative errno-style error code. + <function>sd_journal_restart_fields()</function> returns + nothing.</para> + </refsect1> + + <refsect1> + <title>Notes</title> + + <para>The <function>sd_journal_enumerate_fields()</function> and <function>sd_journal_restart_fields()</function> + interfaces are available as a shared library, which can be compiled and linked to with the + <constant>libsystemd</constant> <citerefentry + project='die-net'><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry> file.</para> + </refsect1> + + <refsect1> + <title>Examples</title> + + <para>Use the <function>SD_JOURNAL_FOREACH_FIELD</function> macro to iterate through all field names in use in the + current journal.</para> + + <programlisting>#include <stdio.h> +#include <string.h> +#include <systemd/sd-journal.h> + +int main(int argc, char *argv[]) { + sd_journal *j; + const char *field; + int r; + + r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); + if (r < 0) { + fprintf(stderr, "Failed to open journal: %s\n", strerror(-r)); + return 1; + } + SD_JOURNAL_FOREACH_FIELD(j, field) + printf("%s\n", field); + sd_journal_close(j); + return 0; +}</programlisting> + + </refsect1> + + <refsect1> + <title>See Also</title> + + <para> + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd-journal</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_journal_open</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_journal_query_unique</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_journal_get_data</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_journal_add_match</refentrytitle><manvolnum>3</manvolnum></citerefentry> + </para> + </refsect1> + +</refentry> diff --git a/man/sd_journal_has_runtime_files.xml b/man/sd_journal_has_runtime_files.xml new file mode 100644 index 0000000000..237e649206 --- /dev/null +++ b/man/sd_journal_has_runtime_files.xml @@ -0,0 +1,95 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<!-- + This file is part of systemd. + + Copyright 2016 Jan SynáÄek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +--> + +<refentry id="sd_journal_has_runtime_files"> + + <refentryinfo> + <title>sd_journal_has_runtime_files</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Developer</contrib> + <firstname>Jan</firstname> + <surname>SynáÄek</surname> + <email>jan.synacek@gmail.com</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>sd_journal_has_runtime_files</refentrytitle> + <manvolnum>3</manvolnum> + </refmeta> + + <refnamediv> + <refname>sd_journal_has_runtime_files</refname> + <refname>sd_journal_has_persistent_files</refname> + <refpurpose>Query availability of runtime or persistent journal files.</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <funcsynopsis> + <funcsynopsisinfo>#include <systemd/sd-journal.h></funcsynopsisinfo> + + <funcprototype> + <funcdef>int <function>sd_journal_has_runtime_files</function></funcdef> + <paramdef>sd_journal *<parameter>j</parameter></paramdef> + </funcprototype> + + <funcprototype> + <funcdef>int <function>sd_journal_has_persistent_files</function></funcdef> + <paramdef>sd_journal *<parameter>j</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><function>sd_journal_has_runtime_files()</function> returns a positive value + if runtime journal files (present in /run/systemd/journal/) have been found. + Otherwise returns 0.</para> + + <para><function>sd_journal_has_persistent_files()</function> returns a positive value + if persistent journal files (present in /var/log/journal/) have been found. + Otherwise returns 0.</para> + </refsect1> + + <refsect1> + <title>Return value</title> + <para>Both <function>sd_journal_has_runtime_files()</function> + and <function>sd_journal_has_persistent_files()</function> return -EINVAL + if their argument is NULL. + </para> + </refsect1> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>sd-journal</refentrytitle><manvolnum>3</manvolnum></citerefentry> + </para> + </refsect1> + +</refentry> diff --git a/man/sd_journal_query_unique.xml b/man/sd_journal_query_unique.xml index ac0e5f633f..dbff55c105 100644 --- a/man/sd_journal_query_unique.xml +++ b/man/sd_journal_query_unique.xml @@ -128,6 +128,11 @@ <para>Note that these functions currently are not influenced by matches set with <function>sd_journal_add_match()</function> but this might change in a later version of this software.</para> + + <para>To enumerate all field names currently in use (and thus all suitable field parameters for + <function>sd_journal_query_unique()</function>), use the + <citerefentry><refentrytitle>sd_journal_enumerate_fields</refentrytitle><manvolnum>3</manvolnum></citerefentry> + call.</para> </refsect1> <refsect1> @@ -167,25 +172,25 @@ #include <systemd/sd-journal.h> int main(int argc, char *argv[]) { - sd_journal *j; - const void *d; - size_t l; - int r; - - r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); - if (r < 0) { - fprintf(stderr, "Failed to open journal: %s\n", strerror(-r)); - return 1; - } - r = sd_journal_query_unique(j, "_SYSTEMD_UNIT"); - if (r < 0) { - fprintf(stderr, "Failed to query journal: %s\n", strerror(-r)); - return 1; - } - SD_JOURNAL_FOREACH_UNIQUE(j, d, l) - printf("%.*s\n", (int) l, (const char*) d); - sd_journal_close(j); - return 0; + sd_journal *j; + const void *d; + size_t l; + int r; + + r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); + if (r < 0) { + fprintf(stderr, "Failed to open journal: %s\n", strerror(-r)); + return 1; + } + r = sd_journal_query_unique(j, "_SYSTEMD_UNIT"); + if (r < 0) { + fprintf(stderr, "Failed to query journal: %s\n", strerror(-r)); + return 1; + } + SD_JOURNAL_FOREACH_UNIQUE(j, d, l) + printf("%.*s\n", (int) l, (const char*) d); + sd_journal_close(j); + return 0; }</programlisting> </refsect1> @@ -198,6 +203,7 @@ int main(int argc, char *argv[]) { <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd-journal</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_open</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + <citerefentry><refentrytitle>sd_journal_enumerate_fields</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_get_data</refentrytitle><manvolnum>3</manvolnum></citerefentry>, <citerefentry><refentrytitle>sd_journal_add_match</refentrytitle><manvolnum>3</manvolnum></citerefentry> </para> diff --git a/man/sd_notify.xml b/man/sd_notify.xml index dbf6330453..bd6cfdcd29 100644 --- a/man/sd_notify.xml +++ b/man/sd_notify.xml @@ -242,7 +242,7 @@ multiple file descriptors are submitted at once, the specified name will be assigned to all of them. In order to assign different names to submitted file descriptors, submit them in - seperate invocations of + separate invocations of <function>sd_pid_notify_with_fds()</function>. The name may consist of any ASCII character, but must not contain control characters or <literal>:</literal>. It may not be longer than diff --git a/man/sd_seat_get_active.xml b/man/sd_seat_get_active.xml index 6e1d505dce..c5e6ddab02 100644 --- a/man/sd_seat_get_active.xml +++ b/man/sd_seat_get_active.xml @@ -192,7 +192,7 @@ <function>sd_seat_get_sessions()</function>, <function>sd_seat_can_multi_session()</function>, <function>sd_seat_can_tty()</function> and - <function>sd_seat_can_grapical()</function> interfaces are + <function>sd_seat_can_graphical()</function> interfaces are available as a shared library, which can be compiled and linked to with the <constant>libsystemd</constant> <citerefentry project='die-net'><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry> diff --git a/man/systemctl.xml b/man/systemctl.xml index 1fb056874c..cce7861139 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -683,14 +683,11 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service <para>Start (activate) one or more units specified on the command line.</para> - <para>Note that glob patterns operate on a list of currently - loaded units. Units which are not active and are not in a - failed state usually are not loaded, and would not be - matched by any pattern. In addition, in case of - instantiated units, systemd is often unaware of the - instance name until the instance has been started. Therefore, - using glob patterns with <command>start</command> - has limited usefulness.</para> + <para>Note that glob patterns operate on the set of primary names of currently loaded units. Units which + are not active and are not in a failed state usually are not loaded, and will not be matched by any + pattern. In addition, in case of instantiated units, systemd is often unaware of the instance name until + the instance has been started. Therefore, using glob patterns with <command>start</command> has limited + usefulness. Also, secondary alias names of units are not considered.</para> </listitem> </varlistentry> <varlistentry> @@ -736,9 +733,9 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service <listitem> <para>Restart one or more units specified on the command line if the units are running. This does nothing if units are not - running. Note that, for compatibility with Red Hat init - scripts, <command>condrestart</command> is equivalent to this - command.</para> + running.</para> + <!-- Note that we don't document condrestart here, as that is just compatibility support, and we generally + don't document that. --> </listitem> </varlistentry> <varlistentry> @@ -751,14 +748,14 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service </listitem> </varlistentry> <varlistentry> - <term><command>reload-or-try-restart <replaceable>PATTERN</replaceable>...</command></term> + <term><command>try-reload-or-restart <replaceable>PATTERN</replaceable>...</command></term> <listitem> <para>Reload one or more units if they support it. If not, restart them instead. This does nothing if the units are not - running. Note that, for compatibility with SysV init scripts, - <command>force-reload</command> is equivalent to this - command.</para> + running.</para> + <!-- Note that we don't document force-reload here, as that is just compatibility support, and we generally + don't document that. --> </listitem> </varlistentry> <varlistentry> @@ -832,7 +829,7 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service output. If you are looking for computer-parsable output, use <command>show</command> instead. By default, this function only shows 10 lines of output and ellipsizes - lines to fit in the terminal window. This can be changes + lines to fit in the terminal window. This can be changed with <option>--lines</option> and <option>--full</option>, see above. In addition, <command>journalctl --unit=<replaceable>NAME</replaceable></command> or @@ -1137,7 +1134,7 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service <tbody> <row> <entry><literal>enabled</literal></entry> - <entry morerows='1'>Enabled through a symlink in a <filename>.wants/</filename> or <filename>.requires/</filename> subdirectory of <filename>/etc/systemd/system/</filename> (persistently) or <filename>/run/systemd/system/</filename> (transiently).</entry> + <entry morerows='1'>Enabled through creating symlinks encoded in the <literal>[Install]</literal> section (permanently or just in <filename>/run</filename>).</entry> <entry morerows='1'>0</entry> </row> <row> @@ -1176,7 +1173,7 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service </row> <row> <entry><literal>bad</literal></entry> - <entry>Unit file is invalid or another error occured. Note that <command>is-enabled</command> will not actually return this state, but print an error message instead. However the unit file listing printed by <command>list-unit-files</command> might show it.</entry> + <entry>Unit file is invalid or another error occurred. Note that <command>is-enabled</command> will not actually return this state, but print an error message instead. However the unit file listing printed by <command>list-unit-files</command> might show it.</entry> <entry>> 0</entry> </row> </tbody> @@ -1724,11 +1721,10 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service are equivalent to: <programlisting># systemctl status dev-sda.device # systemctl status home.mount</programlisting> - In the second case, shell-style globs will be matched against - currently loaded units; literal unit names, with or without - a suffix, will be treated as in the first case. This means that - literal unit names always refer to exactly one unit, but globs - may match zero units and this is not considered an error.</para> + In the second case, shell-style globs will be matched against the primary names of all currently loaded units; + literal unit names, with or without a suffix, will be treated as in the first case. This means that literal unit + names always refer to exactly one unit, but globs may match zero units and this is not considered an + error.</para> <para>Glob patterns use <citerefentry project='man-pages'><refentrytitle>fnmatch</refentrytitle><manvolnum>3</manvolnum></citerefentry>, @@ -1736,11 +1732,12 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service <literal>*</literal>, <literal>?</literal>, <literal>[]</literal> may be used. See <citerefentry project='man-pages'><refentrytitle>glob</refentrytitle><manvolnum>7</manvolnum></citerefentry> - for more details. The patterns are matched against the names of + for more details. The patterns are matched against the primary names of currently loaded units, and patterns which do not match anything are silently skipped. For example: <programlisting># systemctl stop sshd@*.service</programlisting> - will stop all <filename>sshd@.service</filename> instances. + will stop all <filename>sshd@.service</filename> instances. Note that alias names of units, and units that aren't + loaded are not considered for glob expansion. </para> <para>For unit file commands, the specified diff --git a/man/systemd-activate.xml b/man/systemd-activate.xml index 5fe1a39057..c950a0836d 100644 --- a/man/systemd-activate.xml +++ b/man/systemd-activate.xml @@ -78,6 +78,9 @@ to <command>systemd-activate</command> will be passed through to the daemon, in the original positions. Other sockets specified with <option>--listen</option> will use consecutive descriptors. + By default, <command>systemd-activate</command> listens on a + stream socket, use <option>--datagram</option> to listen on + a datagram socket instead (see below). </para> </refsect1> @@ -104,6 +107,13 @@ </varlistentry> <varlistentry> + <term><option>-d</option></term> + <term><option>--datagram</option></term> + + <listitem><para>Listen on a datagram socket, instead of a stream socket.</para></listitem> + </varlistentry> + + <varlistentry> <term><option>-E <replaceable>VAR</replaceable><optional>=<replaceable>VALUE</replaceable></optional></option></term> <term><option>--setenv=<replaceable>VAR</replaceable><optional>=<replaceable>VALUE</replaceable></optional></option></term> diff --git a/man/systemd-ask-password.xml b/man/systemd-ask-password.xml index 6fb322e849..2a4d24349b 100644 --- a/man/systemd-ask-password.xml +++ b/man/systemd-ask-password.xml @@ -149,7 +149,7 @@ possible to cache multiple passwords under the same keyname, in which case they will be stored as NUL-separated list of passwords. Use - <citerefentry><refentrytitle>keyctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> + <citerefentry project='die-net'><refentrytitle>keyctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> to access the cached key via the kernel keyring directly. Example: <literal>--keyname=cryptsetup</literal></para></listitem> </varlistentry> @@ -209,7 +209,7 @@ <para> <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, - <citerefentry><refentrytitle>keyctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry project='die-net'><refentrytitle>keyctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, <citerefentry project='die-net'><refentrytitle>plymouth</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry project='man-pages'><refentrytitle>wall</refentrytitle><manvolnum>1</manvolnum></citerefentry> </para> diff --git a/man/systemd-detect-virt.xml b/man/systemd-detect-virt.xml index 5d19322cdc..2b7f4e69ab 100644 --- a/man/systemd-detect-virt.xml +++ b/man/systemd-detect-virt.xml @@ -59,7 +59,7 @@ <para><command>systemd-detect-virt</command> detects execution in a virtualized environment. It identifies the virtualization - technology and can distinguish full VM virtualization from + technology and can distinguish full machine virtualization from container virtualization. <filename>systemd-detect-virt</filename> exits with a return value of 0 (success) if a virtualization technology is detected, and non-zero (error) otherwise. By default, @@ -88,7 +88,7 @@ </thead> <tbody> <row> - <entry morerows="9">VM</entry> + <entry valign="top" morerows="9">VM</entry> <entry><varname>qemu</varname></entry> <entry>QEMU software virtualization</entry> </row> @@ -139,7 +139,7 @@ </row> <row> - <entry morerows="5">Container</entry> + <entry valign="top" morerows="5">Container</entry> <entry><varname>openvz</varname></entry> <entry>OpenVZ/Virtuozzo</entry> </row> @@ -173,8 +173,8 @@ </table> <para>If multiple virtualization solutions are used, only the - "innermost" is detected and identified. That means if both VM - virtualization and container virtualization are used in + "innermost" is detected and identified. That means if both + machine and container virtualization are used in conjunction, only the latter will be identified (unless <option>--vm</option> is passed).</para> </refsect1> @@ -197,8 +197,7 @@ <term><option>-v</option></term> <term><option>--vm</option></term> - <listitem><para>Only detects VM virtualization (i.e. full - hardware virtualization).</para></listitem> + <listitem><para>Only detects hardware virtualization).</para></listitem> </varlistentry> <varlistentry> diff --git a/man/systemd-escape.xml b/man/systemd-escape.xml index 5407773f23..dbb3869a24 100644 --- a/man/systemd-escape.xml +++ b/man/systemd-escape.xml @@ -64,7 +64,7 @@ used to escape and to undo escaping of strings.</para> <para>The command takes any number of strings on the command line, - and will process them individually, one after the other. It will + and will process them individually, one after another. It will output them separated by spaces to stdout.</para> <para>By default, this command will escape the strings passed, diff --git a/man/systemd-journal-gatewayd.service.xml b/man/systemd-journal-gatewayd.service.xml index 6df2248578..e32ac26850 100644 --- a/man/systemd-journal-gatewayd.service.xml +++ b/man/systemd-journal-gatewayd.service.xml @@ -193,7 +193,7 @@ </varlistentry> <varlistentry> - <term><constant>application/event-stream</constant></term> + <term><constant>text/event-stream</constant></term> <listitem><para>Entries are formatted as JSON data structures, wrapped in a format suitable for <ulink diff --git a/man/systemd-journal-remote.xml b/man/systemd-journal-remote.xml index ebaca2657f..3899f175d4 100644 --- a/man/systemd-journal-remote.xml +++ b/man/systemd-journal-remote.xml @@ -293,15 +293,24 @@ journalctl -o export | systemd-journal-remote -o /tmp/dir - </programlisting> </para> - <para>Retrieve events from a remote + <para>Retrieve all available events from a remote <citerefentry><refentrytitle>systemd-journal-gatewayd</refentrytitle><manvolnum>8</manvolnum></citerefentry> instance and store them in - <filename>/var/log/journal/some.host/remote-some~host.journal</filename>: + <filename>/var/log/journal/remote/remote-some.host.journal</filename>: <programlisting> systemd-journal-remote --url http://some.host:19531/ </programlisting> </para> - </refsect1> + + <para>Retrieve current boot events and wait for new events from a remote + <citerefentry><refentrytitle>systemd-journal-gatewayd</refentrytitle><manvolnum>8</manvolnum></citerefentry> + instance, and store them in + <filename>/var/log/journal/remote/remote-some.host.journal</filename>: + <programlisting> +systemd-journal-remote --url http://some.host:19531/entries?boot&follow + </programlisting> + </para> +</refsect1> <refsect1> <title>See Also</title> diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index a97b7c44eb..86cdb4e124 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -249,15 +249,75 @@ </varlistentry> <varlistentry> + <term><option>-a</option></term> + <term><option>--as-pid2</option></term> + + <listitem><para>Invoke the shell or specified program as process ID (PID) 2 instead of PID 1 (init). By + default, if neither this option nor <option>--boot</option> is used, the selected binary is run as process with + PID 1, a mode only suitable for programs that are aware of the special semantics that the process with PID 1 + has on UNIX. For example, it needs to reap all processes reparented to it, and should implement + <command>sysvinit</command> compatible signal handling (specifically: it needs to reboot on SIGINT, reexecute + on SIGTERM, reload configuration on SIGHUP, and so on). With <option>--as-pid2</option> a minimal stub init + process is run as PID 1 and the selected binary is executed as PID 2 (and hence does not need to implement any + special semantics). The stub init process will reap processes as necessary and react appropriately to + signals. It is recommended to use this mode to invoke arbitrary commands in containers, unless they have been + modified to run correctly as PID 1. Or in other words: this switch should be used for pretty much all commands, + except when the command refers to an init or shell implementation, as these are generally capable of running + correctly as PID 1). This option may not be combined with <option>--boot</option> or + <option>--share-system</option>.</para> + </listitem> + </varlistentry> + + <varlistentry> <term><option>-b</option></term> <term><option>--boot</option></term> - <listitem><para>Automatically search for an init binary and - invoke it instead of a shell or a user supplied program. If - this option is used, arguments specified on the command line - are used as arguments for the init binary. This option may not - be combined with <option>--share-system</option>. - </para></listitem> + <listitem><para>Automatically search for an init binary and invoke it as PID 1, instead of a shell or a user + supplied program. If this option is used, arguments specified on the command line are used as arguments for the + init binary. This option may not be combined with <option>--as-pid2</option> or + <option>--share-system</option>.</para> + + <para>The following table explains the different modes of invocation and relationship to + <option>--as-pid2</option> (see above):</para> + + <table> + <title>Invocation Mode</title> + <tgroup cols='2' align='left' colsep='1' rowsep='1'> + <colspec colname="switch" /> + <colspec colname="explanation" /> + <thead> + <row> + <entry>Switch</entry> + <entry>Explanation</entry> + </row> + </thead> + <tbody> + <row> + <entry>Neither <option>--as-pid2</option> nor <option>--boot</option> specified</entry> + <entry>The passed parameters are interpreted as command line, which is executed as PID 1 in the container.</entry> + </row> + + <row> + <entry><option>--as-pid2</option> specified</entry> + <entry>The passed parameters are interpreted as command line, which are executed as PID 2 in the container. A stub init process is run as PID 1.</entry> + </row> + + <row> + <entry><option>--boot</option> specified</entry> + <entry>An init binary as automatically searched and run as PID 1 in the container. The passed parameters are used as invocation parameters for this process.</entry> + </row> + + </tbody> + </tgroup> + </table> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>--chdir=</option></term> + + <listitem><para>Change to the specified working directory before invoking the process in the container. Expects + an absolute path in the container's file system namespace.</para></listitem> </varlistentry> <varlistentry> @@ -770,8 +830,8 @@ <option>yes</option>, full volatile mode is enabled. This means the root directory is mounted as a mostly unpopulated <literal>tmpfs</literal> instance, and - <filename>/usr</filename> from the OS tree is mounted into it, - read-only (the system thus starts up with read-only OS + <filename>/usr</filename> from the OS tree is mounted into it + in read-only mode (the system thus starts up with read-only OS resources, but pristine state and configuration, any changes to the either are lost on shutdown). When the mode parameter is specified as <option>state</option>, the OS tree is diff --git a/man/systemd-path.xml b/man/systemd-path.xml index da6026e3b3..e2b23eec51 100644 --- a/man/systemd-path.xml +++ b/man/systemd-path.xml @@ -60,7 +60,7 @@ <para><command>systemd-path</command> may be used to query system and user paths. The tool makes many of the paths described in <citerefentry><refentrytitle>file-hierarchy</refentrytitle><manvolnum>7</manvolnum></citerefentry> - queriable.</para> + available for querying.</para> <para>When invoked without arguments, a list of known paths and their current values is shown. When at least one argument is diff --git a/man/systemd-resolve.xml b/man/systemd-resolve.xml new file mode 100644 index 0000000000..f1e663c5bb --- /dev/null +++ b/man/systemd-resolve.xml @@ -0,0 +1,277 @@ +<?xml version='1.0'?> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" +"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<!-- + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +--> + +<refentry id="systemd-resolve" + xmlns:xi="http://www.w3.org/2001/XInclude"> + + <refentryinfo> + <title>systemd-resolve</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Developer</contrib> + <firstname>Lennart</firstname> + <surname>Poettering</surname> + <email>lennart@poettering.net</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>systemd-resolve</refentrytitle> + <manvolnum>1</manvolnum> + </refmeta> + + <refnamediv> + <refname>systemd-resolve</refname> + <refpurpose>Resolve domain names, IPV4 and IPv6 addresses, DNS resource records, and services</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <cmdsynopsis> + <command>systemd-resolve</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <arg choice="plain" rep="repeat"><replaceable>HOSTNAME</replaceable></arg> + </cmdsynopsis> + + <cmdsynopsis> + <command>systemd-resolve</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <arg choice="plain" rep="repeat"><replaceable>ADDRESS</replaceable></arg> + </cmdsynopsis> + + <cmdsynopsis> + <command>systemd-resolve</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <command> --type=<replaceable>TYPE</replaceable></command> + <arg choice="plain" rep="repeat"><replaceable>RRDOMAIN</replaceable></arg> + </cmdsynopsis> + + <cmdsynopsis> + <command>systemd-resolve</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <command> --service</command> + <arg choice="plain"><arg choice="opt"><arg choice="opt"><replaceable>NAME</replaceable></arg> + <replaceable>TYPE</replaceable></arg> <replaceable>DOMAIN</replaceable></arg> + </cmdsynopsis> + + <cmdsynopsis> + <command>systemd-resolve</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <command> --statistics</command> + </cmdsynopsis> + + <cmdsynopsis> + <command>systemd-resolve</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <command> --reset-statistics</command> + </cmdsynopsis> + + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><command>systemd-resolve</command> may be used to resolve domain names, IPv4 and IPv6 addresses, DNS resource + records and services with the + <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> + resolver service. By default, the specified list of parameters will be resolved as hostnames, retrieving their IPv4 + and IPv6 addresses. If the parameters specified are formatted as IPv4 or IPv6 operation the reverse operation is + done, and a hostname is retrieved for the specified addresses.</para> + + <para>The <option>--type=</option> switch may be used to specify a DNS resource record type (A, AAAA, SOA, MX, ...) in + order to request a specific DNS resource record, instead of the address or reverse address lookups. + The special value <literal>help</literal> may be used to list known values.</para> + + <para>The <option>--service</option> switch may be used to resolve <ulink + url="https://tools.ietf.org/html/rfc2782">SRV</ulink> and <ulink + url="https://tools.ietf.org/html/rfc6763">DNS-SD</ulink> services (see below). In this mode, between one and three + arguments are required. If three parameters are passed the first is assumed to be the DNS-SD service name, the + second the SRV service type, and the third the domain to search in. In this case a full DNS-SD style SRV and TXT + lookup is executed. If only two parameters are specified, the first is assumed to be the SRV service type, and the + second the domain to look in. In this case no TXT RR is requested. Finally, if only one parameter is specified, it + is assumed to be a domain name, that is already prefixed with an SRV type, and an SRV lookup is done (no + TXT).</para> + + <para>The <option>--statistics</option> switch may be used to show resolver statistics, including information about + the number of succesful and failed DNSSEC validations.</para> + + <para>The <option>--reset-statistics</option> may be used to reset various statistics counters maintained the + resolver, including those shown in the <option>--statistics</option> output. This operation requires root + privileges.</para> + </refsect1> + + <refsect1> + <title>Options</title> + <variablelist> + <varlistentry> + <term><option>-4</option></term> + <term><option>-6</option></term> + + <listitem><para>By default, when resolving a hostname, both IPv4 and IPv6 + addresses are acquired. By specifying <option>-4</option> only IPv4 addresses are requested, by specifying + <option>-6</option> only IPv6 addresses are requested.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>-i</option> <replaceable>INTERFACE</replaceable></term> + <term><option>--interface=</option><replaceable>INTERFACE</replaceable></term> + + <listitem><para>Specifies the network interface to execute the query on. This may either be specified as numeric + interface index or as network interface string (e.g. <literal>en0</literal>). Note that this option has no + effect if system-wide DNS configuration (as configured in <filename>/etc/resolv.conf</filename> or + <filename>/etc/systemd/resolve.conf</filename>) in place of per-link configuration is used.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>-p</option> <replaceable>PROTOCOL</replaceable></term> + <term><option>--protocol=</option><replaceable>PROTOCOL</replaceable></term> + + <listitem><para>Specifies the network protocol for the query. May be one of <literal>dns</literal> + (i.e. classic unicast DNS), <literal>llmnr</literal> (<ulink + url="https://tools.ietf.org/html/rfc4795">Link-Local Multicast Name Resolution</ulink>), + <literal>llmr-ipv4</literal>, <literal>llmnr-ipv6</literal> (LLMNR via the indicated underlying IP + protocols). By default the lookup is done via all protocols suitable for the lookup. If used, limits the set of + protocols that may be used. Use this option multiple times to enable resolving via multiple protocols at the + same time. The setting <literal>llmnr</literal> is identical to specifying this switch once with + <literal>llmnr-ipv4</literal> and once via <literal>llmnr-ipv6</literal>. Note that this option does not force + the service to resolve the operation with the specified protocol, as that might require a suitable network + interface and configuration. + The special value <literal>help</literal> may be used to list known values. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><option>-t</option> <replaceable>TYPE</replaceable></term> + <term><option>--type=</option><replaceable>TYPE</replaceable></term> + <term><option>-c</option> <replaceable>CLASS</replaceable></term> + <term><option>--class=</option><replaceable>CLASS</replaceable></term> + + <listitem><para>Specifies the DNS resource record type (e.g. A, AAAA, MX, …) and class (e.g. IN, ANY, …) to + look up. If these options are used a DNS resource record set matching the specified class and type is + requested. The class defaults to IN if only a type is specified. + The special value <literal>help</literal> may be used to list known values. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--service</option></term> + + <listitem><para>Enables service resolution. This enables DNS-SD and simple SRV service resolution, depending + on the specified list of parameters (see above).</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--service-address=</option><replaceable>BOOL</replaceable></term> + + <listitem><para>Takes a boolean parameter. If true (the default), when doing a service lookup with + <option>--service</option> the hostnames contained in the SRV resource records are resolved as well.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--service-txt=</option><replaceable>BOOL</replaceable></term> + + <listitem><para>Takes a boolean parameter. If true (the default), when doing a DNS-SD service lookup with + <option>--service</option> the TXT service metadata record is resolved as well.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--cname=</option><replaceable>BOOL</replaceable></term> + + <listitem><para>Takes a boolean parameter. If true (the default), DNS CNAME or DNAME redirections are + followed. Otherwise, if a CNAME or DNAME record is encountered while resolving, an error is + returned.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--search=</option><replaceable>BOOL</replaceable></term> + + <listitem><para>Takes a boolean parameter. If true (the default), any specified single-label hostnames will be + searched in the domains configured in the search domain list, if it is non-empty. Otherwise, the search domain + logic is disabled.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--legend=</option><replaceable>BOOL</replaceable></term> + + <listitem><para>Takes a boolean parameter. If true (the default), column headers and meta information about the + query response are shown. Otherwise, this output is suppressed.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--statistics</option></term> + + <listitem><para>If specified general resolver statistics are shown, including information whether DNSSEC is + enabled and available, as well as resolution and validation statistics.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--reset-statistics</option></term> + + <listitem><para>Resets the statistics counters shown in <option>--statistics</option> to zero.</para></listitem> + </varlistentry> + + <xi:include href="standard-options.xml" xpointer="help" /> + <xi:include href="standard-options.xml" xpointer="version" /> + </variablelist> + </refsect1> + + <refsect1> + <title>Examples</title> + + <example> + <title>Retrieve the addresses of the <literal>www.0pointer.net</literal> domain</title> + + <programlisting>$ systemd-resolve www.0pointer.net</programlisting> + </example> + + <example> + <title>Retrieve the domain of the <literal>85.214.157.71</literal> IP address</title> + + <programlisting>$ systemd-resolve 85.214.157.71</programlisting> + </example> + + <example> + <title>Retrieve the MX record of the <literal>0pointer.net</literal> domain</title> + + <programlisting>$ systemd-resolve -t MX 0pointer.net</programlisting> + </example> + + <example> + <title>Resolve an SRV service</title> + + <programlisting>$ systemd-resolve --service _xmpp-server._tcp gmail.com</programlisting> + </example> + + </refsect1> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> + </para> + </refsect1> +</refentry> diff --git a/man/systemd-resolved.service.xml b/man/systemd-resolved.service.xml index 43d568c6f7..4e144b5c98 100644 --- a/man/systemd-resolved.service.xml +++ b/man/systemd-resolved.service.xml @@ -56,15 +56,15 @@ <refsect1> <title>Description</title> - <para><command>systemd-resolved</command> is a system service that - manages network name resolution. It implements a caching DNS stub - resolver and an LLMNR resolver and responder. It also generates - <filename>/run/systemd/resolve/resolv.conf</filename> for - compatibility which may be symlinked from - <filename>/etc/resolv.conf</filename>. The glibc NSS module - <citerefentry><refentrytitle>nss-resolve</refentrytitle><manvolnum>8</manvolnum></citerefentry> - is necessary to allow libc's NSS resolver functions to resolve - host names via <command>systemd-resolved</command>.</para> + <para><command>systemd-resolved</command> is a system service that provides network name resolution to local + applications. It implements a caching and validating DNS/DNSSEC stub resolver, as well as an LLMNR resolver and + responder. In addition it maintains the <filename>/run/systemd/resolve/resolv.conf</filename> file for + compatibility with traditional Linux programs. This file may be symlinked from + <filename>/etc/resolv.conf</filename>.</para> + + <para>The glibc NSS module + <citerefentry><refentrytitle>nss-resolve</refentrytitle><manvolnum>8</manvolnum></citerefentry> is required to + permit glibc's NSS resolver functions to resolve host names via <command>systemd-resolved</command>.</para> <para>The DNS servers contacted are determined from the global settings in <filename>/etc/systemd/resolved.conf</filename>, the @@ -104,7 +104,7 @@ <itemizedlist> <listitem><para>Lookups for the special hostname <literal>localhost</literal> are never routed to the - network.</para></listitem> + network. (A few other, special domains are handled the same way.)</para></listitem> <listitem><para>Single-label names are routed to all local interfaces capable of IP multicasting, using the LLMNR @@ -117,7 +117,7 @@ <listitem><para>Multi-label names are routed to all local interfaces that have a DNS sever configured, plus the globally configured DNS server if there is one. Address lookups from the - link-local addres range are never routed to + link-local address range are never routed to DNS.</para></listitem> </itemizedlist> @@ -133,10 +133,8 @@ per-interface domains are exclusively routed to the matching interfaces.</para> - <para>Note that - <filename>/run/systemd/resolve/resolv.conf</filename> should not - be used directly, but only through a symlink from - <filename>/etc/resolv.conf</filename>.</para> + <para>Note that <filename>/run/systemd/resolve/resolv.conf</filename> should not be used directly by applications, + but only through a symlink from <filename>/etc/resolv.conf</filename>.</para> </refsect1> <refsect1> @@ -144,7 +142,10 @@ <para> <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, <citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>dnssec-trust-anchors.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>, <citerefentry><refentrytitle>nss-resolve</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-resolve</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry project='man-pages'><refentrytitle>resolv.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> </para> diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 5f98ef163c..f0f77c5091 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -807,6 +807,35 @@ </varlistentry> <varlistentry> + <term><varname>AmbientCapabilities=</varname></term> + + <listitem><para>Controls which capabilities to include in the + ambient capability set for the executed process. Takes a + whitespace-separated list of capability names as read by + <citerefentry project='mankier'><refentrytitle>cap_from_name</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + e.g. <constant>CAP_SYS_ADMIN</constant>, + <constant>CAP_DAC_OVERRIDE</constant>, + <constant>CAP_SYS_PTRACE</constant>. This option may appear more than + once in which case the ambient capability sets are merged. + If the list of capabilities is prefixed with <literal>~</literal>, all + but the listed capabilities will be included, the effect of the + assignment inverted. If the empty string is + assigned to this option, the ambient capability set is reset to + the empty capability set, and all prior settings have no effect. + If set to <literal>~</literal> (without any further argument), the + ambient capability set is reset to the full set of available + capabilities, also undoing any previous settings. Note that adding + capabilities to ambient capability set adds them to the process's + inherited capability set. + </para><para> + Ambient capability sets are useful if you want to execute a process + as a non-privileged user but still want to give it some capabilities. + Note that in this case option <constant>keep-caps</constant> is + automatically added to <varname>SecureBits=</varname> to retain the + capabilities over the user change.</para></listitem> + </varlistentry> + + <varlistentry> <term><varname>SecureBits=</varname></term> <listitem><para>Controls the secure bits set for the executed process. Takes a space-separated combination of options from diff --git a/man/systemd.generator.xml b/man/systemd.generator.xml index b36aab3259..62658fb115 100644 --- a/man/systemd.generator.xml +++ b/man/systemd.generator.xml @@ -315,7 +315,7 @@ </example> <example> - <title>Debuging a generator</title> + <title>Debugging a generator</title> <programlisting>dir=$(mktemp -d) SYSTEMD_LOG_LEVEL=debug &systemgeneratordir;/systemd-fstab-generator \ diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml index 16e41e05b3..b697d0c9a6 100644 --- a/man/systemd.netdev.xml +++ b/man/systemd.netdev.xml @@ -493,6 +493,25 @@ VXLAN Group Policy </ulink> document. Defaults to false.</para> </listitem> </varlistentry> + <varlistentry> + <term><varname>DestinationPort=</varname></term> + <listitem> + <para>Configures the default destination UDP port on a per-device basis. + If destination port is not specified then Linux kernel default will be used. + Set destination port 4789 to get the IANA assigned value, + and destination port 0 to get default values.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><varname>PortRange=</varname></term> + <listitem> + <para>Configures VXLAN port range. VXLAN bases source + UDP port based on flow to help the receiver to be able + to load balance based on outer header flow. It + restricts the port range to the normal UDP local + ports, and allows overriding via configuration.</para> + </listitem> + </varlistentry> </variablelist> </refsect1> <refsect1> diff --git a/man/systemd.network.xml b/man/systemd.network.xml index e6dedb027d..f88751b672 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -228,7 +228,7 @@ <literal>ipv4</literal>, or <literal>ipv6</literal>.</para> <para>Note that DHCPv6 will by default be triggered by Router - Advertisment, if that is enabled, regardless of this parameter. + Advertisement, if that is enabled, regardless of this parameter. By enabling DHCPv6 support explicitly, the DHCPv6 client will be started regardless of the presence of routers on the link, or what flags the routers pass. See @@ -277,10 +277,59 @@ <varlistentry> <term><varname>LLMNR=</varname></term> <listitem> - <para>A boolean or <literal>resolve</literal>. When true, enables - Link-Local Multicast Name Resolution on the link. When set to - <literal>resolve</literal>, only resolution is enabled, but not - announcement. Defaults to true.</para> + <para>A boolean or <literal>resolve</literal>. When true, + enables <ulink + url="https://tools.ietf.org/html/rfc4795">Link-Local + Multicast Name Resolution</ulink> on the link. When set to + <literal>resolve</literal>, only resolution is enabled, + but not host registration and announcement. Defaults to + true. This setting is read by + <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><varname>MulticastDNS=</varname></term> + <listitem> + <para>A boolean or <literal>resolve</literal>. When true, + enables <ulink + url="https://tools.ietf.org/html/rfc6762">Multicast + DNS</ulink> support on the link. When set to + <literal>resolve</literal>, only resolution is enabled, + but not host or service registration and + announcement. Defaults to false. This setting is read by + <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><varname>DNSSEC=</varname></term> + <listitem> + <para>A boolean or + <literal>allow-downgrade</literal>. When true, enables + <ulink + url="https://tools.ietf.org/html/rfc4033">DNSSEC</ulink> + DNS validation support on the link. When set to + <literal>allow-downgrade</literal>, compatibility with + non-DNSSEC capable networks is increased, by automatically + turning off DNSEC in this case. This option defines a + per-interface setting for + <citerefentry><refentrytitle>resolved.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>'s + global <varname>DNSSEC=</varname> option. Defaults to + false. This setting is read by + <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><varname>DNSSECNegativeTrustAnchors=</varname></term> + <listitem><para>A space-separated list of DNSSEC negative + trust anchor domains. If specified and DNSSEC is enabled, + look-ups done via the interface's DNS server will be subject + to the list of negative trust anchors, and not require + authentication for the specified domains, or anything below + it. Use this to disable DNSSEC authentication for specific + private domains, that cannot be proven valid using the + Internet DNS hierarchy. Defaults to the empty list. This + setting is read by + <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para> </listitem> </varlistentry> <varlistentry> @@ -346,19 +395,38 @@ <para>A DNS server address, which must be in the format described in <citerefentry project='man-pages'><refentrytitle>inet_pton</refentrytitle><manvolnum>3</manvolnum></citerefentry>. - This option may be specified more than once.</para> + This option may be specified more than once. This setting is read by + <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para> </listitem> </varlistentry> <varlistentry> <term><varname>Domains=</varname></term> <listitem> - <para>The domains used for DNS resolution over this link.</para> + <para>The domains used for DNS host name resolution on this link. Takes a list of DNS domain names which + are used as search suffixes for extending single-label host names (host names containing no dots) to become + fully qualified domain names (FQDNs). If a single-label host name is resolved on this interface, each of + the specified search domains are appended to it in turn, converting it into a fully qualified domain name, + until one of them may be successfully resolved.</para> + + <para>The specified domains are also used for routing of DNS queries: look-ups for host names ending in the + domains specified here are preferably routed to the DNS servers configured for this interface. If a domain + name is prefixed with <literal>~</literal>, the domain name becomes a pure "routing" domain, is used for + DNS query routing purposes only and is not used in the described domain search logic. By specifying a + routing domain of <literal>~.</literal> (the tilda indicating definition of a routing domain, the dot + referring to the DNS root domain which is the implied suffix of all valid DNS names) it is possible to + route all DNS traffic preferably to the DNS server specified for this interface. The route domain logic is + particularly useful on multi-homed hosts with DNS servers serving particular private DNS zones on each + interface.</para> + + <para>This setting is read by + <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para> </listitem> </varlistentry> <varlistentry> <term><varname>NTP=</varname></term> <listitem> - <para>An NTP server address. This option may be specified more than once.</para> + <para>An NTP server address. This option may be specified more than once. This setting is read by + <citerefentry><refentrytitle>systemd-timesyncd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para> </listitem> </varlistentry> <varlistentry> @@ -649,15 +717,20 @@ <varlistentry> <term><varname>UseDomains=</varname></term> <listitem> - <para>When true (not the default), the domain name - received from the DHCP server will be used for DNS - resolution over this link. When a name cannot be resolved - as specified, the domain name will be used a suffix and - name resolution of that will be attempted.</para> + <para>Takes a boolean argument, or a the special value <literal>route</literal>. When true, the domain name + received from the DHCP server will be used as DNS search domain over this link, similar to the effect of + the <option>Domains=</option> setting. If set to <literal>route</literal>, the domain name received from + the DHCP server will be used for routing DNS queries only, but not for searching, similar to the effect of + the <option>Domains=</option> setting when the argument is prefixed with <literal>~</literal>. Defaults to + false.</para> + + <para>It is recommended to enable this option only on trusted networks, as setting this affects resolution + of all host names, in particular to single-label names. It is generally safer to use the supplied domain + only as routing domain, rather than as search domain, in order to not have it affect local resolution of + single-label names.</para> - <para>This corresponds to the <option>domain</option> - option in <citerefentry project='man-pages'><refentrytitle>resolv.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry> - and should not be enabled on untrusted networks.</para> + <para>When set to true, this setting corresponds to the <option>domain</option> option in <citerefentry + project='man-pages'><refentrytitle>resolv.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para> </listitem> </varlistentry> <varlistentry> @@ -673,7 +746,7 @@ <term><varname>UseTimezone=</varname></term> <listitem><para>When true, the timezone received from the - DHCP server will be set as as timezone of the local + DHCP server will be set as timezone of the local system. Defaults to <literal>no</literal>.</para></listitem> </varlistentry> @@ -1011,9 +1084,10 @@ DHCP=yes <title>See Also</title> <para> <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, - <citerefentry><refentrytitle>systemd-networkd</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd.link</refentrytitle><manvolnum>5</manvolnum></citerefentry>, - <citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry> + <citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> </para> </refsect1> diff --git a/man/systemd.nspawn.xml b/man/systemd.nspawn.xml index e952688331..c07a4b0243 100644 --- a/man/systemd.nspawn.xml +++ b/man/systemd.nspawn.xml @@ -141,15 +141,21 @@ <varlistentry> <term><varname>Boot=</varname></term> - <listitem><para>Takes a boolean argument, which defaults to off. If - enabled, <command>systemd-nspawn</command> will automatically - search for an <filename>init</filename> executable and invoke - it. In this case, the specified parameters using - <varname>Parameters=</varname> are passed as additional - arguments to the <filename>init</filename> process. This - setting corresponds to the <option>--boot</option> switch on - the <command>systemd-nspawn</command> command - line. </para></listitem> + <listitem><para>Takes a boolean argument, which defaults to off. If enabled, <command>systemd-nspawn</command> + will automatically search for an <filename>init</filename> executable and invoke it. In this case, the + specified parameters using <varname>Parameters=</varname> are passed as additional arguments to the + <filename>init</filename> process. This setting corresponds to the <option>--boot</option> switch on the + <command>systemd-nspawn</command> command line. This option may not be combined with + <varname>ProcessTwo=yes</varname>.</para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>ProcessTwo=</varname></term> + + <listitem><para>Takes a boolean argument, which defaults to off. If enabled, the specified program is run as + PID 2. A stub init process is run as PID 1. This setting corresponds to the <option>--as-pid2</option> switch + on the <command>systemd-nspawn</command> command line. This option may not be combined with + <varname>Boot=yes</varname>.</para></listitem> </varlistentry> <varlistentry> @@ -187,12 +193,20 @@ </varlistentry> <varlistentry> + <term><varname>WorkingDirectory=</varname></term> + + <listitem><para>Selects the working directory for the process invoked in the container. Expects an absolute + path in the container's file system namespace. This corresponds to the <option>--chdir=</option> command line + switch.</para></listitem> + </varlistentry> + + <varlistentry> <term><varname>Capability=</varname></term> <term><varname>DropCapability=</varname></term> <listitem><para>Takes a space-separated list of Linux process capabilities (see - <citerefentry><refentrytitle>capabilities</refentrytitle><manvolnum>7</manvolnum></citerefentry> + <citerefentry project='man-pages'><refentrytitle>capabilities</refentrytitle><manvolnum>7</manvolnum></citerefentry> for details). The <varname>Capability=</varname> setting specifies additional capabilities to pass on top of the default set of capabilities. The diff --git a/man/systemd.resource-control.xml b/man/systemd.resource-control.xml index b1106c759d..b6b38fde58 100644 --- a/man/systemd.resource-control.xml +++ b/man/systemd.resource-control.xml @@ -459,6 +459,12 @@ this setting is the parent slice. Since the name of a slice unit implies the parent slice, it is hence redundant to ever set this parameter directly for slice units.</para> + + <para>Special care should be taken when relying on the default slice assignment in templated service units + that have <varname>DefaultDependencies=no</varname> set, see + <citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>, section + "Automatic Dependencies" for details.</para> + </listitem> </varlistentry> diff --git a/man/systemd.service.xml b/man/systemd.service.xml index b998a1f81f..4cd36ac70e 100644 --- a/man/systemd.service.xml +++ b/man/systemd.service.xml @@ -113,6 +113,16 @@ involved with early boot or late system shutdown should disable this option.</para> + <para>Instanced service units (i.e. service units with an <literal>@</literal> in their name) are assigned by + default a per-template slice unit (see + <citerefentry><refentrytitle>systemd.slice</refentrytitle><manvolnum>5</manvolnum></citerefentry>), named after the + template unit, containing all instances of the specific template. This slice is normally stopped at shutdown, + together with all template instances. If that is not desired, set <varname>DefaultDependencies=no</varname> in the + template unit, and either define your own per-template slice unit file that also sets + <varname>DefaultDependencies=no</varname>, or set <varname>Slice=system.slice</varname> (or another suitable slice) + in the template unit. Also see + <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para> + <para>Additional implicit dependencies may be added as result of execution and resource control parameters as documented in <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> @@ -373,6 +383,11 @@ used to start long-running processes. All processes forked off by processes invoked via <varname>ExecStartPre=</varname> will be killed before the next service process is run.</para> + + <para>Note that if any of the commands specified in <varname>ExecStartPre=</varname>, + <varname>ExecStart=</varname>, or <varname>ExecStartPost=</varname> fail (and are not prefixed with + <literal>-</literal>, see above) or time out before the service is fully up, execution continues with commands + specified in <varname>ExecStopPost=</varname>, the commands in <varname>ExecStop=</varname> are skipped.</para> </listitem> </varlistentry> @@ -428,21 +443,36 @@ <constant>SIGKILL</constant> immediately after the command exited, this would not result in a clean stop. The specified command should hence be a synchronous operation, not an - asynchronous one.</para></listitem> + asynchronous one.</para> + + <para>Note that the commands specified in <varname>ExecStop=</varname> are only executed when the service + started successfuly first. They are not invoked if the service was never started at all, or in case its + start-up failed, for example because any of the commands specified in <varname>ExecStart=</varname>, + <varname>ExecStartPre=</varname> or <varname>ExecStartPost=</varname> failed (and weren't prefixed with + <literal>-</literal>, see above) or timed out. Use <varname>ExecStopPost=</varname> to invoke commands when a + service failed to start up correctly and is shut down again.</para> + + <para>It is recommended to use this setting for commands that communicate with the service requesting clean + termination. When the commands specified with this option are executed it should be assumed that the service is + still fully up and is able to react correctly to all commands. For post-mortem clean-up steps use + <varname>ExecStopPost=</varname> instead.</para></listitem> </varlistentry> <varlistentry> <term><varname>ExecStopPost=</varname></term> - <listitem><para>Additional commands that are executed after - the service was stopped. This includes cases where the - commands configured in <varname>ExecStop=</varname> were used, - where the service does not have any - <varname>ExecStop=</varname> defined, or where the service - exited unexpectedly. This argument takes multiple command - lines, following the same scheme as described for - <varname>ExecStart=</varname>. Use of these settings is - optional. Specifier and environment variable substitution is - supported.</para></listitem> + <listitem><para>Additional commands that are executed after the service is stopped. This includes cases where + the commands configured in <varname>ExecStop=</varname> were used, where the service does not have any + <varname>ExecStop=</varname> defined, or where the service exited unexpectedly. This argument takes multiple + command lines, following the same scheme as described for <varname>ExecStart=</varname>. Use of these settings + is optional. Specifier and environment variable substitution is supported. Note that – unlike + <varname>ExecStop=</varname> – commands specified with this setting are invoked when a service failed to start + up correctly and is shut down again.</para> + + <para>It is recommended to use this setting for clean-up operations that shall be executed even when the + service failed to start up correctly. Commands configured with this setting need to be able to operate even if + the service failed starting up half-way and left incompletely initialized data around. As the service's + processes have been terminated already when the commands specified with this setting are executed they should + not attempt to communicate with them.</para></listitem> </varlistentry> <varlistentry> @@ -460,7 +490,7 @@ configured time, the service will be considered failed and will be shut down again. Takes a unit-less value in seconds, or a time span value such as "5min 20s". Pass - <literal>0</literal> to disable the timeout logic. Defaults to + <literal>infinity</literal> to disable the timeout logic. Defaults to <varname>DefaultTimeoutStartSec=</varname> from the manager configuration file, except when <varname>Type=oneshot</varname> is used, in which case the @@ -479,7 +509,7 @@ <varname>KillMode=</varname> in <citerefentry><refentrytitle>systemd.kill</refentrytitle><manvolnum>5</manvolnum></citerefentry>). Takes a unit-less value in seconds, or a time span value such - as "5min 20s". Pass <literal>0</literal> to disable the + as "5min 20s". Pass <literal>infinity</literal> to disable the timeout logic. Defaults to <varname>DefaultTimeoutStopSec=</varname> from the manager configuration file (see @@ -496,6 +526,16 @@ </varlistentry> <varlistentry> + <term><varname>RuntimeMaxSec=</varname></term> + + <listitem><para>Configures a maximum time for the service to run. If this is used and the service has been + active for longer than the specified time it is terminated and put into a failure state. Note that this setting + does not have any effect on <varname>Type=oneshot</varname> services, as they terminate immediately after + activation completed. Pass <literal>infinity</literal> (the default) to configure no runtime + limit.</para></listitem> + </varlistentry> + + <varlistentry> <term><varname>WatchdogSec=</varname></term> <listitem><para>Configures the watchdog timeout for a service. The watchdog is activated when the start-up is completed. The diff --git a/man/systemd.special.xml b/man/systemd.special.xml index 54e7c49a9e..0a37f65956 100644 --- a/man/systemd.special.xml +++ b/man/systemd.special.xml @@ -92,6 +92,7 @@ <filename>shutdown.target</filename>, <filename>sigpwr.target</filename>, <filename>sleep.target</filename>, + <filename>slices.target</filename>, <filename>smartcard.target</filename>, <filename>sockets.target</filename>, <filename>sound.target</filename>, @@ -503,10 +504,23 @@ </listitem> </varlistentry> <varlistentry> + <term><filename>slices.target</filename></term> + <listitem> + <para>A special target unit that sets up all slice units (see + <citerefentry><refentrytitle>systemd.slice</refentrytitle><manvolnum>5</manvolnum></citerefentry> for + details) that shall be active after boot. By default the generic <filename>user.slice</filename>, + <filename>system.slice</filename>, <filename>machines.slice</filename> slice units, as well as the the root + slice unit <filename>-.slice</filename> are pulled in and ordered before this unit (see below).</para> + + <para>It's a good idea to add <varname>WantedBy=slices.target</varname> lines to the <literal>[Install]</literal> + section of all slices units that may be installed dynamically.</para> + </listitem> + </varlistentry> + <varlistentry> <term><filename>sockets.target</filename></term> <listitem> <para>A special target unit that sets up all socket - units.(see + units (see <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry> for details) that shall be active after boot.</para> @@ -587,7 +601,7 @@ <varlistentry> <term><filename>umount.target</filename></term> <listitem> - <para>A special target unit that umounts all mount and + <para>A special target unit that unmounts all mount and automount points on system shutdown.</para> <para>Mounts that shall be unmounted on system shutdown diff --git a/man/systemd.timer.xml b/man/systemd.timer.xml index cfa13015b0..29e235e2dc 100644 --- a/man/systemd.timer.xml +++ b/man/systemd.timer.xml @@ -284,7 +284,7 @@ unloaded. Turning this off is particularly useful for transient timer units that shall disappear after they first elapse. Note that this setting has an effect on repeatedly - starting the a timer unit that only elapses once: if + starting a timer unit that only elapses once: if <varname>RemainAfterElapse=</varname> is on, it will not be started again, and is guaranteed to elapse only once. However, if <varname>RemainAfterLeapse=</varname> is off, it might be diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 5b12378eda..a95c160954 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -191,6 +191,17 @@ <literal>.d/</literal> subdirectory and reads its <literal>.conf</literal> files.</para> + <para>In addition to <filename>/etc/systemd/system</filename>, + the drop-in <literal>.conf</literal> files for system services + can be placed in <filename>/usr/lib/systemd/system</filename> or + <filename>/run/systemd/system</filename> directories. Drop-in + files in <filename>/etc</filename> take precedence over those in + <filename>/run</filename> which in turn take precedence over + those in <filename>/usr/lib</filename>. Drop-in files under any of + these directories take precedence over unit files wherever located. + (Of course, since <filename>/run</filename> is temporary and + <filename>/usr/lib</filename> is for vendors, it is unlikely + drop-ins should be used in either of those places.)</para> <!-- Note that we do not document .include here, as we consider it mostly obsolete, and want people to use .d/ drop-ins instead. --> @@ -875,7 +886,7 @@ <para><varname>ConditionSecurity=</varname> may be used to check whether the given security module is enabled on the - system. Currently, the recognized values values are + system. Currently, the recognized values are <varname>selinux</varname>, <varname>apparmor</varname>, <varname>ima</varname>, @@ -918,7 +929,7 @@ <filename>/var</filename> on the next following boot. Units making use of this condition should order themselves before <citerefentry><refentrytitle>systemd-update-done.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>, - to make sure they run before the stamp files's modification + to make sure they run before the stamp file's modification time gets reset indicating a completed update.</para> <para><varname>ConditionFirstBoot=</varname> takes a boolean @@ -1402,6 +1413,7 @@ PrivateTmp=yes</programlisting> cannot be reset to an empty list, so dependencies can only be added in drop-ins. If you want to remove dependencies, you have to override the entire unit.</para> + </example> </refsect1> diff --git a/man/systemd.xml b/man/systemd.xml index 6de18f8294..b8d91b8943 100644 --- a/man/systemd.xml +++ b/man/systemd.xml @@ -255,6 +255,14 @@ <option>inherit</option>.</para></listitem> </varlistentry> + <varlistentry> + <term><option>--machine-id=</option></term> + + <listitem><para>Override the machine-id set on the hard drive, + useful for network booting or for containers. May not be set + to all zeros.</para></listitem> + </varlistentry> + <xi:include href="standard-options.xml" xpointer="help" /> <xi:include href="standard-options.xml" xpointer="version" /> </variablelist> @@ -827,6 +835,13 @@ </varlistentry> <varlistentry> + <term><varname>$SYSTEMD_COLORS</varname></term> + + <listitem><para>Controls whether colorized output should be generated. + </para></listitem> + </varlistentry> + + <varlistentry> <term><varname>$LISTEN_PID</varname></term> <term><varname>$LISTEN_FDS</varname></term> <term><varname>$LISTEN_FDNAMES</varname></term> @@ -977,6 +992,15 @@ </varlistentry> <varlistentry> + <term><varname>systemd.machine_id=</varname></term> + + <listitem><para>Takes a 32 character hex value to be + used for setting the machine-id. Intended mostly for + network booting where the same machine-id is desired + for every boot.</para></listitem> + </varlistentry> + + <varlistentry> <term><varname>quiet</varname></term> <listitem><para>Turn off status output at boot, much like diff --git a/man/sysusers.d.xml b/man/sysusers.d.xml index 42b53b2759..18ee3800d6 100644 --- a/man/sysusers.d.xml +++ b/man/sysusers.d.xml @@ -113,7 +113,7 @@ u root 0 "Superuser" /root</programlisting> <varlistentry> <term><varname>m</varname></term> <listitem><para>Add a user to a group. If the user or group - are not existing yet, they will be implicitly + do not exist yet, they will be implicitly created.</para></listitem> </varlistentry> diff --git a/man/timesyncd.conf.xml b/man/timesyncd.conf.xml index 10c2de89f6..8c86fd0074 100644 --- a/man/timesyncd.conf.xml +++ b/man/timesyncd.conf.xml @@ -68,6 +68,8 @@ <refsect1> <title>Options</title> + <para>The following settings are configured in the <literal>[Time]</literal> section:</para> + <variablelist class='network-directives'> <varlistentry> diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml index 5bf1f2956b..3b6b1e3f11 100644 --- a/man/tmpfiles.d.xml +++ b/man/tmpfiles.d.xml @@ -264,7 +264,8 @@ be removed and be replaced by the symlink. If the argument is omitted, symlinks to files with the same name residing in the directory <filename>/usr/share/factory/</filename> are - created.</para></listitem> + created. Note that permissions and ownership on symlinks + are ignored.</para></listitem> </varlistentry> <varlistentry> @@ -421,7 +422,7 @@ <command>systemd-tmpfiles</command> will automatically add the required base entries for user and group based on the access mode of the file, unless base entries already exist - or are explictly specified. The mask will be added if not + or are explicitly specified. The mask will be added if not specified explicitly or already present. Lines of this type accept shell-style globs in place of normal path names. This can be useful for allowing additional access to certain diff --git a/man/udev_device_new_from_syspath.xml b/man/udev_device_new_from_syspath.xml index 11db1a0fab..0bb71c8e91 100644 --- a/man/udev_device_new_from_syspath.xml +++ b/man/udev_device_new_from_syspath.xml @@ -134,7 +134,7 @@ a <filename>uevent</filename> file. <function>udev_device_new_from_devnum</function> takes a device type, which can be <constant>b</constant> for block devices or <constant>c</constant> for character devices, as well as a devnum (see - <citerefentry><refentrytitle>makedev</refentrytitle><manvolnum>3</manvolnum></citerefentry>). + <citerefentry project='man-pages'><refentrytitle>makedev</refentrytitle><manvolnum>3</manvolnum></citerefentry>). <function>udev_device_new_from_subsystem_sysname</function> looks up devices based on the provided subsystem and sysname (see <citerefentry><refentrytitle>udev_device_get_subsystem</refentrytitle><manvolnum>3</manvolnum></citerefentry> @@ -171,7 +171,7 @@ <para><function>udev_device_new_from_environment</function> creates a device from the current environment (see - <citerefentry><refentrytitle>environ</refentrytitle><manvolnum>7</manvolnum></citerefentry>). + <citerefentry project='man-pages'><refentrytitle>environ</refentrytitle><manvolnum>7</manvolnum></citerefentry>). Each key-value pair is interpreted in the same way as if it was received in an uevent (see <citerefentry><refentrytitle>udev_monitor_receive_device</refentrytitle><manvolnum>3</manvolnum></citerefentry>). @@ -189,7 +189,8 @@ <function>udev_device_new_from_device_id()</function> and <function>udev_device_new_from_environment()</function> return a pointer to the allocated udev device. On failure, - <constant>NULL</constant> is returned. + <constant>NULL</constant> is returned, + and <varname>errno</varname> is set appropriately. <function>udev_device_ref()</function> returns the argument that it was passed, unmodified. <function>udev_device_unref()</function> always returns @@ -1,14 +1,14 @@ # Hungarian translation of systemd -# Copyright (C) 2015. Free Software Foundation, Inc. +# Copyright (C) 2015, 2016. Free Software Foundation, Inc. # This file is distributed under the same license as the systemd package. # -# Gabor Kelemen <kelemeng at gnome dot hu>, 2015. +# Gabor Kelemen <kelemeng at gnome dot hu>, 2015, 2016. msgid "" msgstr "" "Project-Id-Version: systemd master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-11-22 16:37+0100\n" -"PO-Revision-Date: 2015-01-02 22:58+0100\n" +"POT-Creation-Date: 2016-01-02 13:41+0100\n" +"PO-Revision-Date: 2016-01-02 13:45+0100\n" "Last-Translator: Gabor Kelemen <kelemeng at ubuntu dot com>\n" "Language-Team: Hungarian <openscope at googlegroups dot com>\n" "Language: hu\n" @@ -29,15 +29,13 @@ msgstr "" "HitelesÃtés szükséges a bevitt jelmondat visszaküldéséhez a rendszernek." #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:3 -#, fuzzy msgid "Manage system services or other units" -msgstr "Rendszerszolgáltatások vagy -egységek kezelése" +msgstr "Rendszerszolgáltatások vagy más egységek kezelése" #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:4 -#, fuzzy msgid "Authentication is required to manage system services or other units." msgstr "" -"HitelesÃtés szükséges a rendszerszolgáltatások vagy -egységek kezeléséhez." +"HitelesÃtés szükséges a rendszerszolgáltatások vagy más egységek kezeléséhez." #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:5 msgid "Manage system service or unit files" @@ -51,14 +49,16 @@ msgstr "" #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:7 msgid "Set or unset system and service manager environment variables" msgstr "" +"Rendszer- és szolgáltatáskezelÅ‘ környezeti változóinak beállÃtása vagy " +"törlése" #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:8 -#, fuzzy msgid "" "Authentication is required to set or unset system and service manager " "environment variables." msgstr "" -"HitelesÃtés szükséges a rendszerszolgáltatás- vagy egységfájlok kezeléséhez." +"HitelesÃtés szükséges a rendszer- és szolgáltatáskezelÅ‘ környezeti " +"változóinak beállÃtásához vagy törléséhez." #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:9 msgid "Reload the systemd state" @@ -98,30 +98,27 @@ msgstr "HitelesÃtés szükséges a helyi gép információinak beállÃtásáho #: ../src/import/org.freedesktop.import1.policy.in.h:1 msgid "Import a VM or container image" -msgstr "" +msgstr "VM vagy konténer lemezkép importálása" #: ../src/import/org.freedesktop.import1.policy.in.h:2 -#, fuzzy msgid "Authentication is required to import a VM or container image" -msgstr "HitelesÃtés szükséges a bejelentkezéshez egy helyi konténerbe." +msgstr "HitelesÃtés szükséges a VM vagy konténer lemezkép importálásához." #: ../src/import/org.freedesktop.import1.policy.in.h:3 msgid "Export a VM or container image" -msgstr "" +msgstr "VM vagy konténer lemezkép exportálása" #: ../src/import/org.freedesktop.import1.policy.in.h:4 -#, fuzzy msgid "Authentication is required to export a VM or container image" -msgstr "HitelesÃtés szükséges a bejelentkezéshez egy helyi konténerbe." +msgstr "HitelesÃtés szükséges a VM vagy konténer lemezkép exportálásához." #: ../src/import/org.freedesktop.import1.policy.in.h:5 msgid "Download a VM or container image" -msgstr "" +msgstr "VM vagy konténer lemezkép letöltése" #: ../src/import/org.freedesktop.import1.policy.in.h:6 -#, fuzzy msgid "Authentication is required to download a VM or container image" -msgstr "HitelesÃtés szükséges a bejelentkezéshez egy helyi konténerbe." +msgstr "HitelesÃtés szükséges a VM vagy konténer lemezkép letöltéséhez." #: ../src/locale/org.freedesktop.locale1.policy.in.h:1 msgid "Set system locale" @@ -409,123 +406,113 @@ msgstr "" #: ../src/login/org.freedesktop.login1.policy.in.h:49 msgid "Manage active sessions, users and seats" -msgstr "" +msgstr "AktÃv munkamenetek, felhasználók és munkaállomások kezelése" #: ../src/login/org.freedesktop.login1.policy.in.h:50 -#, fuzzy msgid "" "Authentication is required for managing active sessions, users and seats." msgstr "" -"HitelesÃtés szükséges eszköz csatolásának engedélyezéséhez egy " -"munkaállomáshoz" +"HitelesÃtés szükséges az aktÃv munkamenetek, felhasználók és munkaállomások " +"kezeléséhez." #: ../src/login/org.freedesktop.login1.policy.in.h:51 msgid "Lock or unlock active sessions" -msgstr "" +msgstr "AktÃv munkamenetek zárolása vagy feloldása" #: ../src/login/org.freedesktop.login1.policy.in.h:52 -#, fuzzy msgid "Authentication is required to lock or unlock active sessions." -msgstr "HitelesÃtés szükséges a bejelentkezéshez egy helyi konténerbe." +msgstr "" +"HitelesÃtés szükséges az aktÃv munkamenetek zárolásához vagy feloldásához." #: ../src/login/org.freedesktop.login1.policy.in.h:53 msgid "Allow indication to the firmware to boot to setup interface" -msgstr "" +msgstr "A firmware-nek jelezhetÅ‘, hogy a beállÃtófelületet bootolja" #: ../src/login/org.freedesktop.login1.policy.in.h:54 -#, fuzzy msgid "" "Authentication is required to indicate to the firmware to boot to setup " "interface." -msgstr "HitelesÃtés szükséges a helyi gépnév beállÃtásához." +msgstr "" +"HitelesÃtés szükséges a firmware-nek jelzéshez, hogy a beállÃtófelületet " +"bootolja" #: ../src/login/org.freedesktop.login1.policy.in.h:55 msgid "Set a wall message" -msgstr "" +msgstr "Falüzenet beállÃtása" #: ../src/login/org.freedesktop.login1.policy.in.h:56 -#, fuzzy msgid "Authentication is required to set a wall message" -msgstr "HitelesÃtés szükséges a helyi gépnév beállÃtásához." +msgstr "HitelesÃtés szükséges a falüzenet beállÃtásához" #: ../src/machine/org.freedesktop.machine1.policy.in.h:1 msgid "Log into a local container" msgstr "Bejelentkezés helyi konténerbe" #: ../src/machine/org.freedesktop.machine1.policy.in.h:2 -#, fuzzy msgid "Authentication is required to log into a local container." msgstr "HitelesÃtés szükséges a bejelentkezéshez egy helyi konténerbe." #: ../src/machine/org.freedesktop.machine1.policy.in.h:3 -#, fuzzy msgid "Log into the local host" -msgstr "Bejelentkezés helyi konténerbe" +msgstr "Bejelentkezés a helyi gépre" #: ../src/machine/org.freedesktop.machine1.policy.in.h:4 -#, fuzzy msgid "Authentication is required to log into the local host." -msgstr "HitelesÃtés szükséges a bejelentkezéshez egy helyi konténerbe." +msgstr "HitelesÃtés szükséges a bejelentkezéshez a helyi gépre." #: ../src/machine/org.freedesktop.machine1.policy.in.h:5 -#, fuzzy msgid "Acquire a shell in a local container" -msgstr "Bejelentkezés helyi konténerbe" +msgstr "ParancsértelmezÅ‘ elérése helyi konténerben" #: ../src/machine/org.freedesktop.machine1.policy.in.h:6 -#, fuzzy msgid "Authentication is required to acquire a shell in a local container." -msgstr "HitelesÃtés szükséges a bejelentkezéshez egy helyi konténerbe." +msgstr "HitelesÃtés szükséges a parancsértelmezÅ‘ eléréséhez helyi konténerben." #: ../src/machine/org.freedesktop.machine1.policy.in.h:7 msgid "Acquire a shell on the local host" -msgstr "" +msgstr "ParancsértelmezÅ‘ elérése a helyi gépen" #: ../src/machine/org.freedesktop.machine1.policy.in.h:8 -#, fuzzy msgid "Authentication is required to acquire a shell on the local host." -msgstr "HitelesÃtés szükséges a helyi gépnév beállÃtásához." +msgstr "HitelesÃtés szükséges a parancsértelmezÅ‘ eléréséhez a helyi gépen." #: ../src/machine/org.freedesktop.machine1.policy.in.h:9 -#, fuzzy msgid "Acquire a pseudo TTY in a local container" -msgstr "Bejelentkezés helyi konténerbe" +msgstr "Pszeudoterminál elérése helyi konténerben" #: ../src/machine/org.freedesktop.machine1.policy.in.h:10 -#, fuzzy msgid "" "Authentication is required to acquire a pseudo TTY in a local container." -msgstr "HitelesÃtés szükséges a bejelentkezéshez egy helyi konténerbe." +msgstr "HitelesÃtés szükséges a pszeudoterminál eléréséhez helyi konténerben." #: ../src/machine/org.freedesktop.machine1.policy.in.h:11 msgid "Acquire a pseudo TTY on the local host" -msgstr "" +msgstr "Pszeudoterminál elérése helyi gépen" #: ../src/machine/org.freedesktop.machine1.policy.in.h:12 -#, fuzzy msgid "Authentication is required to acquire a pseudo TTY on the local host." -msgstr "HitelesÃtés szükséges a helyi gépnév beállÃtásához." +msgstr "HitelesÃtés szükséges a pszeudoterminál eléréséhez a helyi gépen." #: ../src/machine/org.freedesktop.machine1.policy.in.h:13 msgid "Manage local virtual machines and containers" -msgstr "" +msgstr "Virtuális gépek és konténerek kezelése" #: ../src/machine/org.freedesktop.machine1.policy.in.h:14 -#, fuzzy msgid "" "Authentication is required to manage local virtual machines and containers." -msgstr "HitelesÃtés szükséges a helyi gép információinak beállÃtásához." +msgstr "HitelesÃtés szükséges helyi virtuális gépek és konténerek kezeléséhez." #: ../src/machine/org.freedesktop.machine1.policy.in.h:15 msgid "Manage local virtual machine and container images" -msgstr "" +msgstr "Helyi virtuális gép és konténer lemezképek kezelése" #: ../src/machine/org.freedesktop.machine1.policy.in.h:16 -#, fuzzy msgid "" "Authentication is required to manage local virtual machine and container " "images." -msgstr "HitelesÃtés szükséges a helyi gép információinak beállÃtásához." +msgstr "" +"HitelesÃtés szükséges a helyi virtuális gép és konténer lemezképek " +"kezeléséhez." #: ../src/timedate/org.freedesktop.timedate1.policy.in.h:1 msgid "Set system time" @@ -565,37 +552,33 @@ msgid "" "shall be enabled." msgstr "HitelesÃtés szükséges a hálózati idÅ‘szinkronizáció engedélyezéséhez." -#: ../src/core/dbus-unit.c:428 -#, fuzzy +#: ../src/core/dbus-unit.c:449 msgid "Authentication is required to start '$(unit)'." -msgstr "HitelesÃtés szükséges a rendszeridÅ‘ beállÃtásához." +msgstr "HitelesÃtés szükséges a következÅ‘ elindÃtásához: „$(unit)â€." -#: ../src/core/dbus-unit.c:429 -#, fuzzy +#: ../src/core/dbus-unit.c:450 msgid "Authentication is required to stop '$(unit)'." -msgstr "HitelesÃtés szükséges a rendszeridÅ‘ beállÃtásához." +msgstr "HitelesÃtés szükséges a következÅ‘ leállÃtásához: „$(unit)â€." -#: ../src/core/dbus-unit.c:430 -#, fuzzy +#: ../src/core/dbus-unit.c:451 msgid "Authentication is required to reload '$(unit)'." -msgstr "HitelesÃtés szükséges a systemd állapotának újratöltéséhez." +msgstr "HitelesÃtés szükséges a következÅ‘ újratöltéséhez: „$(unit)â€." -#: ../src/core/dbus-unit.c:431 ../src/core/dbus-unit.c:432 -#, fuzzy +#: ../src/core/dbus-unit.c:452 ../src/core/dbus-unit.c:453 msgid "Authentication is required to restart '$(unit)'." -msgstr "HitelesÃtés szükséges a rendszeridÅ‘ beállÃtásához." +msgstr "HitelesÃtés szükséges a következÅ‘ újraindÃtásához: „$(unit)â€." -#: ../src/core/dbus-unit.c:535 -#, fuzzy +#: ../src/core/dbus-unit.c:556 msgid "Authentication is required to kill '$(unit)'." -msgstr "HitelesÃtés szükséges a bejelentkezéshez egy helyi konténerbe." +msgstr "HitelesÃtés szükséges a következÅ‘ kilövéséhez: „$(unit)â€." -#: ../src/core/dbus-unit.c:565 -#, fuzzy +#: ../src/core/dbus-unit.c:586 msgid "Authentication is required to reset the \"failed\" state of '$(unit)'." -msgstr "HitelesÃtés szükséges a helyi gépnév beállÃtásához." +msgstr "" +"HitelesÃtés szükséges a következÅ‘ „sikertelen†állapotának törléséhez: " +"„$(unit)â€." -#: ../src/core/dbus-unit.c:597 -#, fuzzy +#: ../src/core/dbus-unit.c:618 msgid "Authentication is required to set properties on '$(unit)'." -msgstr "HitelesÃtés szükséges a rendszeridÅ‘ beállÃtásához." +msgstr "" +"HitelesÃtés szükséges a következÅ‘ tulajdonságainak beállÃtásához: „$(unit)â€." @@ -1,13 +1,13 @@ # translation of ru.po to Rissian # Julia Dronova <juliette.tux@gmail.com>, 2013. -# Sergey Ptashnick <0comffdiz@inbox.ru>, 2013-2015. +# Sergey Ptashnick <0comffdiz@inbox.ru>, 2013-2016. # msgid "" msgstr "" "Project-Id-Version: systemd\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-11-22 16:37+0100\n" -"PO-Revision-Date: 2015-03-22 21:53+0300\n" +"PO-Revision-Date: 2016-02-02 20:22+0300\n" "Last-Translator: Sergey Ptashnick <0comffdiz@inbox.ru>\n" "Language: ru\n" "MIME-Version: 1.0\n" @@ -26,12 +26,10 @@ msgid "" msgstr "Чтобы отправить пароль ÑиÑтеме, необходимо пройти аутентификацию." #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:3 -#, fuzzy msgid "Manage system services or other units" msgstr "Управление ÑиÑтемными Ñлужбами и юнитами" #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:4 -#, fuzzy msgid "Authentication is required to manage system services or other units." msgstr "" "Ð”Ð»Ñ ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÑиÑтемными Ñлужбами и юнитами, необходимо пройти " @@ -456,23 +454,24 @@ msgstr "" #: ../src/login/org.freedesktop.login1.policy.in.h:53 msgid "Allow indication to the firmware to boot to setup interface" -msgstr "" +msgstr "Разрешить загрузку в режиме наÑтройки прошивки материнÑкой платы" #: ../src/login/org.freedesktop.login1.policy.in.h:54 -#, fuzzy msgid "" "Authentication is required to indicate to the firmware to boot to setup " "interface." -msgstr "Чтобы наÑтроить Ð¸Ð¼Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð°, необходимо пройти аутентификацию." +msgstr "" +"Чтобы разрешить загрузку в режиме наÑтройки прошивки материнÑкой платы, " +"необходимо пройти аутентификацию." #: ../src/login/org.freedesktop.login1.policy.in.h:55 msgid "Set a wall message" -msgstr "" +msgstr "Отправить Ñообщение на вÑе терминалы" #: ../src/login/org.freedesktop.login1.policy.in.h:56 -#, fuzzy msgid "Authentication is required to set a wall message" -msgstr "Чтобы наÑтроить Ð¸Ð¼Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð°, необходимо пройти аутентификацию." +msgstr "" +"Чтобы отправить Ñообщение на вÑе терминалы, необходимо пройти аутентификацию." #: ../src/machine/org.freedesktop.machine1.policy.in.h:1 msgid "Log into a local container" @@ -483,53 +482,53 @@ msgid "Authentication is required to log into a local container." msgstr "Чтобы зайти в локальный контейнер, необходимо пройти аутентификацию." #: ../src/machine/org.freedesktop.machine1.policy.in.h:3 -#, fuzzy msgid "Log into the local host" -msgstr "Зайти в локальный контейнер" +msgstr "Зайти на Ñтот компьютер" #: ../src/machine/org.freedesktop.machine1.policy.in.h:4 -#, fuzzy msgid "Authentication is required to log into the local host." -msgstr "Чтобы зайти в локальный контейнер, необходимо пройти аутентификацию." +msgstr "Чтобы зайти на Ñтот компьютер, необходимо пройти аутентификацию." #: ../src/machine/org.freedesktop.machine1.policy.in.h:5 -#, fuzzy msgid "Acquire a shell in a local container" -msgstr "Зайти в локальный контейнер" +msgstr "Получить командную оболочку в локальном контейнере" #: ../src/machine/org.freedesktop.machine1.policy.in.h:6 -#, fuzzy msgid "Authentication is required to acquire a shell in a local container." -msgstr "Чтобы зайти в локальный контейнер, необходимо пройти аутентификацию." +msgstr "" +"Чтобы получить командную оболочку в локальном контейнере, необходимо пройти " +"аутентификацию." #: ../src/machine/org.freedesktop.machine1.policy.in.h:7 msgid "Acquire a shell on the local host" -msgstr "" +msgstr "ЗапуÑтить командную оболочку на Ñтом компьютере" #: ../src/machine/org.freedesktop.machine1.policy.in.h:8 -#, fuzzy msgid "Authentication is required to acquire a shell on the local host." -msgstr "Чтобы наÑтроить Ð¸Ð¼Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð°, необходимо пройти аутентификацию." +msgstr "" +"Чтобы запуÑтить командную оболочку на Ñтом компьютере, необходимо пройти " +"аутентификацию." #: ../src/machine/org.freedesktop.machine1.policy.in.h:9 -#, fuzzy msgid "Acquire a pseudo TTY in a local container" -msgstr "Зайти в локальный контейнер" +msgstr "Получить пÑевдо-терминал в локальном контейнере" #: ../src/machine/org.freedesktop.machine1.policy.in.h:10 -#, fuzzy msgid "" "Authentication is required to acquire a pseudo TTY in a local container." -msgstr "Чтобы зайти в локальный контейнер, необходимо пройти аутентификацию." +msgstr "" +"Чтобы получить пÑевдо-терминал в локальном контейнере, необходимо пройти " +"аутентификацию." #: ../src/machine/org.freedesktop.machine1.policy.in.h:11 msgid "Acquire a pseudo TTY on the local host" -msgstr "" +msgstr "Получить пÑевдо-терминал на Ñтом компьютере" #: ../src/machine/org.freedesktop.machine1.policy.in.h:12 -#, fuzzy msgid "Authentication is required to acquire a pseudo TTY on the local host." -msgstr "Чтобы наÑтроить Ð¸Ð¼Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð°, необходимо пройти аутентификацию." +msgstr "" +"Чтобы получить пÑевдо-терминал на Ñтом компьютере, необходимо пройти " +"аутентификацию." #: ../src/machine/org.freedesktop.machine1.policy.in.h:13 msgid "Manage local virtual machines and containers" @@ -595,41 +594,37 @@ msgstr "" "пройти аутентификацию." #: ../src/core/dbus-unit.c:428 -#, fuzzy msgid "Authentication is required to start '$(unit)'." -msgstr "Чтобы наÑтроить ÑиÑтемное времÑ, необходимо пройти аутентификацию." +msgstr "Чтобы запуÑтить «$(unit)», необходимо пройти аутентификацию." #: ../src/core/dbus-unit.c:429 -#, fuzzy msgid "Authentication is required to stop '$(unit)'." -msgstr "Чтобы наÑтроить ÑиÑтемное времÑ, необходимо пройти аутентификацию." +msgstr "Чтобы оÑтановить «$(unit)», необходимо пройти аутентификацию." #: ../src/core/dbus-unit.c:430 -#, fuzzy msgid "Authentication is required to reload '$(unit)'." msgstr "" -"Чтобы заÑтавить systemd перечитать конфигурацию, необходимо пройти " +"Чтобы заÑтавить «$(unit)» перечитать конфигурацию, необходимо пройти " "аутентификацию." #: ../src/core/dbus-unit.c:431 ../src/core/dbus-unit.c:432 -#, fuzzy msgid "Authentication is required to restart '$(unit)'." -msgstr "Чтобы наÑтроить ÑиÑтемное времÑ, необходимо пройти аутентификацию." +msgstr "Чтобы перезапуÑтить «$(unit)», необходимо пройти аутентификацию." #: ../src/core/dbus-unit.c:535 -#, fuzzy msgid "Authentication is required to kill '$(unit)'." -msgstr "Чтобы зайти в локальный контейнер, необходимо пройти аутентификацию." +msgstr "Чтобы убить юнит «$(unit)», необходимо пройти аутентификацию." #: ../src/core/dbus-unit.c:565 -#, fuzzy msgid "Authentication is required to reset the \"failed\" state of '$(unit)'." -msgstr "Чтобы наÑтроить Ð¸Ð¼Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð°, необходимо пройти аутентификацию." +msgstr "" +"Чтобы ÑброÑить ÑоÑтоÑние «failed» у юнита «$(unit)», необходимо пройти " +"аутентификацию." #: ../src/core/dbus-unit.c:597 -#, fuzzy msgid "Authentication is required to set properties on '$(unit)'." -msgstr "Чтобы наÑтроить ÑиÑтемное времÑ, необходимо пройти аутентификацию." +msgstr "Чтобы изменить параметры юнита «$(unit)», необходимо пройти " +"аутентификацию." #~ msgid "Press Ctrl+C to cancel all filesystem checks in progress" #~ msgstr "" @@ -2,21 +2,21 @@ # Copyright (C) 2014 systemd's COPYRIGHT HOLDER # This file is distributed under the same license as the systemd package. # Eugene Melnik <jeka7js@gmail.com>, 2014. -# Daniel Korostil <ted.korostiled@gmail.com>, 2014. +# Daniel Korostil <ted.korostiled@gmail.com>, 2014, 2016. msgid "" msgstr "" "Project-Id-Version: systemd master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-11-22 16:37+0100\n" -"PO-Revision-Date: 2014-07-16 19:13+0300\n" +"POT-Creation-Date: 2016-01-11 09:21+0200\n" +"PO-Revision-Date: 2016-01-11 11:00+0300\n" "Last-Translator: Daniel Korostil <ted.korostiled@gmail.com>\n" "Language-Team: linux.org.ua\n" "Language: uk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Virtaal 0.7.1\n" #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:1 @@ -30,43 +30,42 @@ msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб надіÑлати ввР#: ../src/core/org.freedesktop.systemd1.policy.in.in.h:3 msgid "Manage system services or other units" -msgstr "" +msgstr "Керувати ÑиÑтемними Ñлужбами й іншими одиницÑми" #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:4 -#, fuzzy msgid "Authentication is required to manage system services or other units." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб доÑтупитиÑÑŒ до менеджера ÑиÑтеми Ñ– Ñлужб." +msgstr "" +"ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб керувати ÑиÑтемними Ñлужбами й іншими одиницÑми." #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:5 msgid "Manage system service or unit files" -msgstr "" +msgstr "Керувати ÑиÑтемними Ñлужбами й файлами одиниць" #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:6 -#, fuzzy msgid "Authentication is required to manage system service or unit files." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб доÑтупитиÑÑŒ до менеджера ÑиÑтеми Ñ– Ñлужб." +msgstr "" +"ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб керувати ÑиÑтемними Ñлужбами й файлами одиниць." #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:7 -#, fuzzy msgid "Set or unset system and service manager environment variables" -msgstr "Привілейований доÑтуп до менеджера ÑиÑтеми Ñ– Ñлужб" +msgstr "" +"Ð’Ñтановити або забрати змінну Ñередовища з ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ñлужбами Ñ– ÑиÑтемою" #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:8 -#, fuzzy msgid "" "Authentication is required to set or unset system and service manager " "environment variables." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб доÑтупитиÑÑŒ до менеджера ÑиÑтеми Ñ– Ñлужб." +msgstr "" +"ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб уÑтановити або забрати змінні Ñередовища з " +"ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ñлужбами Ñ– ÑиÑтемою." #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:9 -#, fuzzy msgid "Reload the systemd state" -msgstr "Перезавантажити ÑиÑтему" +msgstr "ПерезапуÑтити Ñтан ÑиÑтеми" #: ../src/core/org.freedesktop.systemd1.policy.in.in.h:10 -#, fuzzy msgid "Authentication is required to reload the systemd state." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб вказати ÑиÑтемний чаÑ." +msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб перезапуÑтити Ñтан ÑиÑтеми." #: ../src/hostname/org.freedesktop.hostname1.policy.in.h:1 msgid "Set host name" @@ -98,30 +97,31 @@ msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб вказати локаР#: ../src/import/org.freedesktop.import1.policy.in.h:1 msgid "Import a VM or container image" -msgstr "" +msgstr "Імпортувати образ контейнера або віртуальної машини" #: ../src/import/org.freedesktop.import1.policy.in.h:2 -#, fuzzy msgid "Authentication is required to import a VM or container image" -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб вказати ÑиÑтемний чаÑ." +msgstr "" +"ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб імпортувати образ контейнера або віртуальної машини" #: ../src/import/org.freedesktop.import1.policy.in.h:3 msgid "Export a VM or container image" -msgstr "" +msgstr "ЕкÑпортувати образ контейнера або віртуальної машини" #: ../src/import/org.freedesktop.import1.policy.in.h:4 -#, fuzzy msgid "Authentication is required to export a VM or container image" -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб вказати ÑиÑтемний чаÑ." +msgstr "" +"ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб екÑпортувати образ контейнера або віртуальної " +"машини" #: ../src/import/org.freedesktop.import1.policy.in.h:5 msgid "Download a VM or container image" -msgstr "" +msgstr "Звантажити образ контейнера або віртуальної машини" #: ../src/import/org.freedesktop.import1.policy.in.h:6 -#, fuzzy msgid "Authentication is required to download a VM or container image" -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб вказати локальну інформацію про машини." +msgstr "" +"ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб звантажити образ контейнера або віртуальної машини" #: ../src/locale/org.freedesktop.locale1.policy.in.h:1 msgid "Set system locale" @@ -277,7 +277,7 @@ msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб вимкнути ÑиÑÑ #: ../src/login/org.freedesktop.login1.policy.in.h:27 msgid "Power off the system while other users are logged in" -msgstr "Вимикнути ÑиÑтему, коли інші кориÑтувачі ще в ній" +msgstr "Вимкнути ÑиÑтему, коли інші кориÑтувачі ще в ній" #: ../src/login/org.freedesktop.login1.policy.in.h:28 msgid "" @@ -288,7 +288,7 @@ msgstr "" #: ../src/login/org.freedesktop.login1.policy.in.h:29 msgid "Power off the system while an application asked to inhibit it" -msgstr "Вимкнути ÑиÑтему, коли програми намагаютьÑÑ Ð¿ÐµÑ€ÑˆÐºÐ¾Ð´Ð¸Ñ‚Ð¸ цьому" +msgstr "Вимкнути ÑиÑтему, коли програми намагаютьÑÑ Ð¿ÐµÑ€ÐµÑˆÐºÐ¾Ð´Ð¸Ñ‚Ð¸ цьому" #: ../src/login/org.freedesktop.login1.policy.in.h:30 msgid "" @@ -296,7 +296,7 @@ msgid "" "asked to inhibit it." msgstr "" "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб вимкнути ÑиÑтему, коли програми намагаютьÑÑ " -"першкодити цьому." +"перешкодити цьому." #: ../src/login/org.freedesktop.login1.policy.in.h:31 msgid "Reboot the system" @@ -308,7 +308,7 @@ msgstr "Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÐ·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ ÑиÑтеми необхіднР#: ../src/login/org.freedesktop.login1.policy.in.h:33 msgid "Reboot the system while other users are logged in" -msgstr "Перезавантажити, Ñкщо інщі кориÑтувачі в ÑиÑтемі" +msgstr "Перезавантажити, Ñкщо інші кориÑтувачі в ÑиÑтемі" #: ../src/login/org.freedesktop.login1.policy.in.h:34 msgid "" @@ -319,7 +319,7 @@ msgstr "" #: ../src/login/org.freedesktop.login1.policy.in.h:35 msgid "Reboot the system while an application asked to inhibit it" -msgstr "ПерезапуÑтити ÑиÑтему, коли програми намагаютьÑÑ Ð¿ÐµÑ€ÑˆÐºÐ¾Ð´Ð¸Ñ‚Ð¸ цьому" +msgstr "ПерезапуÑтити ÑиÑтему, коли програми намагаютьÑÑ Ð¿ÐµÑ€ÐµÑˆÐºÐ¾Ð´Ð¸Ñ‚Ð¸ цьому" #: ../src/login/org.freedesktop.login1.policy.in.h:36 msgid "" @@ -327,7 +327,7 @@ msgid "" "asked to inhibit it." msgstr "" "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб перезапуÑтити ÑиÑтему, коли програми намагаютьÑÑ " -"першкодити цьому." +"перешкодити цьому." #: ../src/login/org.freedesktop.login1.policy.in.h:37 msgid "Suspend the system" @@ -350,15 +350,15 @@ msgstr "" #: ../src/login/org.freedesktop.login1.policy.in.h:41 msgid "Suspend the system while an application asked to inhibit it" -msgstr "Призупинити ÑиÑтему, коли програми намагаютьÑÑ Ð¿ÐµÑ€ÑˆÐºÐ¾Ð´Ð¸Ñ‚Ð¸ цьому" +msgstr "Призупинити ÑиÑтему, коли програми намагаютьÑÑ Ð¿ÐµÑ€ÐµÑˆÐºÐ¾Ð´Ð¸Ñ‚Ð¸ цьому" #: ../src/login/org.freedesktop.login1.policy.in.h:42 msgid "" "Authentication is required for suspending the system while an application " "asked to inhibit it." msgstr "" -"ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб призупнити ÑиÑтему, коли програми намагаютьÑÑ " -"першкодити цьому." +"ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб призупинити ÑиÑтему, коли програми намагаютьÑÑ " +"перешкодити цьому." #: ../src/login/org.freedesktop.login1.policy.in.h:43 msgid "Hibernate the system" @@ -381,7 +381,7 @@ msgstr "" #: ../src/login/org.freedesktop.login1.policy.in.h:47 msgid "Hibernate the system while an application asked to inhibit it" -msgstr "ПриÑпати ÑиÑтему, коли програми намагаютьÑÑ Ð¿ÐµÑ€ÑˆÐºÐ¾Ð´Ð¸Ñ‚Ð¸ цьому" +msgstr "ПриÑпати ÑиÑтему, коли програми намагаютьÑÑ Ð¿ÐµÑ€ÐµÑˆÐºÐ¾Ð´Ð¸Ñ‚Ð¸ цьому" #: ../src/login/org.freedesktop.login1.policy.in.h:48 msgid "" @@ -389,124 +389,118 @@ msgid "" "asked to inhibit it." msgstr "" "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб приÑпати ÑиÑтему, коли програми намагаютьÑÑ " -"першкодити цьому." +"перешкодити цьому." #: ../src/login/org.freedesktop.login1.policy.in.h:49 msgid "Manage active sessions, users and seats" -msgstr "" +msgstr "Керувати ÑеанÑами, кориÑтувачами Ñ– робочими міÑцÑми" #: ../src/login/org.freedesktop.login1.policy.in.h:50 -#, fuzzy msgid "" "Authentication is required for managing active sessions, users and seats." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб під'єднувати приÑтрої до міÑць." +msgstr "" +"ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб керувати ÑеанÑами, кориÑтувачами Ñ– робочими " +"міÑцÑми." #: ../src/login/org.freedesktop.login1.policy.in.h:51 msgid "Lock or unlock active sessions" -msgstr "" +msgstr "Заблокувати або розблокувати ÑеанÑи" #: ../src/login/org.freedesktop.login1.policy.in.h:52 -#, fuzzy msgid "Authentication is required to lock or unlock active sessions." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб вказати локальну інформацію про машини." +msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб заблокувати або розблокувати ÑеанÑи." #: ../src/login/org.freedesktop.login1.policy.in.h:53 msgid "Allow indication to the firmware to boot to setup interface" -msgstr "" +msgstr "Дозволити мікрокоду визначати, чи завантажувати Ñ–Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð²ÑтановленнÑ" #: ../src/login/org.freedesktop.login1.policy.in.h:54 -#, fuzzy msgid "" "Authentication is required to indicate to the firmware to boot to setup " "interface." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб вÑтановити назву локального вузла." +msgstr "" +"ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб дозволити мікрокоду визначати, чи завантажувати " +"Ñ–Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð²ÑтановленнÑ." #: ../src/login/org.freedesktop.login1.policy.in.h:55 msgid "Set a wall message" -msgstr "" +msgstr "Вказати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð½Ð° Ñтіні" #: ../src/login/org.freedesktop.login1.policy.in.h:56 -#, fuzzy msgid "Authentication is required to set a wall message" -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб вÑтановити назву локального вузла." +msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб вказати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð½Ð° Ñтіні" #: ../src/machine/org.freedesktop.machine1.policy.in.h:1 msgid "Log into a local container" -msgstr "" +msgstr "Увійти в локальний контейнер" #: ../src/machine/org.freedesktop.machine1.policy.in.h:2 -#, fuzzy msgid "Authentication is required to log into a local container." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб вÑтановити назву локального вузла." +msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб увійти в локальний контейнер." #: ../src/machine/org.freedesktop.machine1.policy.in.h:3 msgid "Log into the local host" -msgstr "" +msgstr "Увійти в локальний вузол" #: ../src/machine/org.freedesktop.machine1.policy.in.h:4 -#, fuzzy msgid "Authentication is required to log into the local host." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб вÑтановити назву локального вузла." +msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб увійти в локальний вузол." #: ../src/machine/org.freedesktop.machine1.policy.in.h:5 -#, fuzzy msgid "Acquire a shell in a local container" -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб вÑтановити назву локального вузла." +msgstr "ПерейнÑти оболонку в локальному контейнері" #: ../src/machine/org.freedesktop.machine1.policy.in.h:6 -#, fuzzy msgid "Authentication is required to acquire a shell in a local container." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб вÑтановити назву локального вузла." +msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб перейнÑти оболонку в локальному контейнері." #: ../src/machine/org.freedesktop.machine1.policy.in.h:7 msgid "Acquire a shell on the local host" -msgstr "" +msgstr "ПерейнÑти оболонку на локальному вузлі" #: ../src/machine/org.freedesktop.machine1.policy.in.h:8 -#, fuzzy msgid "Authentication is required to acquire a shell on the local host." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб вÑтановити назву локального вузла." +msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб перейнÑти оболонку на локальному вузлі." #: ../src/machine/org.freedesktop.machine1.policy.in.h:9 -#, fuzzy msgid "Acquire a pseudo TTY in a local container" -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб вÑтановити назву локального вузла." +msgstr "ПерейнÑти пÑевдо TTY в локальному контейнері" #: ../src/machine/org.freedesktop.machine1.policy.in.h:10 -#, fuzzy msgid "" "Authentication is required to acquire a pseudo TTY in a local container." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб вÑтановити назву локального вузла." +msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб перейнÑти пÑевдо TTY в локальному контейнері." #: ../src/machine/org.freedesktop.machine1.policy.in.h:11 msgid "Acquire a pseudo TTY on the local host" -msgstr "" +msgstr "ПерейнÑти пÑевдо TTY на локальному вузлі" #: ../src/machine/org.freedesktop.machine1.policy.in.h:12 -#, fuzzy msgid "Authentication is required to acquire a pseudo TTY on the local host." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб вÑтановити назву локального вузла." +msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб перейнÑти пÑевдо TTY на локальному вузлі." #: ../src/machine/org.freedesktop.machine1.policy.in.h:13 msgid "Manage local virtual machines and containers" -msgstr "" +msgstr "Керувати локальними віртуальними машинами Ñ– контейнерами" #: ../src/machine/org.freedesktop.machine1.policy.in.h:14 -#, fuzzy msgid "" "Authentication is required to manage local virtual machines and containers." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб вказати локальну інформацію про машини." +msgstr "" +"ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб керувати локальними віртуальними машинами Ñ– " +"контейнерами." #: ../src/machine/org.freedesktop.machine1.policy.in.h:15 msgid "Manage local virtual machine and container images" -msgstr "" +msgstr "Керувати локальними образами віртуальних машин Ñ– контейнерів" #: ../src/machine/org.freedesktop.machine1.policy.in.h:16 -#, fuzzy msgid "" "Authentication is required to manage local virtual machine and container " "images." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб вказати локальну інформацію про машини." +msgstr "" +"ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб керувати локальними образами віртуальних машин Ñ– " +"контейнерів." #: ../src/timedate/org.freedesktop.timedate1.policy.in.h:1 msgid "Set system time" @@ -546,37 +540,30 @@ msgstr "" "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб контролювати, чи ÑÐ¸Ð½Ñ…Ñ€Ð¾Ð½Ñ–Ð·ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‡Ð°Ñу через мережу " "запущено." -#: ../src/core/dbus-unit.c:428 -#, fuzzy +#: ../src/core/dbus-unit.c:449 msgid "Authentication is required to start '$(unit)'." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб вказати ÑиÑтемний чаÑ." +msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб запуÑтити «$(unit)»." -#: ../src/core/dbus-unit.c:429 -#, fuzzy +#: ../src/core/dbus-unit.c:450 msgid "Authentication is required to stop '$(unit)'." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб вказати ÑиÑтемний чаÑ." +msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб зупинити «$(unit)»." -#: ../src/core/dbus-unit.c:430 -#, fuzzy +#: ../src/core/dbus-unit.c:451 msgid "Authentication is required to reload '$(unit)'." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб вказати ÑиÑтемний чаÑ." +msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб перезавантажити «$(unit)»." -#: ../src/core/dbus-unit.c:431 ../src/core/dbus-unit.c:432 -#, fuzzy +#: ../src/core/dbus-unit.c:452 ../src/core/dbus-unit.c:453 msgid "Authentication is required to restart '$(unit)'." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб вказати ÑиÑтемний чаÑ." +msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб перезапуÑтити «$(unit)»." -#: ../src/core/dbus-unit.c:535 -#, fuzzy +#: ../src/core/dbus-unit.c:556 msgid "Authentication is required to kill '$(unit)'." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб вÑтановити назву локального вузла." +msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб вбити «$(unit)»." -#: ../src/core/dbus-unit.c:565 -#, fuzzy +#: ../src/core/dbus-unit.c:586 msgid "Authentication is required to reset the \"failed\" state of '$(unit)'." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб вÑтановити назву локального вузла." +msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ðµ, щоб Ñкинути «пошкоджений» Ñтан з «$(unit)»." -#: ../src/core/dbus-unit.c:597 -#, fuzzy +#: ../src/core/dbus-unit.c:618 msgid "Authentication is required to set properties on '$(unit)'." -msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб вказати ÑиÑтемний чаÑ." +msgstr "ЗаÑÐ²Ñ–Ð´Ñ‡ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ñ€Ñ–Ð±Ð½Ð¾, щоб вказати влаÑтивоÑÑ‚Ñ– на «$(unit)»." diff --git a/shell-completion/bash/systemctl.in b/shell-completion/bash/systemctl.in index 6ffab33e45..ef7dc6285a 100644 --- a/shell-completion/bash/systemctl.in +++ b/shell-completion/bash/systemctl.in @@ -170,7 +170,7 @@ _systemctl () { [STARTABLE_UNITS]='start' [STOPPABLE_UNITS]='stop condstop kill try-restart condrestart' [ISOLATABLE_UNITS]='isolate' - [RELOADABLE_UNITS]='reload condreload reload-or-try-restart force-reload' + [RELOADABLE_UNITS]='reload condreload try-reload-or-restart force-reload' [RESTARTABLE_UNITS]='restart reload-or-restart' [TARGET_AND_UNITS]='add-wants add-requires' [MASKED_UNITS]='unmask' diff --git a/shell-completion/zsh/_journalctl b/shell-completion/zsh/_journalctl index b50f0cafc9..2bee23b6d3 100644 --- a/shell-completion/zsh/_journalctl +++ b/shell-completion/zsh/_journalctl @@ -34,7 +34,10 @@ _journal_none() { _journal_fields() { local -a _fields cmd cmd=("journalctl" "-F ${@[-1]}" "2>/dev/null" ) - _fields=( ${(f)"$(_call_program fields $cmd[@])"} ) + _fields=$(_call_program fields $cmd[@]) + _fields=${_fields//'\'/'\\'} + _fields=${_fields//':'/'\:'} + _fields=( ${(f)_fields} ) typeset -U _fields _describe 'possible values' _fields } diff --git a/shell-completion/zsh/_systemctl.in b/shell-completion/zsh/_systemctl.in index 58c88c9d98..667243eb53 100644 --- a/shell-completion/zsh/_systemctl.in +++ b/shell-completion/zsh/_systemctl.in @@ -17,7 +17,7 @@ "force-reload:Reload one or more units if possible, otherwise restart if active" "hibernate:Hibernate the system" "hybrid-sleep:Hibernate and suspend the system" - "reload-or-try-restart:Reload one or more units if possible, otherwise restart if active" + "try-reload-or-restart:Reload one or more units if possible, otherwise restart if active" "isolate:Start one unit and stop all others" "kill:Send signal to processes of a unit" "is-active:Check whether units are active" @@ -69,7 +69,7 @@ # Deal with any aliases case $cmd in condrestart) cmd="try-restart";; - force-reload) cmd="reload-or-try-restart";; + force-reload) cmd="try-reload-or-restart";; esac if (( $#cmd )); then @@ -230,7 +230,7 @@ done } # Completion functions for RELOADABLE_UNITS -for fun in reload reload-or-try-restart force-reload ; do +for fun in reload try-reload-or-restart force-reload ; do (( $+functions[_systemctl_$fun] )) || _systemctl_$fun() { local _sys_active_units; _systemctl_active_units diff --git a/src/activate/activate.c b/src/activate/activate.c index b7e6255f49..558d16824a 100644 --- a/src/activate/activate.c +++ b/src/activate/activate.c @@ -39,6 +39,7 @@ static char** arg_listen = NULL; static bool arg_accept = false; +static bool arg_datagram = false; static char** arg_args = NULL; static char** arg_setenv = NULL; static const char *arg_fdname = NULL; @@ -98,7 +99,11 @@ static int open_sockets(int *epoll_fd, bool accept) { STRV_FOREACH(address, arg_listen) { - fd = make_socket_fd(LOG_DEBUG, *address, SOCK_STREAM | (arg_accept*SOCK_CLOEXEC)); + if (arg_datagram) + fd = make_socket_fd(LOG_DEBUG, *address, SOCK_DGRAM, SOCK_CLOEXEC); + else + fd = make_socket_fd(LOG_DEBUG, *address, SOCK_STREAM, (arg_accept*SOCK_CLOEXEC)); + if (fd < 0) { log_open(); return log_error_errno(fd, "Failed to open '%s': %m", *address); @@ -146,24 +151,44 @@ static int launch(char* name, char **argv, char **env, int fds) { return log_oom(); STRV_FOREACH(s, arg_setenv) { - if (strchr(*s, '=')) - envp[n_env++] = *s; - else { + if (strchr(*s, '=')) { + char *k; + + k = strdup(*s); + if (!k) + return log_oom(); + + envp[n_env++] = k; + } else { _cleanup_free_ char *p; + const char *n; p = strappend(*s, "="); if (!p) return log_oom(); - envp[n_env] = strv_find_prefix(env, p); - if (envp[n_env]) - n_env ++; + + n = strv_find_prefix(env, p); + if (!n) + continue; + + envp[n_env] = strdup(n); + if (!envp[n_env]) + return log_oom(); } } for (i = 0; i < ELEMENTSOF(tocopy); i++) { - envp[n_env] = strv_find_prefix(env, tocopy[i]); - if (envp[n_env]) - n_env ++; + const char *n; + + n = strv_find_prefix(env, tocopy[i]); + if (!n) + continue; + + envp[n_env] = strdup(n); + if (!envp[n_env]) + return log_oom(); + + n_env ++; } if ((asprintf((char**)(envp + n_env++), "LISTEN_FDS=%d", fds) < 0) || @@ -272,7 +297,7 @@ static int do_accept(const char* name, char **argv, char **envp, int fd) { } getsockname_pretty(fd2, &local); - getpeername_pretty(fd2, &peer); + getpeername_pretty(fd2, true, &peer); log_info("Connection from %s to %s", strna(peer), strna(local)); return launch1(name, argv, envp, fd2); @@ -304,11 +329,12 @@ static void help(void) { printf("%s [OPTIONS...]\n\n" "Listen on sockets and launch child on connection.\n\n" "Options:\n" + " -h --help Show this help and exit\n" + " --version Print version string and exit\n" " -l --listen=ADDR Listen for raw connections at ADDR\n" + " -d --datagram Listen on datagram instead of stream socket\n" " -a --accept Spawn separate child for each connection\n" - " -h --help Show this help and exit\n" " -E --setenv=NAME[=VALUE] Pass an environment variable to children\n" - " --version Print version string and exit\n" "\n" "Note: file descriptors from sd_listen_fds() will be passed through.\n" , program_invocation_short_name); @@ -323,6 +349,7 @@ static int parse_argv(int argc, char *argv[]) { static const struct option options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, + { "datagram", no_argument, NULL, 'd' }, { "listen", required_argument, NULL, 'l' }, { "accept", no_argument, NULL, 'a' }, { "setenv", required_argument, NULL, 'E' }, @@ -336,7 +363,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "+hl:aE:", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "+hl:aEd", options, NULL)) >= 0) switch(c) { case 'h': help(); @@ -352,6 +379,10 @@ static int parse_argv(int argc, char *argv[]) { break; + case 'd': + arg_datagram = true; + break; + case 'a': arg_accept = true; break; @@ -385,6 +416,12 @@ static int parse_argv(int argc, char *argv[]) { return -EINVAL; } + if (arg_datagram && arg_accept) { + log_error("Datagram sockets do not accept connections. " + "The --datagram and --accept options may not be combined."); + return -EINVAL; + } + arg_args = argv + optind; return 1 /* work to do */; diff --git a/src/analyze/analyze-verify.c b/src/analyze/analyze-verify.c index 6e460697db..3c9766da04 100644 --- a/src/analyze/analyze-verify.c +++ b/src/analyze/analyze-verify.c @@ -30,6 +30,47 @@ #include "pager.h" #include "path-util.h" #include "strv.h" +#include "unit-name.h" + +static int prepare_filename(const char *filename, char **ret) { + int r; + const char *name; + _cleanup_free_ char *abspath = NULL; + _cleanup_free_ char *dir = NULL; + _cleanup_free_ char *with_instance = NULL; + char *c; + + assert(filename); + assert(ret); + + r = path_make_absolute_cwd(filename, &abspath); + if (r < 0) + return r; + + name = basename(abspath); + if (!unit_name_is_valid(name, UNIT_NAME_ANY)) + return -EINVAL; + + if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { + r = unit_name_replace_instance(name, "i", &with_instance); + if (r < 0) + return r; + } + + dir = dirname_malloc(abspath); + if (!dir) + return -ENOMEM; + + if (with_instance) + c = path_join(NULL, dir, with_instance); + else + c = path_join(NULL, dir, name); + if (!c) + return -ENOMEM; + + *ret = c; + return 0; +} static int generate_path(char **var, char **filenames) { char **filename; @@ -233,18 +274,19 @@ int verify_units(char **filenames, ManagerRunningAs running_as, bool check_man) log_debug("Loading remaining units from the command line..."); STRV_FOREACH(filename, filenames) { - char fname[UNIT_NAME_MAX + 2 + 1] = "./"; + _cleanup_free_ char *prepared = NULL; log_debug("Handling %s...", *filename); - /* manager_load_unit does not like pure basenames, so prepend - * the local directory, but only for valid names. manager_load_unit - * will print the error for other ones. */ - if (!strchr(*filename, '/') && strlen(*filename) <= UNIT_NAME_MAX) { - strncat(fname + 2, *filename, UNIT_NAME_MAX); - k = manager_load_unit(m, NULL, fname, &err, &units[count]); - } else - k = manager_load_unit(m, NULL, *filename, &err, &units[count]); + k = prepare_filename(*filename, &prepared); + if (k < 0) { + log_error_errno(k, "Failed to prepare filename %s: %m", *filename); + if (r == 0) + r = k; + continue; + } + + k = manager_load_unit(m, NULL, prepared, &err, &units[count]); if (k < 0) { log_error_errno(k, "Failed to load %s: %m", *filename); if (r == 0) 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/barrier.c b/src/basic/barrier.c index 9a78a80eb2..26ae123341 100644 --- a/src/basic/barrier.c +++ b/src/basic/barrier.c @@ -197,6 +197,7 @@ static bool barrier_write(Barrier *b, uint64_t buf) { if (barrier_i_aborted(b)) return false; + assert(b->me >= 0); do { len = write(b->me, &buf, sizeof(buf)); } while (len < 0 && IN_SET(errno, EAGAIN, EINTR)); diff --git a/src/basic/bitmap.c b/src/basic/bitmap.c index 95f59e400a..50078822a7 100644 --- a/src/basic/bitmap.c +++ b/src/basic/bitmap.c @@ -140,7 +140,8 @@ bool bitmap_isset(Bitmap *b, unsigned n) { bool bitmap_isclear(Bitmap *b) { unsigned i; - assert(b); + if (!b) + return true; for (i = 0; i < b->n_bitmaps; i++) if (b->bitmaps[i] != 0) @@ -150,7 +151,9 @@ bool bitmap_isclear(Bitmap *b) { } void bitmap_clear(Bitmap *b) { - assert(b); + + if (!b) + return; b->bitmaps = mfree(b->bitmaps); b->n_bitmaps = 0; @@ -197,7 +200,10 @@ bool bitmap_equal(Bitmap *a, Bitmap *b) { Bitmap *c; unsigned i; - if (!a ^ !b) + if (a == b) + return true; + + if (!a != !b) return false; if (!a) diff --git a/src/basic/btrfs-util.c b/src/basic/btrfs-util.c index acd48f6954..03c7609c92 100644 --- a/src/basic/btrfs-util.c +++ b/src/basic/btrfs-util.c @@ -43,6 +43,7 @@ #include "copy.h" #include "fd-util.h" #include "fileio.h" +#include "io-util.h" #include "macro.h" #include "missing.h" #include "path-util.h" @@ -913,6 +914,10 @@ int btrfs_resize_loopback_fd(int fd, uint64_t new_size, bool grow_only) { dev_t dev = 0; int r; + /* In contrast to btrfs quota ioctls ftruncate() cannot make sense of "infinity" or file sizes > 2^31 */ + if (!FILE_SIZE_VALID(new_size)) + return -EINVAL; + /* btrfs cannot handle file systems < 16M, hence use this as minimum */ if (new_size < 16*1024*1024) new_size = 16*1024*1024; @@ -2051,7 +2056,7 @@ int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret) { args.key.nr_items = 256; if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) - return -errno; + return negative_errno(); if (args.key.nr_items <= 0) break; diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index fef722b6f2..49c2d61afe 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -96,7 +96,62 @@ unsigned long cap_last_cap(void) { return p; } -int capability_bounding_set_drop(uint64_t drop, bool right_now) { +int capability_update_inherited_set(cap_t caps, uint64_t set) { + unsigned long i; + + /* Add capabilities in the set to the inherited caps. Do not apply + * them yet. */ + + for (i = 0; i < cap_last_cap(); i++) { + + if (set & (UINT64_C(1) << i)) { + cap_value_t v; + + v = (cap_value_t) i; + + /* Make the capability inheritable. */ + if (cap_set_flag(caps, CAP_INHERITABLE, 1, &v, CAP_SET) < 0) + return -errno; + } + } + + return 0; +} + +int capability_ambient_set_apply(uint64_t set, bool also_inherit) { + unsigned long i; + _cleanup_cap_free_ cap_t caps = NULL; + + /* Add the capabilities to the ambient set. */ + + if (also_inherit) { + int r; + caps = cap_get_proc(); + if (!caps) + return -errno; + + r = capability_update_inherited_set(caps, set); + if (r < 0) + return -errno; + + if (cap_set_proc(caps) < 0) + return -errno; + } + + for (i = 0; i < cap_last_cap(); i++) { + + if (set & (UINT64_C(1) << i)) { + + /* Add the capability to the ambient set. */ + if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, i, 0, 0) < 0) + return -errno; + } + } + + return 0; +} + +int capability_bounding_set_drop(uint64_t keep, bool right_now) { _cleanup_cap_free_ cap_t after_cap = NULL; cap_flag_value_t fv; unsigned long i; @@ -137,7 +192,7 @@ int capability_bounding_set_drop(uint64_t drop, bool right_now) { for (i = 0; i <= cap_last_cap(); i++) { - if (drop & ((uint64_t) 1ULL << (uint64_t) i)) { + if (!(keep & (UINT64_C(1) << i))) { cap_value_t v; /* Drop it from the bounding set */ @@ -176,7 +231,7 @@ finish: return r; } -static int drop_from_file(const char *fn, uint64_t drop) { +static int drop_from_file(const char *fn, uint64_t keep) { int r, k; uint32_t hi, lo; uint64_t current, after; @@ -196,7 +251,7 @@ static int drop_from_file(const char *fn, uint64_t drop) { return -EIO; current = (uint64_t) lo | ((uint64_t) hi << 32ULL); - after = current & ~drop; + after = current & keep; if (current == after) return 0; @@ -213,14 +268,14 @@ static int drop_from_file(const char *fn, uint64_t drop) { return r; } -int capability_bounding_set_drop_usermode(uint64_t drop) { +int capability_bounding_set_drop_usermode(uint64_t keep) { int r; - r = drop_from_file("/proc/sys/kernel/usermodehelper/inheritable", drop); + r = drop_from_file("/proc/sys/kernel/usermodehelper/inheritable", keep); if (r < 0) return r; - r = drop_from_file("/proc/sys/kernel/usermodehelper/bset", drop); + r = drop_from_file("/proc/sys/kernel/usermodehelper/bset", keep); if (r < 0) return r; @@ -257,7 +312,7 @@ int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities) { return log_error_errno(errno, "Failed to disable keep capabilities flag: %m"); /* Drop all caps from the bounding set, except the ones we want */ - r = capability_bounding_set_drop(~keep_capabilities, true); + r = capability_bounding_set_drop(keep_capabilities, true); if (r < 0) return log_error_errno(r, "Failed to drop capabilities: %m"); diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h index 6bbf7318fd..be41475441 100644 --- a/src/basic/capability-util.h +++ b/src/basic/capability-util.h @@ -29,10 +29,15 @@ #include "macro.h" #include "util.h" +#define CAP_ALL (uint64_t) -1 + unsigned long cap_last_cap(void); int have_effective_cap(int value); -int capability_bounding_set_drop(uint64_t drop, bool right_now); -int capability_bounding_set_drop_usermode(uint64_t drop); +int capability_bounding_set_drop(uint64_t keep, bool right_now); +int capability_bounding_set_drop_usermode(uint64_t keep); + +int capability_ambient_set_apply(uint64_t set, bool also_inherit); +int capability_update_inherited_set(cap_t caps, uint64_t ambient_set); int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities); @@ -46,3 +51,9 @@ static inline void cap_free_charpp(char **p) { cap_free(*p); } #define _cleanup_cap_free_charp_ _cleanup_(cap_free_charpp) + +static inline bool cap_test_all(uint64_t caps) { + uint64_t m; + m = (UINT64_C(1) << (cap_last_cap() + 1)) - 1; + return (caps & m) == m; +} diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index 639f9f3db1..f873fb89d3 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -53,6 +53,7 @@ #include "set.h" #include "special.h" #include "stat-util.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "unit-name.h" @@ -92,7 +93,7 @@ int cg_read_pid(FILE *f, pid_t *_pid) { if (feof(f)) return 0; - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; } if (ul <= 0) @@ -647,7 +648,7 @@ int cg_trim(const char *controller, const char *path, bool delete_root) { if (nftw(fs, trim_cb, 64, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) != 0) { if (errno == ENOENT) r = 0; - else if (errno != 0) + else if (errno > 0) r = -errno; else r = -EIO; @@ -716,7 +717,7 @@ int cg_attach(const char *controller, const char *path, pid_t pid) { if (pid == 0) pid = getpid(); - snprintf(c, sizeof(c), PID_FMT"\n", pid); + xsprintf(c, PID_FMT "\n", pid); return write_string_file(fs, c, 0); } @@ -2090,7 +2091,7 @@ int cg_kernel_controllers(Set *controllers) { if (feof(f)) break; - if (ferror(f) && errno != 0) + if (ferror(f) && errno > 0) return -errno; return -EBADMSG; diff --git a/src/basic/clock-util.c b/src/basic/clock-util.c index 00f549c023..c64b2e5e8c 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; @@ -144,3 +146,17 @@ int clock_reset_timewarp(void) { return 0; } + +#define TIME_EPOCH_USEC ((usec_t) TIME_EPOCH * USEC_PER_SEC) + +int clock_apply_epoch(void) { + struct timespec ts; + + if (now(CLOCK_REALTIME) >= TIME_EPOCH_USEC) + return 0; + + if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, TIME_EPOCH_USEC)) < 0) + return -errno; + + return 1; +} diff --git a/src/basic/clock-util.h b/src/basic/clock-util.h index fef2d471a6..09d46758f4 100644 --- a/src/basic/clock-util.h +++ b/src/basic/clock-util.h @@ -28,3 +28,4 @@ int clock_set_timezone(int *min); int clock_reset_timewarp(void); int clock_get_hwclock(struct tm *tm); int clock_set_hwclock(const struct tm *tm); +int clock_apply_epoch(void); diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c index 75dad228e3..5854caeb51 100644 --- a/src/basic/conf-files.c +++ b/src/basic/conf-files.c @@ -41,6 +41,7 @@ static int files_add(Hashmap *h, const char *root, const char *path, const char *suffix) { _cleanup_closedir_ DIR *dir = NULL; const char *dirpath; + struct dirent *de; int r; assert(path); @@ -55,18 +56,9 @@ static int files_add(Hashmap *h, const char *root, const char *path, const char return -errno; } - for (;;) { - struct dirent *de; + FOREACH_DIRENT(de, dir, return -errno) { char *p; - errno = 0; - de = readdir(dir); - if (!de && errno != 0) - return -errno; - - if (!de) - break; - if (!dirent_is_file_with_suffix(de, suffix)) continue; @@ -116,17 +108,15 @@ static int conf_files_list_strv_internal(char ***strv, const char *suffix, const STRV_FOREACH(p, dirs) { r = files_add(fh, root, *p, suffix); - if (r == -ENOMEM) { + if (r == -ENOMEM) return r; - } else if (r < 0) - log_debug_errno(r, "Failed to search for files in %s: %m", - *p); + if (r < 0) + log_debug_errno(r, "Failed to search for files in %s, ignoring: %m", *p); } files = hashmap_get_strv(fh); - if (files == NULL) { + if (!files) return -ENOMEM; - } qsort_safe(files, hashmap_size(fh), sizeof(char *), base_cmp); *strv = files; 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..f276c36c56 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, char32_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; } @@ -236,7 +230,7 @@ int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode int a[8]; unsigned i; - uint32_t c; + char32_t c; if (length != (size_t) -1 && length < 9) return -EINVAL; @@ -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; } @@ -282,7 +267,7 @@ int cunescape_one(const char *p, size_t length, char *ret, uint32_t *ret_unicode case '7': { /* octal encoding */ int a, b, c; - uint32_t m; + char32_t m; if (length != (size_t) -1 && length < 3) return -EINVAL; @@ -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; } @@ -341,8 +327,8 @@ 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; + char32_t u; 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..ac8f5f3910 100644 --- a/src/basic/escape.h +++ b/src/basic/escape.h @@ -25,8 +25,10 @@ #include <stddef.h> #include <stdint.h> #include <sys/types.h> +#include <uchar.h> #include "string-util.h" +#include "missing.h" /* What characters are special in the shell? */ /* must be escaped outside and inside double-quotes */ @@ -45,7 +47,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, char32_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..6dcd4f9f5b 100644 --- a/src/basic/extract-word.c +++ b/src/basic/extract-word.c @@ -107,9 +107,10 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra } if (flags & EXTRACT_CUNESCAPE) { - uint32_t u; + bool eight_bit = false; + char32_t u; - r = cunescape_one(*p, (size_t) -1, &c, &u); + r = cunescape_one(*p, (size_t) -1, &u, &eight_bit); if (r < 0) { if (flags & EXTRACT_CUNESCAPE_RELAX) { s[sz++] = '\\'; @@ -119,10 +120,10 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra } else { (*p) += r - 1; - if (c != 0) - s[sz++] = c; /* normal explicit char */ + if (eight_bit) + s[sz++] = u; else - sz += utf8_encode_unichar(s + sz, u); /* unicode chars we'll encode as utf8 */ + sz += utf8_encode_unichar(s + sz, u); } } else s[sz++] = c; diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index 5ce1592eeb..20890e3279 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -73,3 +73,7 @@ int same_fd(int a, int b); void cmsg_close_all(struct msghdr *mh); bool fdname_is_valid(const char *s); + +/* Hint: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5 */ +#define ERRNO_IS_DISCONNECT(r) \ + IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, ENETUNREACH) diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 3a237252b5..3ff70310e1 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; } @@ -1251,3 +1251,32 @@ int read_timestamp_file(const char *fn, usec_t *ret) { *ret = (usec_t) t; return 0; } + +int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space) { + int r; + + assert(s); + + /* Outputs the specified string with fputs(), but optionally prefixes it with a separator. The *space parameter + * when specified shall initially point to a boolean variable initialized to false. It is set to true after the + * first invocation. This call is supposed to be use in loops, where a separator shall be inserted between each + * element, but not before the first one. */ + + if (!f) + f = stdout; + + if (space) { + if (!separator) + separator = " "; + + if (*space) { + r = fputs(separator, f); + if (r < 0) + return r; + } + + *space = true; + } + + return fputs(s, f); +} diff --git a/src/basic/fileio.h b/src/basic/fileio.h index 95e8698941..9e09574133 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -82,3 +82,5 @@ int tempfn_random_child(const char *p, const char *extra, char **ret); int write_timestamp_file_atomic(const char *fn, usec_t n); int read_timestamp_file(const char *fn, usec_t *ret); + +int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space); diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index fb760abe18..61b651b573 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -341,7 +341,8 @@ int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gi if (parents) mkdir_parents(path, 0755); - fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, mode > 0 ? mode : 0644); + fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, + (mode == 0 || mode == MODE_INVALID) ? 0644 : mode); if (fd < 0) return -errno; @@ -481,7 +482,7 @@ int get_files_in_directory(const char *path, char ***list) { errno = 0; de = readdir(d); - if (!de && errno != 0) + if (!de && errno > 0) return -errno; if (!de) break; diff --git a/src/basic/glob-util.c b/src/basic/glob-util.c index a0be0efd40..811ab6ec36 100644 --- a/src/basic/glob-util.c +++ b/src/basic/glob-util.c @@ -40,7 +40,7 @@ int glob_exists(const char *path) { if (k == GLOB_NOSPACE) return -ENOMEM; if (k != 0) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; return !strv_isempty(g.gl_pathv); } @@ -58,7 +58,7 @@ int glob_extend(char ***strv, const char *path) { if (k == GLOB_NOSPACE) return -ENOMEM; if (k != 0) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; if (strv_isempty(g.gl_pathv)) return -ENOENT; diff --git a/src/basic/hash-funcs.c b/src/basic/hash-funcs.c new file mode 100644 index 0000000000..d4affaffee --- /dev/null +++ b/src/basic/hash-funcs.c @@ -0,0 +1,83 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2014 Michal Schmidt + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "hash-funcs.h" + +void string_hash_func(const void *p, struct siphash *state) { + siphash24_compress(p, strlen(p) + 1, state); +} + +int string_compare_func(const void *a, const void *b) { + return strcmp(a, b); +} + +const struct hash_ops string_hash_ops = { + .hash = string_hash_func, + .compare = string_compare_func +}; + +void trivial_hash_func(const void *p, struct siphash *state) { + siphash24_compress(&p, sizeof(p), state); +} + +int trivial_compare_func(const void *a, const void *b) { + return a < b ? -1 : (a > b ? 1 : 0); +} + +const struct hash_ops trivial_hash_ops = { + .hash = trivial_hash_func, + .compare = trivial_compare_func +}; + +void uint64_hash_func(const void *p, struct siphash *state) { + siphash24_compress(p, sizeof(uint64_t), state); +} + +int uint64_compare_func(const void *_a, const void *_b) { + uint64_t a, b; + a = *(const uint64_t*) _a; + b = *(const uint64_t*) _b; + return a < b ? -1 : (a > b ? 1 : 0); +} + +const struct hash_ops uint64_hash_ops = { + .hash = uint64_hash_func, + .compare = uint64_compare_func +}; + +#if SIZEOF_DEV_T != 8 +void devt_hash_func(const void *p, struct siphash *state) { + siphash24_compress(p, sizeof(dev_t), state); +} + +int devt_compare_func(const void *_a, const void *_b) { + dev_t a, b; + a = *(const dev_t*) _a; + b = *(const dev_t*) _b; + return a < b ? -1 : (a > b ? 1 : 0); +} + +const struct hash_ops devt_hash_ops = { + .hash = devt_hash_func, + .compare = devt_compare_func +}; +#endif diff --git a/src/basic/hash-funcs.h b/src/basic/hash-funcs.h new file mode 100644 index 0000000000..c640eaf4d1 --- /dev/null +++ b/src/basic/hash-funcs.h @@ -0,0 +1,67 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2014 Michal Schmidt + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "macro.h" +#include "siphash24.h" + +typedef void (*hash_func_t)(const void *p, struct siphash *state); +typedef int (*compare_func_t)(const void *a, const void *b); + +struct hash_ops { + hash_func_t hash; + compare_func_t compare; +}; + +void string_hash_func(const void *p, struct siphash *state); +int string_compare_func(const void *a, const void *b) _pure_; +extern const struct hash_ops string_hash_ops; + +/* This will compare the passed pointers directly, and will not + * dereference them. This is hence not useful for strings or + * suchlike. */ +void trivial_hash_func(const void *p, struct siphash *state); +int trivial_compare_func(const void *a, const void *b) _const_; +extern const struct hash_ops trivial_hash_ops; + +/* 32bit values we can always just embed in the pointer itself, but + * in order to support 32bit archs we need store 64bit values + * indirectly, since they don't fit in a pointer. */ +void uint64_hash_func(const void *p, struct siphash *state); +int uint64_compare_func(const void *a, const void *b) _pure_; +extern const struct hash_ops uint64_hash_ops; + +/* On some archs dev_t is 32bit, and on others 64bit. And sometimes + * it's 64bit on 32bit archs, and sometimes 32bit on 64bit archs. Yuck! */ +#if SIZEOF_DEV_T != 8 +void devt_hash_func(const void *p, struct siphash *state) _pure_; +int devt_compare_func(const void *a, const void *b) _pure_; +extern const struct hash_ops devt_hash_ops = { + .hash = devt_hash_func, + .compare = devt_compare_func +}; +#else +#define devt_hash_func uint64_hash_func +#define devt_compare_func uint64_compare_func +#define devt_hash_ops uint64_hash_ops +#endif diff --git a/src/basic/hashmap.c b/src/basic/hashmap.c index b3954e3223..dcd8ae412d 100644 --- a/src/basic/hashmap.c +++ b/src/basic/hashmap.c @@ -37,6 +37,7 @@ #include "util.h" #ifdef ENABLE_DEBUG_HASHMAP +#include <pthread.h> #include "list.h" #endif @@ -279,66 +280,6 @@ static const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX] = { }, }; -void string_hash_func(const void *p, struct siphash *state) { - siphash24_compress(p, strlen(p) + 1, state); -} - -int string_compare_func(const void *a, const void *b) { - return strcmp(a, b); -} - -const struct hash_ops string_hash_ops = { - .hash = string_hash_func, - .compare = string_compare_func -}; - -void trivial_hash_func(const void *p, struct siphash *state) { - siphash24_compress(&p, sizeof(p), state); -} - -int trivial_compare_func(const void *a, const void *b) { - return a < b ? -1 : (a > b ? 1 : 0); -} - -const struct hash_ops trivial_hash_ops = { - .hash = trivial_hash_func, - .compare = trivial_compare_func -}; - -void uint64_hash_func(const void *p, struct siphash *state) { - siphash24_compress(p, sizeof(uint64_t), state); -} - -int uint64_compare_func(const void *_a, const void *_b) { - uint64_t a, b; - a = *(const uint64_t*) _a; - b = *(const uint64_t*) _b; - return a < b ? -1 : (a > b ? 1 : 0); -} - -const struct hash_ops uint64_hash_ops = { - .hash = uint64_hash_func, - .compare = uint64_compare_func -}; - -#if SIZEOF_DEV_T != 8 -void devt_hash_func(const void *p, struct siphash *state) { - siphash24_compress(p, sizeof(dev_t), state); -} - -int devt_compare_func(const void *_a, const void *_b) { - dev_t a, b; - a = *(const dev_t*) _a; - b = *(const dev_t*) _b; - return a < b ? -1 : (a > b ? 1 : 0); -} - -const struct hash_ops devt_hash_ops = { - .hash = devt_hash_func, - .compare = devt_compare_func -}; -#endif - static unsigned n_buckets(HashmapBase *h) { return h->has_indirect ? h->indirect.n_buckets : hashmap_type_info[h->type].n_direct_buckets; diff --git a/src/basic/hashmap.h b/src/basic/hashmap.h index 708811124b..fdba9c61ff 100644 --- a/src/basic/hashmap.h +++ b/src/basic/hashmap.h @@ -26,8 +26,8 @@ #include <stdbool.h> #include <stddef.h> +#include "hash-funcs.h" #include "macro.h" -#include "siphash24.h" #include "util.h" /* @@ -70,47 +70,6 @@ typedef struct { #define _IDX_ITERATOR_FIRST (UINT_MAX - 1) #define ITERATOR_FIRST ((Iterator) { .idx = _IDX_ITERATOR_FIRST, .next_key = NULL }) -typedef void (*hash_func_t)(const void *p, struct siphash *state); -typedef int (*compare_func_t)(const void *a, const void *b); - -struct hash_ops { - hash_func_t hash; - compare_func_t compare; -}; - -void string_hash_func(const void *p, struct siphash *state); -int string_compare_func(const void *a, const void *b) _pure_; -extern const struct hash_ops string_hash_ops; - -/* This will compare the passed pointers directly, and will not - * dereference them. This is hence not useful for strings or - * suchlike. */ -void trivial_hash_func(const void *p, struct siphash *state); -int trivial_compare_func(const void *a, const void *b) _const_; -extern const struct hash_ops trivial_hash_ops; - -/* 32bit values we can always just embedd in the pointer itself, but - * in order to support 32bit archs we need store 64bit values - * indirectly, since they don't fit in a pointer. */ -void uint64_hash_func(const void *p, struct siphash *state); -int uint64_compare_func(const void *a, const void *b) _pure_; -extern const struct hash_ops uint64_hash_ops; - -/* On some archs dev_t is 32bit, and on others 64bit. And sometimes - * it's 64bit on 32bit archs, and sometimes 32bit on 64bit archs. Yuck! */ -#if SIZEOF_DEV_T != 8 -void devt_hash_func(const void *p, struct siphash *state) _pure_; -int devt_compare_func(const void *a, const void *b) _pure_; -extern const struct hash_ops devt_hash_ops = { - .hash = devt_hash_func, - .compare = devt_compare_func -}; -#else -#define devt_hash_func uint64_hash_func -#define devt_compare_func uint64_compare_func -#define devt_hash_ops uint64_hash_ops -#endif - /* Macros for type checking */ #define PTR_COMPATIBLE_WITH_HASHMAP_BASE(h) \ (__builtin_types_compatible_p(typeof(h), HashmapBase*) || \ diff --git a/src/basic/hexdecoct.c b/src/basic/hexdecoct.c index 1e907de228..f30e028f45 100644 --- a/src/basic/hexdecoct.c +++ b/src/basic/hexdecoct.c @@ -514,14 +514,14 @@ int unbase64char(char c) { return -EINVAL; } -char *base64mem(const void *p, size_t l) { +ssize_t base64mem(const void *p, size_t l, char **out) { char *r, *z; const uint8_t *x; /* three input bytes makes four output bytes, padding is added so we must round up */ z = r = malloc(4 * (l + 2) / 3 + 1); if (!r) - return NULL; + return -ENOMEM; for (x = p; x < (const uint8_t*) p + (l / 3) * 3; x += 3) { /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */ @@ -549,9 +549,64 @@ char *base64mem(const void *p, size_t l) { } *z = 0; - return r; + *out = r; + return z - r; +} + +static int base64_append_width(char **prefix, int plen, + const char *sep, int indent, + const void *p, size_t l, + int width) { + + _cleanup_free_ char *x = NULL; + char *t, *s; + ssize_t slen, len, avail; + int line, lines; + + len = base64mem(p, l, &x); + if (len <= 0) + return len; + + lines = (len + width - 1) / width; + + slen = sep ? strlen(sep) : 0; + t = realloc(*prefix, plen + 1 + slen + (indent + width + 1) * lines); + if (!t) + return -ENOMEM; + + memcpy(t + plen, sep, slen); + + for (line = 0, s = t + plen + slen, avail = len; line < lines; line++) { + int act = MIN(width, avail); + + if (line > 0 || sep) { + memset(s, ' ', indent); + s += indent; + } + + memcpy(s, x + width * line, act); + s += act; + *(s++) = line < lines - 1 ? '\n' : '\0'; + avail -= act; + } + assert(avail == 0); + + *prefix = t; + return 0; } +int base64_append(char **prefix, int plen, + const void *p, size_t l, + int indent, int width) { + if (plen > width / 2 || plen + indent > width) + /* leave indent on the left, keep last column free */ + return base64_append_width(prefix, plen, "\n", indent, p, l, width - indent - 1); + else + /* leave plen on the left, keep last column free */ + return base64_append_width(prefix, plen, NULL, plen, p, l, width - plen - 1); +}; + + int unbase64mem(const char *p, size_t l, void **mem, size_t *_len) { _cleanup_free_ uint8_t *r = NULL; int a, b, c, d; diff --git a/src/basic/hexdecoct.h b/src/basic/hexdecoct.h index d9eb54a8a1..243c5e921e 100644 --- a/src/basic/hexdecoct.h +++ b/src/basic/hexdecoct.h @@ -49,7 +49,10 @@ int unbase64char(char c) _const_; char *base32hexmem(const void *p, size_t l, bool padding); int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *len); -char *base64mem(const void *p, size_t l); +ssize_t base64mem(const void *p, size_t l, char **out); +int base64_append(char **prefix, int plen, + const void *p, size_t l, + int margin, int width); int unbase64mem(const char *p, size_t l, void **mem, size_t *len); void hexdump(FILE *f, const void *p, size_t s); 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/io-util.h b/src/basic/io-util.h index 5f77a556c0..7d0d2bd810 100644 --- a/src/basic/io-util.h +++ b/src/basic/io-util.h @@ -77,3 +77,21 @@ static inline size_t IOVEC_INCREMENT(struct iovec *i, unsigned n, size_t k) { return k; } + +static inline bool FILE_SIZE_VALID(uint64_t l) { + /* ftruncate() and friends take an unsigned file size, but actually cannot deal with file sizes larger than + * 2^63 since the kernel internally handles it as signed value. This call allows checking for this early. */ + + return (l >> 63) == 0; +} + +static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) { + + /* Same as above, but allows one extra value: -1 as indication for infinity. */ + + if (l == (uint64_t) -1) + return true; + + return FILE_SIZE_VALID(l); + +} diff --git a/src/basic/json.c b/src/basic/json.c index 1523e9fb09..3a3d1ad1e1 100644 --- a/src/basic/json.c +++ b/src/basic/json.c @@ -322,7 +322,7 @@ static int json_parse_string(const char **p, char **ret) { else if (*c == 't') ch = '\t'; else if (*c == 'u') { - uint16_t x; + char16_t x; int r; r = unhex_ucs2(c + 1, &x); @@ -335,11 +335,11 @@ static int json_parse_string(const char **p, char **ret) { return -ENOMEM; if (!utf16_is_surrogate(x)) - n += utf8_encode_unichar(s + n, x); + n += utf8_encode_unichar(s + n, (char32_t) x); else if (utf16_is_trailing_surrogate(x)) return -EINVAL; else { - uint16_t y; + char16_t y; if (c[0] != '\\' || c[1] != 'u') return -EINVAL; diff --git a/src/basic/log.c b/src/basic/log.c index 1a9e6bdb91..18d4b82be2 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -352,7 +352,7 @@ static int write_to_console( highlight = LOG_PRI(level) <= LOG_ERR && show_color; if (show_location) { - snprintf(location, sizeof(location), "(%s:%i) ", file, line); + xsprintf(location, "(%s:%i) ", file, line); IOVEC_SET_STRING(iovec[n++], location); } @@ -777,7 +777,7 @@ static void log_assert( return; DISABLE_WARNING_FORMAT_NONLITERAL; - snprintf(buffer, sizeof(buffer), format, text, file, line, func); + xsprintf(buffer, format, text, file, line, func); REENABLE_WARNING; log_abort_msg = buffer; @@ -805,6 +805,52 @@ int log_oom_internal(const char *file, int line, const char *func) { return -ENOMEM; } +int log_format_iovec( + struct iovec *iovec, + unsigned iovec_len, + unsigned *n, + bool newline_separator, + int error, + const char *format, + va_list ap) { + + static const char nl = '\n'; + + while (format && *n + 1 < iovec_len) { + va_list aq; + char *m; + int r; + + /* We need to copy the va_list structure, + * since vasprintf() leaves it afterwards at + * an undefined location */ + + if (error != 0) + errno = error; + + va_copy(aq, ap); + r = vasprintf(&m, format, aq); + va_end(aq); + if (r < 0) + return -EINVAL; + + /* Now, jump enough ahead, so that we point to + * the next format string */ + VA_FORMAT_ADVANCE(format, ap); + + IOVEC_SET_STRING(iovec[(*n)++], m); + + if (newline_separator) { + iovec[*n].iov_base = (char*) &nl; + iovec[*n].iov_len = 1; + (*n)++; + } + + format = va_arg(ap, char *); + } + return 0; +} + int log_struct_internal( int level, int error, @@ -837,10 +883,10 @@ int log_struct_internal( char header[LINE_MAX]; struct iovec iovec[17] = {}; unsigned n = 0, i; + int r; struct msghdr mh = { .msg_iov = iovec, }; - static const char nl = '\n'; bool fallback = false; /* If the journal is available do structured logging */ @@ -848,43 +894,14 @@ int log_struct_internal( IOVEC_SET_STRING(iovec[n++], header); va_start(ap, format); - while (format && n + 1 < ELEMENTSOF(iovec)) { - va_list aq; - char *m; - - /* We need to copy the va_list structure, - * since vasprintf() leaves it afterwards at - * an undefined location */ - - if (error != 0) - errno = error; - - va_copy(aq, ap); - if (vasprintf(&m, format, aq) < 0) { - va_end(aq); - fallback = true; - goto finish; - } - va_end(aq); - - /* Now, jump enough ahead, so that we point to - * the next format string */ - VA_FORMAT_ADVANCE(format, ap); - - IOVEC_SET_STRING(iovec[n++], m); - - iovec[n].iov_base = (char*) &nl; - iovec[n].iov_len = 1; - n++; - - format = va_arg(ap, char *); + r = log_format_iovec(iovec, ELEMENTSOF(iovec), &n, true, error, format, ap); + if (r < 0) + fallback = true; + else { + mh.msg_iovlen = n; + (void) sendmsg(journal_fd, &mh, MSG_NOSIGNAL); } - mh.msg_iovlen = n; - - (void) sendmsg(journal_fd, &mh, MSG_NOSIGNAL); - - finish: va_end(ap); for (i = 1; i < n; i += 2) free(iovec[i].iov_base); diff --git a/src/basic/log.h b/src/basic/log.h index cda1e45cc8..8c7c5e4598 100644 --- a/src/basic/log.h +++ b/src/basic/log.h @@ -26,6 +26,7 @@ #include <stdbool.h> #include <stdlib.h> #include <sys/signalfd.h> +#include <sys/socket.h> #include <syslog.h> #include "sd-id128.h" @@ -127,6 +128,15 @@ int log_oom_internal( int line, const char *func); +int log_format_iovec( + struct iovec *iovec, + unsigned iovec_len, + unsigned *n, + bool newline_separator, + int error, + const char *format, + va_list ap); + /* This modifies the buffer passed! */ int log_dump_internal( int level, diff --git a/src/basic/macro.h b/src/basic/macro.h index 5088e6720d..c529c6ecad 100644 --- a/src/basic/macro.h +++ b/src/basic/macro.h @@ -320,18 +320,47 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) { #define SET_FLAG(v, flag, b) \ (v) = (b) ? ((v) | (flag)) : ((v) & ~(flag)) -#define IN_SET(x, y, ...) \ - ({ \ - static const typeof(y) _array[] = { (y), __VA_ARGS__ }; \ - const typeof(y) _x = (x); \ - unsigned _i; \ - bool _found = false; \ - for (_i = 0; _i < ELEMENTSOF(_array); _i++) \ - if (_array[_i] == _x) { \ - _found = true; \ - break; \ - } \ - _found; \ +#define CASE_F(X) case X: +#define CASE_F_1(CASE, X) CASE_F(X) +#define CASE_F_2(CASE, X, ...) CASE(X) CASE_F_1(CASE, __VA_ARGS__) +#define CASE_F_3(CASE, X, ...) CASE(X) CASE_F_2(CASE, __VA_ARGS__) +#define CASE_F_4(CASE, X, ...) CASE(X) CASE_F_3(CASE, __VA_ARGS__) +#define CASE_F_5(CASE, X, ...) CASE(X) CASE_F_4(CASE, __VA_ARGS__) +#define CASE_F_6(CASE, X, ...) CASE(X) CASE_F_5(CASE, __VA_ARGS__) +#define CASE_F_7(CASE, X, ...) CASE(X) CASE_F_6(CASE, __VA_ARGS__) +#define CASE_F_8(CASE, X, ...) CASE(X) CASE_F_7(CASE, __VA_ARGS__) +#define CASE_F_9(CASE, X, ...) CASE(X) CASE_F_8(CASE, __VA_ARGS__) +#define CASE_F_10(CASE, X, ...) CASE(X) CASE_F_9(CASE, __VA_ARGS__) +#define CASE_F_11(CASE, X, ...) CASE(X) CASE_F_10(CASE, __VA_ARGS__) +#define CASE_F_12(CASE, X, ...) CASE(X) CASE_F_11(CASE, __VA_ARGS__) +#define CASE_F_13(CASE, X, ...) CASE(X) CASE_F_12(CASE, __VA_ARGS__) +#define CASE_F_14(CASE, X, ...) CASE(X) CASE_F_13(CASE, __VA_ARGS__) +#define CASE_F_15(CASE, X, ...) CASE(X) CASE_F_14(CASE, __VA_ARGS__) +#define CASE_F_16(CASE, X, ...) CASE(X) CASE_F_15(CASE, __VA_ARGS__) +#define CASE_F_17(CASE, X, ...) CASE(X) CASE_F_16(CASE, __VA_ARGS__) +#define CASE_F_18(CASE, X, ...) CASE(X) CASE_F_17(CASE, __VA_ARGS__) +#define CASE_F_19(CASE, X, ...) CASE(X) CASE_F_18(CASE, __VA_ARGS__) +#define CASE_F_20(CASE, X, ...) CASE(X) CASE_F_19(CASE, __VA_ARGS__) + +#define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,NAME,...) NAME +#define FOR_EACH_MAKE_CASE(...) \ + GET_CASE_F(__VA_ARGS__,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12,CASE_F_11, \ + CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \ + (CASE_F,__VA_ARGS__) + +#define IN_SET(x, ...) \ + ({ \ + bool _found = false; \ + /* If the build breaks in the line below, you need to extend the case macros */ \ + static _unused_ char _static_assert__macros_need_to_be_extended[20 - sizeof((int[]){__VA_ARGS__})/sizeof(int)]; \ + switch(x) { \ + FOR_EACH_MAKE_CASE(__VA_ARGS__) \ + _found = true; \ + break; \ + default: \ + break; \ + } \ + _found; \ }) /* Define C11 thread_local attribute even on older gcc compiler diff --git a/src/basic/missing.h b/src/basic/missing.h index d539ed00e4..48ca04a8a1 100644 --- a/src/basic/missing.h +++ b/src/basic/missing.h @@ -36,6 +36,7 @@ #include <stdlib.h> #include <sys/resource.h> #include <sys/syscall.h> +#include <uchar.h> #include <unistd.h> #ifdef HAVE_AUDIT @@ -131,6 +132,10 @@ #define NETLINK_LIST_MEMBERSHIPS 9 #endif +#ifndef SOL_SCTP +#define SOL_SCTP 132 +#endif + #if !HAVE_DECL_PIVOT_ROOT static inline int pivot_root(const char *new_root, const char *put_old) { return syscall(SYS_pivot_root, new_root, put_old); @@ -970,6 +975,10 @@ static inline int setns(int fd, int nstype) { #define IFA_FLAGS 8 #endif +#ifndef IFA_F_MANAGETEMPADDR +#define IFA_F_MANAGETEMPADDR 0x100 +#endif + #ifndef IFA_F_NOPREFIXROUTE #define IFA_F_NOPREFIXROUTE 0x200 #endif @@ -1125,3 +1134,39 @@ static inline key_serial_t request_key(const char *type, const char *description #ifndef KEY_SPEC_USER_KEYRING #define KEY_SPEC_USER_KEYRING -4 #endif + +#ifndef PR_CAP_AMBIENT +#define PR_CAP_AMBIENT 47 +#endif + +#ifndef PR_CAP_AMBIENT_IS_SET +#define PR_CAP_AMBIENT_IS_SET 1 +#endif + +#ifndef PR_CAP_AMBIENT_RAISE +#define PR_CAP_AMBIENT_RAISE 2 +#endif + +#ifndef PR_CAP_AMBIENT_CLEAR_ALL +#define PR_CAP_AMBIENT_CLEAR_ALL 4 +#endif + +/* The following two defines are actually available in the kernel headers for longer, but we define them here anyway, + * since that makes it easier to use them in conjunction with the glibc net/if.h header which conflicts with + * linux/if.h. */ +#ifndef IF_OPER_UNKNOWN +#define IF_OPER_UNKNOWN 0 +#endif + +#ifndef IF_OPER_UP +#define IF_OPER_UP 6 + +#ifndef HAVE_DECL_CHAR32_T +#define char32_t uint32_t +#endif + +#ifndef HAVE_DECL_CHAR16_T +#define char16_t uint16_t +#endif + +#endif diff --git a/src/basic/nss-util.h b/src/basic/nss-util.h index cc30d93aad..4be0136da6 100644 --- a/src/basic/nss-util.h +++ b/src/basic/nss-util.h @@ -27,6 +27,8 @@ #include <pwd.h> #include <resolv.h> +#define NSS_SIGNALS_BLOCK SIGALRM,SIGVTALRM,SIGPIPE,SIGCHLD,SIGTSTP,SIGIO,SIGHUP,SIGUSR1,SIGUSR2,SIGPROF,SIGURG,SIGWINCH + #define NSS_GETHOSTBYNAME_PROTOTYPES(module) \ enum nss_status _nss_##module##_gethostbyname4_r( \ const char *name, \ diff --git a/src/basic/ordered-set.c b/src/basic/ordered-set.c new file mode 100644 index 0000000000..2e0bdf6488 --- /dev/null +++ b/src/basic/ordered-set.c @@ -0,0 +1,64 @@ +/*** + 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 "ordered-set.h" +#include "strv.h" + +int ordered_set_consume(OrderedSet *s, void *p) { + int r; + + r = ordered_set_put(s, p); + if (r <= 0) + free(p); + + return r; +} + +int ordered_set_put_strdup(OrderedSet *s, const char *p) { + char *c; + int r; + + assert(s); + assert(p); + + c = strdup(p); + if (!c) + return -ENOMEM; + + r = ordered_set_consume(s, c); + if (r == -EEXIST) + return 0; + + return r; +} + +int ordered_set_put_strdupv(OrderedSet *s, char **l) { + int n = 0, r; + char **i; + + STRV_FOREACH(i, l) { + r = ordered_set_put_strdup(s, *i); + if (r < 0) + return r; + + n += r; + } + + return n; +} diff --git a/src/basic/ordered-set.h b/src/basic/ordered-set.h index da10e90ff2..ab185c11aa 100644 --- a/src/basic/ordered-set.h +++ b/src/basic/ordered-set.h @@ -62,9 +62,15 @@ static inline bool ordered_set_iterate(OrderedSet *s, Iterator *i, void **value) return ordered_hashmap_iterate((OrderedHashmap*) s, i, value, NULL); } +int ordered_set_consume(OrderedSet *s, void *p); +int ordered_set_put_strdup(OrderedSet *s, const char *p); +int ordered_set_put_strdupv(OrderedSet *s, char **l); + #define ORDERED_SET_FOREACH(e, s, i) \ for ((i) = ITERATOR_FIRST; ordered_set_iterate((s), &(i), (void**)&(e)); ) DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedSet*, ordered_set_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedSet*, ordered_set_free_free); #define _cleanup_ordered_set_free_ _cleanup_(ordered_set_freep) +#define _cleanup_ordered_set_free_free_ _cleanup_(ordered_set_free_freep) 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/process-util.c b/src/basic/process-util.c index 4cc54a51fb..189ef9ab60 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -33,6 +33,9 @@ #include <sys/wait.h> #include <syslog.h> #include <unistd.h> +#ifdef HAVE_VALGRIND_VALGRIND_H +#include <valgrind/valgrind.h> +#endif #include "alloc-util.h" #include "escape.h" @@ -45,6 +48,7 @@ #include "missing.h" #include "process-util.h" #include "signal-util.h" +#include "stat-util.h" #include "string-table.h" #include "string-util.h" #include "user-util.h" @@ -634,6 +638,17 @@ bool pid_is_alive(pid_t pid) { return true; } +int pid_from_same_root_fs(pid_t pid) { + const char *root; + + if (pid < 0) + return 0; + + root = procfs_file_alloca(pid, "root"); + + return files_same(root, "/proc/1/root"); +} + bool is_main_thread(void) { static thread_local int cached = 0; @@ -730,6 +745,23 @@ const char* personality_to_string(unsigned long p) { return NULL; } +void valgrind_summary_hack(void) { +#ifdef HAVE_VALGRIND_VALGRIND_H + if (getpid() == 1 && RUNNING_ON_VALGRIND) { + pid_t pid; + pid = raw_clone(SIGCHLD, NULL); + if (pid < 0) + log_emergency_errno(errno, "Failed to fork off valgrind helper: %m"); + else if (pid == 0) + exit(EXIT_SUCCESS); + else { + log_info("Spawned valgrind helper as PID "PID_FMT".", pid); + (void) wait_for_terminate(pid, NULL); + } + } +#endif +} + static const char *const ioprio_class_table[] = { [IOPRIO_CLASS_NONE] = "none", [IOPRIO_CLASS_RT] = "realtime", diff --git a/src/basic/process-util.h b/src/basic/process-util.h index f4c4437624..f5d193e762 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -70,6 +70,7 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value); bool pid_is_alive(pid_t pid); bool pid_is_unwaited(pid_t pid); +int pid_from_same_root_fs(pid_t pid); bool is_main_thread(void); @@ -98,3 +99,5 @@ int sched_policy_from_string(const char *s); #define PTR_TO_PID(p) ((pid_t) ((uintptr_t) p)) #define PID_TO_PTR(p) ((void*) ((uintptr_t) p)) + +void valgrind_summary_hack(void); diff --git a/src/basic/random-util.c b/src/basic/random-util.c index e1543da5a3..2f468db770 100644 --- a/src/basic/random-util.c +++ b/src/basic/random-util.c @@ -95,17 +95,18 @@ void initialize_srand(void) { if (srand_called) return; - x = 0; - #ifdef HAVE_SYS_AUXV_H - /* The kernel provides us with a bit of entropy in auxv, so - * let's try to make use of that to seed the pseudo-random - * generator. It's better than nothing... */ + /* The kernel provides us with 16 bytes of entropy in auxv, so let's try to make use of that to seed the + * pseudo-random generator. It's better than nothing... */ auxv = (void*) getauxval(AT_RANDOM); - if (auxv) - x ^= *(unsigned*) auxv; + if (auxv) { + assert_cc(sizeof(x) < 16); + memcpy(&x, auxv, sizeof(x)); + } else #endif + x = 0; + x ^= (unsigned) now(CLOCK_REALTIME); x ^= (unsigned) gettid(); diff --git a/src/basic/rlimit-util.c b/src/basic/rlimit-util.c index 44f885db16..8a921a27cb 100644 --- a/src/basic/rlimit-util.c +++ b/src/basic/rlimit-util.c @@ -22,10 +22,14 @@ #include <errno.h> #include <sys/resource.h> +#include "alloc-util.h" +#include "extract-word.h" +#include "formats-util.h" #include "macro.h" #include "missing.h" #include "rlimit-util.h" #include "string-table.h" +#include "time-util.h" int setrlimit_closest(int resource, const struct rlimit *rlim) { struct rlimit highest, fixed; @@ -51,6 +55,202 @@ int setrlimit_closest(int resource, const struct rlimit *rlim) { return 0; } +static int rlimit_parse_u64(const char *val, rlim_t *ret) { + uint64_t u; + int r; + + assert(val); + assert(ret); + + if (streq(val, "infinity")) { + *ret = RLIM_INFINITY; + return 0; + } + + /* setrlimit(2) suggests rlim_t is always 64bit on Linux. */ + assert_cc(sizeof(rlim_t) == sizeof(uint64_t)); + + r = safe_atou64(val, &u); + if (r < 0) + return r; + if (u >= (uint64_t) RLIM_INFINITY) + return -ERANGE; + + *ret = (rlim_t) u; + return 0; +} + +static int rlimit_parse_size(const char *val, rlim_t *ret) { + uint64_t u; + int r; + + assert(val); + assert(ret); + + if (streq(val, "infinity")) { + *ret = RLIM_INFINITY; + return 0; + } + + r = parse_size(val, 1024, &u); + if (r < 0) + return r; + if (u >= (uint64_t) RLIM_INFINITY) + return -ERANGE; + + *ret = (rlim_t) u; + return 0; +} + +static int rlimit_parse_sec(const char *val, rlim_t *ret) { + uint64_t u; + usec_t t; + int r; + + assert(val); + assert(ret); + + if (streq(val, "infinity")) { + *ret = RLIM_INFINITY; + return 0; + } + + r = parse_sec(val, &t); + if (r < 0) + return r; + if (t == USEC_INFINITY) { + *ret = RLIM_INFINITY; + return 0; + } + + u = (uint64_t) DIV_ROUND_UP(t, USEC_PER_SEC); + if (u >= (uint64_t) RLIM_INFINITY) + return -ERANGE; + + *ret = (rlim_t) u; + return 0; +} + +static int rlimit_parse_usec(const char *val, rlim_t *ret) { + usec_t t; + int r; + + assert(val); + assert(ret); + + if (streq(val, "infinity")) { + *ret = RLIM_INFINITY; + return 0; + } + + r = parse_time(val, &t, 1); + if (r < 0) + return r; + if (t == USEC_INFINITY) { + *ret = RLIM_INFINITY; + return 0; + } + + *ret = (rlim_t) t; + return 0; +} + +static int (*const rlimit_parse_table[_RLIMIT_MAX])(const char *val, rlim_t *ret) = { + [RLIMIT_CPU] = rlimit_parse_sec, + [RLIMIT_FSIZE] = rlimit_parse_size, + [RLIMIT_DATA] = rlimit_parse_size, + [RLIMIT_STACK] = rlimit_parse_size, + [RLIMIT_CORE] = rlimit_parse_size, + [RLIMIT_RSS] = rlimit_parse_size, + [RLIMIT_NOFILE] = rlimit_parse_u64, + [RLIMIT_AS] = rlimit_parse_size, + [RLIMIT_NPROC] = rlimit_parse_u64, + [RLIMIT_MEMLOCK] = rlimit_parse_size, + [RLIMIT_LOCKS] = rlimit_parse_u64, + [RLIMIT_SIGPENDING] = rlimit_parse_u64, + [RLIMIT_MSGQUEUE] = rlimit_parse_size, + [RLIMIT_NICE] = rlimit_parse_u64, + [RLIMIT_RTPRIO] = rlimit_parse_u64, + [RLIMIT_RTTIME] = rlimit_parse_usec, +}; + +int rlimit_parse_one(int resource, const char *val, rlim_t *ret) { + assert(val); + assert(ret); + + if (resource < 0) + return -EINVAL; + if (resource >= _RLIMIT_MAX) + return -EINVAL; + + return rlimit_parse_table[resource](val, ret); +} + +int rlimit_parse(int resource, const char *val, struct rlimit *ret) { + _cleanup_free_ char *hard = NULL, *soft = NULL; + rlim_t hl, sl; + int r; + + assert(val); + assert(ret); + + r = extract_first_word(&val, &soft, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + r = rlimit_parse_one(resource, soft, &sl); + if (r < 0) + return r; + + r = extract_first_word(&val, &hard, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (!isempty(val)) + return -EINVAL; + if (r == 0) + hl = sl; + else { + r = rlimit_parse_one(resource, hard, &hl); + if (r < 0) + return r; + if (sl > hl) + return -EILSEQ; + } + + *ret = (struct rlimit) { + .rlim_cur = sl, + .rlim_max = hl, + }; + + return 0; +} + +int rlimit_format(const struct rlimit *rl, char **ret) { + char *s = NULL; + + assert(rl); + assert(ret); + + if (rl->rlim_cur >= RLIM_INFINITY && rl->rlim_max >= RLIM_INFINITY) + s = strdup("infinity"); + else if (rl->rlim_cur >= RLIM_INFINITY) + (void) asprintf(&s, "infinity:" RLIM_FMT, rl->rlim_max); + else if (rl->rlim_max >= RLIM_INFINITY) + (void) asprintf(&s, RLIM_FMT ":infinity", rl->rlim_cur); + else if (rl->rlim_cur == rl->rlim_max) + (void) asprintf(&s, RLIM_FMT, rl->rlim_cur); + else + (void) asprintf(&s, RLIM_FMT ":" RLIM_FMT, rl->rlim_cur, rl->rlim_max); + + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + static const char* const rlimit_table[_RLIMIT_MAX] = { [RLIMIT_CPU] = "LimitCPU", [RLIMIT_FSIZE] = "LimitFSIZE", diff --git a/src/basic/rlimit-util.h b/src/basic/rlimit-util.h index 262f86dd04..abf3c57934 100644 --- a/src/basic/rlimit-util.h +++ b/src/basic/rlimit-util.h @@ -30,4 +30,9 @@ int rlimit_from_string(const char *s) _pure_; int setrlimit_closest(int resource, const struct rlimit *rlim); +int rlimit_parse_one(int resource, const char *val, rlim_t *ret); +int rlimit_parse(int resource, const char *val, struct rlimit *ret); + +int rlimit_format(const struct rlimit *rl, char **ret); + #define RLIMIT_MAKE_CONST(lim) ((struct rlimit) { lim, lim }) diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c index 14f8474da0..4807561723 100644 --- a/src/basic/rm-rf.c +++ b/src/basic/rm-rf.c @@ -82,7 +82,7 @@ int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { errno = 0; de = readdir(d); if (!de) { - if (errno != 0 && ret == 0) + if (errno > 0 && ret == 0) ret = -errno; return ret; } diff --git a/src/basic/signal-util.c b/src/basic/signal-util.c index 7637fccb2f..315efadd93 100644 --- a/src/basic/signal-util.c +++ b/src/basic/signal-util.c @@ -26,6 +26,7 @@ #include "macro.h" #include "parse-util.h" #include "signal-util.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" @@ -234,9 +235,9 @@ const char *signal_to_string(int signo) { return name; if (signo >= SIGRTMIN && signo <= SIGRTMAX) - snprintf(buf, sizeof(buf), "RTMIN+%d", signo - SIGRTMIN); + xsprintf(buf, "RTMIN+%d", signo - SIGRTMIN); else - snprintf(buf, sizeof(buf), "%d", signo); + xsprintf(buf, "%d", signo); return buf; } diff --git a/src/basic/signal-util.h b/src/basic/signal-util.h index e7393e2dac..5d94d1c363 100644 --- a/src/basic/signal-util.h +++ b/src/basic/signal-util.h @@ -41,3 +41,14 @@ int signal_from_string(const char *s) _pure_; int signal_from_string_try_harder(const char *s); void nop_signal_handler(int sig); + +static inline void block_signals_reset(sigset_t *ss) { + assert_se(sigprocmask(SIG_SETMASK, ss, NULL) >= 0); +} + +#define BLOCK_SIGNALS(...) \ + _cleanup_(block_signals_reset) sigset_t _saved_sigset = ({ \ + sigset_t t; \ + assert_se(sigprocmask_many(SIG_BLOCK, &t, __VA_ARGS__, -1) >= 0); \ + t; \ + }) diff --git a/src/basic/siphash24.h b/src/basic/siphash24.h index 3f7e20362b..54e2420cc6 100644 --- a/src/basic/siphash24.h +++ b/src/basic/siphash24.h @@ -16,6 +16,8 @@ struct siphash { void siphash24_init(struct siphash *state, const uint8_t k[16]); void siphash24_compress(const void *in, size_t inlen, struct siphash *state); +#define siphash24_compress_byte(byte, state) siphash24_compress((const uint8_t[]) { (byte) }, 1, (state)) + uint64_t siphash24_finalize(struct siphash *state); uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]); diff --git a/src/basic/socket-label.c b/src/basic/socket-label.c index e169439e04..bd206586ce 100644 --- a/src/basic/socket-label.c +++ b/src/basic/socket-label.c @@ -147,7 +147,7 @@ int socket_address_listen( return r; } -int make_socket_fd(int log_level, const char* address, int flags) { +int make_socket_fd(int log_level, const char* address, int type, int flags) { SocketAddress a; int fd, r; @@ -155,7 +155,9 @@ int make_socket_fd(int log_level, const char* address, int flags) { if (r < 0) return log_error_errno(r, "Failed to parse socket address \"%s\": %m", address); - fd = socket_address_listen(&a, flags, SOMAXCONN, SOCKET_ADDRESS_DEFAULT, + a.type = type; + + fd = socket_address_listen(&a, type | flags, SOMAXCONN, SOCKET_ADDRESS_DEFAULT, NULL, false, false, false, 0755, 0644, NULL); if (fd < 0 || log_get_max_level() >= log_level) { _cleanup_free_ char *p = NULL; diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index be144e157d..f2bb3bab86 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -601,7 +601,7 @@ int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ return 0; } -int getpeername_pretty(int fd, char **ret) { +int getpeername_pretty(int fd, bool include_port, char **ret) { union sockaddr_union sa; socklen_t salen = sizeof(sa); int r; @@ -631,7 +631,7 @@ int getpeername_pretty(int fd, char **ret) { /* For remote sockets we translate IPv6 addresses back to IPv4 * if applicable, since that's nicer. */ - return sockaddr_pretty(&sa.sa, salen, true, true, ret); + return sockaddr_pretty(&sa.sa, salen, true, include_port, ret); } int getsockname_pretty(int fd, char **ret) { diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h index 6da1df68d8..2323ccf3ab 100644 --- a/src/basic/socket-util.h +++ b/src/basic/socket-util.h @@ -89,7 +89,7 @@ int socket_address_listen( mode_t directory_mode, mode_t socket_mode, const char *label); -int make_socket_fd(int log_level, const char* address, int flags); +int make_socket_fd(int log_level, const char* address, int type, int flags); bool socket_address_is(const SocketAddress *a, const char *s, int type); bool socket_address_is_netlink(const SocketAddress *a, const char *s); @@ -105,7 +105,7 @@ bool socket_ipv6_is_supported(void); int sockaddr_port(const struct sockaddr *_sa) _pure_; int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ipv6, bool include_port, char **ret); -int getpeername_pretty(int fd, char **ret); +int getpeername_pretty(int fd, bool include_port, char **ret); int getsockname_pretty(int fd, char **ret); int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret); diff --git a/src/basic/string-table.h b/src/basic/string-table.h index 2181a3a767..588404ab5a 100644 --- a/src/basic/string-table.h +++ b/src/basic/string-table.h @@ -47,16 +47,34 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \ } +#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \ + scope type name##_from_string(const char *s) { \ + int b; \ + b = parse_boolean(s); \ + if (b == 0) \ + return (type) 0; \ + else if (b > 0) \ + return yes; \ + return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \ + } + #define _DEFINE_STRING_TABLE_LOOKUP(name,type,scope) \ _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope) \ struct __useless_struct_to_allow_trailing_semicolon__ +#define _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,scope) \ + _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ + _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \ + struct __useless_struct_to_allow_trailing_semicolon__ + #define DEFINE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,) #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,static) #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,static) #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,static) +#define DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes) _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,) + /* For string conversions where numbers are also acceptable */ #define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max) \ int name##_to_string_alloc(type i, char **str) { \ diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 8178c7093f..cb75b09c74 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -317,18 +317,67 @@ char *truncate_nl(char *s) { return s; } +char ascii_tolower(char x) { + + if (x >= 'A' && x <= 'Z') + return x - 'A' + 'a'; + + return x; +} + char *ascii_strlower(char *t) { char *p; assert(t); for (p = t; *p; p++) - if (*p >= 'A' && *p <= 'Z') - *p = *p - 'A' + 'a'; + *p = ascii_tolower(*p); return t; } +char *ascii_strlower_n(char *t, size_t n) { + size_t i; + + if (n <= 0) + return t; + + for (i = 0; i < n; i++) + t[i] = ascii_tolower(t[i]); + + return t; +} + +int ascii_strcasecmp_n(const char *a, const char *b, size_t n) { + + for (; n > 0; a++, b++, n--) { + int x, y; + + x = (int) (uint8_t) ascii_tolower(*a); + y = (int) (uint8_t) ascii_tolower(*b); + + if (x != y) + return x - y; + } + + return 0; +} + +int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m) { + int r; + + r = ascii_strcasecmp_n(a, b, MIN(n, m)); + if (r != 0) + return r; + + if (n < m) + return -1; + else if (n > m) + return 1; + else + return 0; +} + bool chars_intersect(const char *a, const char *b) { const char *p; @@ -401,6 +450,7 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne char *e; const char *i, *j; unsigned k, len, len2; + int r; assert(s); assert(percent <= 100); @@ -420,10 +470,10 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne k = 0; for (i = s; k < x && i < s + old_length; i = utf8_next_char(i)) { - int c; + char32_t c; - c = utf8_encoded_to_unichar(i); - if (c < 0) + r = utf8_encoded_to_unichar(i, &c); + if (r < 0) return NULL; k += unichar_iswide(c) ? 2 : 1; } @@ -432,11 +482,11 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne x ++; for (j = s + old_length; k < new_length && j > i; ) { - int c; + char32_t c; j = utf8_prev_char(j); - c = utf8_encoded_to_unichar(j); - if (c < 0) + r = utf8_encoded_to_unichar(j, &c); + if (r < 0) return NULL; k += unichar_iswide(c) ? 2 : 1; } diff --git a/src/basic/string-util.h b/src/basic/string-util.h index b59b9b5a71..8ea18f45aa 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -130,7 +130,12 @@ char *strstrip(char *s); char *delete_chars(char *s, const char *bad); char *truncate_nl(char *s); -char *ascii_strlower(char *path); +char ascii_tolower(char x); +char *ascii_strlower(char *s); +char *ascii_strlower_n(char *s, size_t n); + +int ascii_strcasecmp_n(const char *a, const char *b, size_t n); +int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m); bool chars_intersect(const char *a, const char *b) _pure_; diff --git a/src/basic/strv.c b/src/basic/strv.c index 0a3d15706f..5532c53ad1 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -29,6 +29,7 @@ #include "alloc-util.h" #include "escape.h" #include "extract-word.h" +#include "fileio.h" #include "string-util.h" #include "strv.h" #include "util.h" @@ -871,3 +872,22 @@ rollback: nl[k] = NULL; return -ENOMEM; } + +int fputstrv(FILE *f, char **l, const char *separator, bool *space) { + bool b = false; + char **s; + int r; + + /* Like fputs(), but for strv, and with a less stupid argument order */ + + if (!space) + space = &b; + + STRV_FOREACH(s, l) { + r = fputs_with_space(f, *s, separator, space); + if (r < 0) + return r; + } + + return 0; +} diff --git a/src/basic/strv.h b/src/basic/strv.h index bb61db2638..560f90115c 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -169,3 +169,5 @@ char ***strv_free_free(char ***l); char **strv_skip(char **l, size_t n); int strv_extend_n(char ***l, const char *value, size_t n); + +int fputstrv(FILE *f, char **l, const char *separator, bool *space); diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index a39764472b..0a9d2bbdef 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'); @@ -726,9 +726,7 @@ bool tty_is_vc_resolve(const char *tty) { } const char *default_term_for_tty(const char *tty) { - assert(tty); - - return tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt220"; + return tty && tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt220"; } int fd_columns(int fd) { @@ -1135,3 +1133,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/time-util.h b/src/basic/time-util.h index 7321e3c670..9c7758a959 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -69,7 +69,7 @@ typedef struct dual_timestamp { #define FORMAT_TIMESTAMP_RELATIVE_MAX 256 #define FORMAT_TIMESPAN_MAX 64 -#define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1) +#define TIME_T_MAX (time_t)((UINTMAX_C(1) << ((sizeof(time_t) << 3) - 1)) - 1) #define DUAL_TIMESTAMP_NULL ((struct dual_timestamp) { 0ULL, 0ULL }) @@ -127,3 +127,16 @@ time_t mktime_or_timegm(struct tm *tm, bool utc); struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc); unsigned long usec_to_jiffies(usec_t usec); + +static inline usec_t usec_add(usec_t a, usec_t b) { + usec_t c; + + /* Adds two time values, and makes sure USEC_INFINITY as input results as USEC_INFINITY in output, and doesn't + * overflow. */ + + c = a + b; + if (c < a || c < b) /* overflow check */ + return USEC_INFINITY; + + return c; +} diff --git a/src/basic/unit-name.c b/src/basic/unit-name.c index 5fc3b9d6fd..d4a3062658 100644 --- a/src/basic/unit-name.c +++ b/src/basic/unit-name.c @@ -27,6 +27,7 @@ #include "alloc-util.h" #include "bus-label.h" +#include "glob-util.h" #include "hexdecoct.h" #include "macro.h" #include "path-util.h" @@ -35,10 +36,22 @@ #include "strv.h" #include "unit-name.h" +/* Characters valid in a unit name. */ #define VALID_CHARS \ - DIGITS LETTERS \ + DIGITS \ + LETTERS \ ":-_.\\" +/* The same, but also permits the single @ character that may appear */ +#define VALID_CHARS_WITH_AT \ + "@" \ + VALID_CHARS + +/* All chars valid in a unit name glob */ +#define VALID_CHARS_GLOB \ + VALID_CHARS_WITH_AT \ + "[]!-*?" + bool unit_name_is_valid(const char *n, UnitNameFlags flags) { const char *e, *i, *at; @@ -637,7 +650,7 @@ static char *do_escape_mangle(const char *f, UnitNameMangle allow_globs, char *t /* We'll only escape the obvious characters here, to play * safe. */ - valid_chars = allow_globs == UNIT_NAME_GLOB ? "@" VALID_CHARS "[]!-*?" : "@" VALID_CHARS; + valid_chars = allow_globs == UNIT_NAME_GLOB ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT; for (; *f; f++) { if (*f == '/') @@ -672,15 +685,15 @@ int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, c if (!unit_suffix_is_valid(suffix)) return -EINVAL; - if (unit_name_is_valid(name, UNIT_NAME_ANY)) { - /* No mangling necessary... */ - s = strdup(name); - if (!s) - return -ENOMEM; + /* Already a fully valid unit name? If so, no mangling is necessary... */ + if (unit_name_is_valid(name, UNIT_NAME_ANY)) + goto good; - *ret = s; - return 0; - } + /* Already a fully valid globbing expression? If so, no mangling is necessary either... */ + if (allow_globs == UNIT_NAME_GLOB && + string_is_glob(name) && + in_charset(name, VALID_CHARS_GLOB)) + goto good; if (is_device_path(name)) { r = unit_name_from_path(name, ".device", ret); @@ -705,11 +718,21 @@ int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, c t = do_escape_mangle(name, allow_globs, s); *t = 0; - if (unit_name_to_type(s) < 0) + /* Append a suffix if it doesn't have any, but only if this is not a glob, so that we can allow "foo.*" as a + * valid glob. */ + if ((allow_globs != UNIT_NAME_GLOB || !string_is_glob(s)) && unit_name_to_type(s) < 0) strcpy(t, suffix); *ret = s; return 1; + +good: + s = strdup(name); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; } int slice_build_parent_slice(const char *slice, char **ret) { 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/utf8.c b/src/basic/utf8.c index 124effd6df..3f024f7e58 100644 --- a/src/basic/utf8.c +++ b/src/basic/utf8.c @@ -53,7 +53,7 @@ #include "macro.h" #include "utf8.h" -bool unichar_is_valid(uint32_t ch) { +bool unichar_is_valid(char32_t ch) { if (ch >= 0x110000) /* End of unicode space */ return false; @@ -67,7 +67,7 @@ bool unichar_is_valid(uint32_t ch) { return true; } -static bool unichar_is_control(uint32_t ch) { +static bool unichar_is_control(char32_t ch) { /* 0 to ' '-1 is the C0 range. @@ -103,8 +103,9 @@ static int utf8_encoded_expected_len(const char *str) { } /* decode one unicode char */ -int utf8_encoded_to_unichar(const char *str) { - int unichar, len, i; +int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar) { + char32_t unichar; + int len, i; assert(str); @@ -112,34 +113,37 @@ int utf8_encoded_to_unichar(const char *str) { switch (len) { case 1: - return (int)str[0]; + *ret_unichar = (char32_t)str[0]; + return 0; case 2: unichar = str[0] & 0x1f; break; case 3: - unichar = (int)str[0] & 0x0f; + unichar = (char32_t)str[0] & 0x0f; break; case 4: - unichar = (int)str[0] & 0x07; + unichar = (char32_t)str[0] & 0x07; break; case 5: - unichar = (int)str[0] & 0x03; + unichar = (char32_t)str[0] & 0x03; break; case 6: - unichar = (int)str[0] & 0x01; + unichar = (char32_t)str[0] & 0x01; break; default: return -EINVAL; } for (i = 1; i < len; i++) { - if (((int)str[i] & 0xc0) != 0x80) + if (((char32_t)str[i] & 0xc0) != 0x80) return -EINVAL; unichar <<= 6; - unichar |= (int)str[i] & 0x3f; + unichar |= (char32_t)str[i] & 0x3f; } - return unichar; + *ret_unichar = unichar; + + return 0; } bool utf8_is_printable_newline(const char* str, size_t length, bool newline) { @@ -148,15 +152,16 @@ bool utf8_is_printable_newline(const char* str, size_t length, bool newline) { assert(str); for (p = str; length;) { - int encoded_len, val; + int encoded_len, r; + char32_t val; encoded_len = utf8_encoded_valid_unichar(p); if (encoded_len < 0 || (size_t) encoded_len > length) return false; - val = utf8_encoded_to_unichar(p); - if (val < 0 || + r = utf8_encoded_to_unichar(p, &val); + if (r < 0 || unichar_is_control(val) || (!newline && val == '\n')) return false; @@ -276,7 +281,7 @@ char *ascii_is_valid(const char *str) { * Returns: The length in bytes that the UTF-8 representation does or would * occupy. */ -size_t utf8_encode_unichar(char *out_utf8, uint32_t g) { +size_t utf8_encode_unichar(char *out_utf8, char32_t g) { if (g < (1 << 7)) { if (out_utf8) @@ -320,7 +325,7 @@ char *utf16_to_utf8(const void *s, size_t length) { t = r; while (f < (const uint8_t*) s + length) { - uint16_t w1, w2; + char16_t w1, w2; /* see RFC 2781 section 2.2 */ @@ -354,7 +359,7 @@ char *utf16_to_utf8(const void *s, size_t length) { } /* expected size used to encode one unicode char */ -static int utf8_unichar_to_encoded_len(int unichar) { +static int utf8_unichar_to_encoded_len(char32_t unichar) { if (unichar < 0x80) return 1; @@ -372,7 +377,8 @@ static int utf8_unichar_to_encoded_len(int unichar) { /* validate one encoded unicode char and return its length */ int utf8_encoded_valid_unichar(const char *str) { - int len, unichar, i; + int len, i, r; + char32_t unichar; assert(str); @@ -389,7 +395,9 @@ int utf8_encoded_valid_unichar(const char *str) { if ((str[i] & 0x80) != 0x80) return -EINVAL; - unichar = utf8_encoded_to_unichar(str); + r = utf8_encoded_to_unichar(str, &unichar); + if (r < 0) + return r; /* check if encoded length matches encoded value */ if (utf8_unichar_to_encoded_len(unichar) != len) diff --git a/src/basic/utf8.h b/src/basic/utf8.h index 16c4b5b55d..3e2e35b967 100644 --- a/src/basic/utf8.h +++ b/src/basic/utf8.h @@ -24,12 +24,14 @@ #include <stdbool.h> #include <stddef.h> #include <stdint.h> +#include <uchar.h> #include "macro.h" +#include "missing.h" #define UTF8_REPLACEMENT_CHARACTER "\xef\xbf\xbd" -bool unichar_is_valid(uint32_t c); +bool unichar_is_valid(char32_t c); const char *utf8_is_valid(const char *s) _pure_; char *ascii_is_valid(const char *s) _pure_; @@ -40,20 +42,20 @@ bool utf8_is_printable_newline(const char* str, size_t length, bool newline) _pu char *utf8_escape_invalid(const char *s); char *utf8_escape_non_printable(const char *str); -size_t utf8_encode_unichar(char *out_utf8, uint32_t g); +size_t utf8_encode_unichar(char *out_utf8, char32_t g); char *utf16_to_utf8(const void *s, size_t length); int utf8_encoded_valid_unichar(const char *str); -int utf8_encoded_to_unichar(const char *str); +int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar); -static inline bool utf16_is_surrogate(uint16_t c) { +static inline bool utf16_is_surrogate(char16_t c) { return (0xd800 <= c && c <= 0xdfff); } -static inline bool utf16_is_trailing_surrogate(uint16_t c) { +static inline bool utf16_is_trailing_surrogate(char16_t c) { return (0xdc00 <= c && c <= 0xdfff); } -static inline uint32_t utf16_surrogate_pair_to_unichar(uint16_t lead, uint16_t trail) { +static inline char32_t utf16_surrogate_pair_to_unichar(char16_t lead, char16_t trail) { return ((lead - 0xd800) << 10) + (trail - 0xdc00) + 0x10000; } 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/basic/verbs.c b/src/basic/verbs.c index 7feb47c48e..6dded9fb77 100644 --- a/src/basic/verbs.c +++ b/src/basic/verbs.c @@ -28,6 +28,7 @@ #include "macro.h" #include "string-util.h" #include "verbs.h" +#include "virt.h" int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { const Verb *verb; @@ -84,6 +85,11 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { return -EINVAL; } + if ((verb->flags & VERB_NOCHROOT) && running_in_chroot() > 0) { + log_info("Running in chroot, ignoring request."); + return 0; + } + if (name) return verb->dispatch(left, argv + optind, userdata); else { diff --git a/src/basic/verbs.h b/src/basic/verbs.h index d59e4d59b8..4132cad773 100644 --- a/src/basic/verbs.h +++ b/src/basic/verbs.h @@ -22,7 +22,8 @@ ***/ #define VERB_ANY ((unsigned) -1) -#define VERB_DEFAULT 1 +#define VERB_DEFAULT 1U +#define VERB_NOCHROOT 2U typedef struct { const char *verb; diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 4cf42d17f3..13cf323bb7 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -39,6 +39,7 @@ #include "alloc-util.h" #include "blkid-util.h" +#include "dirent-util.h" #include "efivars.h" #include "fd-util.h" #include "fileio.h" @@ -249,13 +250,10 @@ static int enumerate_binaries(const char *esp_path, const char *path, const char return log_error_errno(errno, "Failed to read \"%s\": %m", p); } - while ((de = readdir(d))) { + FOREACH_DIRENT(de, d, break) { _cleanup_close_ int fd = -1; _cleanup_free_ char *v = NULL; - if (de->d_name[0] == '.') - continue; - if (!endswith_no_case(de->d_name, ".efi")) continue; @@ -270,9 +268,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 +322,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; @@ -614,12 +612,9 @@ static int install_binaries(const char *esp_path, bool force) { if (!d) return log_error_errno(errno, "Failed to open \""BOOTLIBDIR"\": %m"); - while ((de = readdir(d))) { + FOREACH_DIRENT(de, d, break) { int k; - if (de->d_name[0] == '.') - continue; - if (!endswith_no_case(de->d_name, ".efi")) continue; @@ -797,13 +792,10 @@ static int remove_boot_efi(const char *esp_path) { return log_error_errno(errno, "Failed to open directory \"%s\": %m", p); } - while ((de = readdir(d))) { + FOREACH_DIRENT(de, d, break) { _cleanup_close_ int fd = -1; _cleanup_free_ char *v = NULL; - if (de->d_name[0] == '.') - continue; - if (!endswith_no_case(de->d_name, ".efi")) continue; diff --git a/src/bootchart/svg.c b/src/bootchart/svg.c index 2bf473ffc1..79e261abe5 100644 --- a/src/bootchart/svg.c +++ b/src/bootchart/svg.c @@ -37,6 +37,7 @@ #include "fileio.h" #include "list.h" #include "macro.h" +#include "stdio-util.h" #include "store.h" #include "svg.h" #include "utf8.h" @@ -171,7 +172,7 @@ static int svg_title(FILE *of, const char *build, int pscount, double log_start, strncpy(rootbdev, &c[10], sizeof(rootbdev) - 1); rootbdev[3] = '\0'; - snprintf(filename, sizeof(filename), "/sys/block/%s/device/model", rootbdev); + xsprintf(filename, "/sys/block/%s/device/model", rootbdev); r = read_one_line_file(filename, &model); if (r < 0) diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index 0a5c11ad0c..4894296554 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -40,6 +40,7 @@ #include "parse-util.h" #include "path-util.h" #include "process-util.h" +#include "stdio-util.h" #include "terminal-util.h" #include "unit-name.h" #include "util.h" @@ -565,9 +566,9 @@ static void display(Hashmap *a) { } if (arg_cpu_type == CPU_PERCENT) - snprintf(buffer, sizeof(buffer), "%6s", "%CPU"); + xsprintf(buffer, "%6s", "%CPU"); else - snprintf(buffer, sizeof(buffer), "%*s", maxtcpu, "CPU Time"); + xsprintf(buffer, "%*s", maxtcpu, "CPU Time"); rows = lines(); if (rows <= 10) diff --git a/src/core/busname.c b/src/core/busname.c index a949cd6d3f..4b0cad8db4 100644 --- a/src/core/busname.c +++ b/src/core/busname.c @@ -112,29 +112,27 @@ static void busname_done(Unit *u) { n->timer_event_source = sd_event_source_unref(n->timer_event_source); } -static int busname_arm_timer(BusName *n) { +static int busname_arm_timer(BusName *n, usec_t usec) { int r; assert(n); - if (n->timeout_usec <= 0) { - n->timer_event_source = sd_event_source_unref(n->timer_event_source); - return 0; - } - if (n->timer_event_source) { - r = sd_event_source_set_time(n->timer_event_source, now(CLOCK_MONOTONIC) + n->timeout_usec); + r = sd_event_source_set_time(n->timer_event_source, usec); if (r < 0) return r; return sd_event_source_set_enabled(n->timer_event_source, SD_EVENT_ONESHOT); } + if (usec == USEC_INFINITY) + return 0; + r = sd_event_add_time( UNIT(n)->manager->event, &n->timer_event_source, CLOCK_MONOTONIC, - now(CLOCK_MONOTONIC) + n->timeout_usec, 0, + usec, 0, busname_dispatch_timer, n); if (r < 0) return r; @@ -372,7 +370,7 @@ static int busname_coldplug(Unit *u) { if (r < 0) return r; - r = busname_arm_timer(n); + r = busname_arm_timer(n, usec_add(u->state_change_timestamp.monotonic, n->timeout_usec)); if (r < 0) return r; } @@ -397,7 +395,7 @@ static int busname_make_starter(BusName *n, pid_t *_pid) { pid_t pid; int r; - r = busname_arm_timer(n); + r = busname_arm_timer(n, usec_add(now(CLOCK_MONOTONIC), n->timeout_usec)); if (r < 0) goto fail; @@ -475,7 +473,7 @@ static void busname_enter_signal(BusName *n, BusNameState state, BusNameResult f } if (r > 0) { - r = busname_arm_timer(n); + r = busname_arm_timer(n, usec_add(now(CLOCK_MONOTONIC), n->timeout_usec)); if (r < 0) { log_unit_warning_errno(UNIT(n), r, "Failed to arm timer: %m"); goto fail; @@ -974,17 +972,21 @@ static int busname_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { return unit_kill_common(u, who, signo, -1, BUSNAME(u)->control_pid, error); } -static int busname_get_timeout(Unit *u, uint64_t *timeout) { +static int busname_get_timeout(Unit *u, usec_t *timeout) { BusName *n = BUSNAME(u); + usec_t t; int r; if (!n->timer_event_source) return 0; - r = sd_event_source_get_time(n->timer_event_source, timeout); + r = sd_event_source_get_time(n->timer_event_source, &t); if (r < 0) return r; + if (t == USEC_INFINITY) + return 0; + *timeout = t; return 1; } diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 1f736b2686..2de28f43e1 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -141,7 +141,7 @@ static int property_get_nice( else { errno = 0; n = getpriority(PRIO_PROCESS, 0); - if (errno != 0) + if (errno > 0) n = 0; } @@ -293,9 +293,25 @@ static int property_get_capability_bounding_set( assert(reply); assert(c); - /* We store this negated internally, to match the kernel, but - * we expose it normalized. */ - return sd_bus_message_append(reply, "t", ~c->capability_bounding_set_drop); + return sd_bus_message_append(reply, "t", c->capability_bounding_set); +} + +static int property_get_ambient_capabilities( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "t", c->capability_ambient_set); } static int property_get_capabilities( @@ -689,6 +705,7 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("Capabilities", "s", property_get_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SecureBits", "i", bus_property_get_int, offsetof(ExecContext, secure_bits), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("CapabilityBoundingSet", "t", property_get_capability_bounding_set, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("AmbientCapabilities", "t", property_get_ambient_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("User", "s", NULL, offsetof(ExecContext, user), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST), @@ -818,7 +835,8 @@ int bus_exec_context_set_transient_property( UnitSetPropertiesMode mode, sd_bus_error *error) { - int r; + const char *soft = NULL; + int r, ri; assert(u); assert(c); @@ -1365,7 +1383,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) { @@ -1475,7 +1493,23 @@ int bus_exec_context_set_transient_property( return 1; - } else if (rlimit_from_string(name) >= 0) { + } + + ri = rlimit_from_string(name); + if (ri < 0) { + soft = endswith(name, "Soft"); + if (soft) { + const char *n; + + n = strndupa(name, soft - name); + ri = rlimit_from_string(n); + if (ri >= 0) + name = n; + + } + } + + if (ri >= 0) { uint64_t rl; rlim_t x; @@ -1493,22 +1527,36 @@ int bus_exec_context_set_transient_property( } if (mode != UNIT_CHECK) { - int z; - - z = rlimit_from_string(name); + _cleanup_free_ char *f = NULL; + struct rlimit nl; + + if (c->rlimit[ri]) { + nl = *c->rlimit[ri]; + + if (soft) + nl.rlim_cur = x; + else + nl.rlim_max = x; + } else + /* When the resource limit is not initialized yet, then assign the value to both fields */ + nl = (struct rlimit) { + .rlim_cur = x, + .rlim_max = x, + }; + + r = rlimit_format(&nl, &f); + if (r < 0) + return r; - if (!c->rlimit[z]) { - c->rlimit[z] = new(struct rlimit, 1); - if (!c->rlimit[z]) + if (c->rlimit[ri]) + *c->rlimit[ri] = nl; + else { + c->rlimit[ri] = newdup(struct rlimit, &nl, 1); + if (!c->rlimit[ri]) return -ENOMEM; } - c->rlimit[z]->rlim_cur = c->rlimit[z]->rlim_max = x; - - if (x == RLIM_INFINITY) - unit_write_drop_in_private_format(u, mode, name, "%s=infinity\n", name); - else - unit_write_drop_in_private_format(u, mode, name, "%s=%" PRIu64 "\n", name, rl); + unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, f); } return 1; diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 61bd0f8d5f..c5c672a0a2 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -229,7 +229,10 @@ static int property_set_log_level( if (r < 0) return r; - return log_set_max_level_from_string(t); + r = log_set_max_level_from_string(t); + if (r == 0) + log_info("Setting log level to %s.", t); + return r; } static int property_get_n_names( @@ -1604,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: @@ -1840,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/dbus-service.c b/src/core/dbus-service.c index 24f611a593..16f50238a1 100644 --- a/src/core/dbus-service.c +++ b/src/core/dbus-service.c @@ -49,6 +49,7 @@ const sd_bus_vtable bus_service_vtable[] = { SD_BUS_PROPERTY("RestartUSec", "t", bus_property_get_usec, offsetof(Service, restart_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("TimeoutStartUSec", "t", bus_property_get_usec, offsetof(Service, timeout_start_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Service, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RuntimeMaxUSec", "t", bus_property_get_usec, offsetof(Service, runtime_max_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("WatchdogUSec", "t", bus_property_get_usec, offsetof(Service, watchdog_usec), SD_BUS_VTABLE_PROPERTY_CONST), BUS_PROPERTY_DUAL_TIMESTAMP("WatchdogTimestamp", offsetof(Service, watchdog_timestamp), 0), SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Service, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST), @@ -125,6 +126,19 @@ static int bus_service_set_transient_property( } return 1; + } else if (streq(name, "RuntimeMaxUSec")) { + usec_t u; + + r = sd_bus_message_read(message, "t", &u); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + s->runtime_max_usec = u; + unit_write_drop_in_private_format(UNIT(s), mode, name, "RuntimeMaxSec=" USEC_FMT "us\n", u); + } + + return 1; } else if (STR_IN_SET(name, "StandardInputFileDescriptor", @@ -153,6 +167,8 @@ static int bus_service_set_transient_property( asynchronous_close(s->stderr_fd); s->stderr_fd = copy; } + + s->exec_context.stdio_as_fds = true; } return 1; diff --git a/src/core/dbus-timer.c b/src/core/dbus-timer.c index ec301df6d7..321ed5da37 100644 --- a/src/core/dbus-timer.c +++ b/src/core/dbus-timer.c @@ -267,19 +267,19 @@ static int bus_timer_set_transient_property( return 1; - } else if (streq(name, "AccuracySec")) { - + } else if (STR_IN_SET(name, "AccuracyUSec", "AccuracySec")) { usec_t u = 0; + if (streq(name, "AccuracySec")) + log_notice("Client is using obsolete AccuracySec= transient property, please use AccuracyUSec= instead."); + r = sd_bus_message_read(message, "t", &u); if (r < 0) return r; if (mode != UNIT_CHECK) { - char time[FORMAT_TIMESPAN_MAX]; - t->accuracy_usec = u; - unit_write_drop_in_private_format(UNIT(t), mode, name, "%s=%s\n", name, format_timespan(time, sizeof(time), u, USEC_PER_MSEC)); + unit_write_drop_in_private_format(UNIT(t), mode, name, "AccuracySec=" USEC_FMT "us\n", u); } return 1; @@ -292,10 +292,8 @@ static int bus_timer_set_transient_property( return r; if (mode != UNIT_CHECK) { - char time[FORMAT_TIMESPAN_MAX]; - t->random_usec = u; - unit_write_drop_in_private_format(UNIT(t), mode, name, "RandomizedDelaySec=%s\n", format_timespan(time, sizeof(time), u, USEC_PER_MSEC)); + unit_write_drop_in_private_format(UNIT(t), mode, name, "RandomizedDelaySec=" USEC_FMT "us\n", u); } return 1; diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index e4d2c08972..d7929e5566 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -458,7 +458,10 @@ int bus_unit_method_start_generic( assert(u); assert(job_type >= 0 && job_type < _JOB_TYPE_MAX); - r = mac_selinux_unit_access_check(u, message, job_type == JOB_STOP ? "stop" : "start", error); + r = mac_selinux_unit_access_check( + u, message, + job_type_to_access_method(job_type), + error); if (r < 0) return r; @@ -671,6 +674,7 @@ const sd_bus_vtable bus_unit_vtable[] = { SD_BUS_PROPERTY("DropInPaths", "as", NULL, offsetof(Unit, dropin_paths), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("UnitFileState", "s", property_get_unit_file_state, 0, 0), SD_BUS_PROPERTY("UnitFilePreset", "s", property_get_unit_file_preset, 0, 0), + BUS_PROPERTY_DUAL_TIMESTAMP("StateChangeTimestamp", offsetof(Unit, state_change_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), BUS_PROPERTY_DUAL_TIMESTAMP("InactiveExitTimestamp", offsetof(Unit, inactive_exit_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), BUS_PROPERTY_DUAL_TIMESTAMP("ActiveEnterTimestamp", offsetof(Unit, active_enter_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), BUS_PROPERTY_DUAL_TIMESTAMP("ActiveExitTimestamp", offsetof(Unit, active_exit_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), @@ -983,19 +987,20 @@ int bus_unit_queue_job( assert(type >= 0 && type < _JOB_TYPE_MAX); assert(mode >= 0 && mode < _JOB_MODE_MAX); + r = mac_selinux_unit_access_check( + u, message, + job_type_to_access_method(type), + error); + if (r < 0) + return r; + if (reload_if_possible && unit_can_reload(u)) { if (type == JOB_RESTART) type = JOB_RELOAD_OR_START; else if (type == JOB_TRY_RESTART) - type = JOB_RELOAD; + type = JOB_TRY_RELOAD; } - r = mac_selinux_unit_access_check( - u, message, - (type == JOB_START || type == JOB_RESTART || type == JOB_TRY_RESTART) ? "start" : - type == JOB_STOP ? "stop" : "reload", error); - if (r < 0) - return r; if (type == JOB_STOP && (u->load_state == UNIT_NOT_FOUND || u->load_state == UNIT_ERROR) && diff --git a/src/core/dbus.c b/src/core/dbus.c index e7ee216f0e..1d89b9e250 100644 --- a/src/core/dbus.c +++ b/src/core/dbus.c @@ -734,9 +734,11 @@ static int bus_on_connection(sd_event_source *s, int fd, uint32_t revents, void return 0; } -static int bus_list_names(Manager *m, sd_bus *bus) { +int manager_sync_bus_names(Manager *m, sd_bus *bus) { _cleanup_strv_free_ char **names = NULL; - char **i; + const char *name; + Iterator i; + Unit *u; int r; assert(m); @@ -746,15 +748,55 @@ static int bus_list_names(Manager *m, sd_bus *bus) { if (r < 0) return log_error_errno(r, "Failed to get initial list of names: %m"); - /* This is a bit hacky, we say the owner of the name is the - * name itself, because we don't want the extra traffic to - * figure out the real owner. */ - STRV_FOREACH(i, names) { - Unit *u; + /* We have to synchronize the current bus names with the + * list of active services. To do this, walk the list of + * all units with bus names. */ + HASHMAP_FOREACH_KEY(u, name, m->watch_bus, i) { + Service *s = SERVICE(u); + + assert(s); - u = hashmap_get(m->watch_bus, *i); - if (u) - UNIT_VTABLE(u)->bus_name_owner_change(u, *i, NULL, *i); + if (!streq_ptr(s->bus_name, name)) { + log_unit_warning(u, "Bus name has changed from %s → %s, ignoring.", s->bus_name, name); + continue; + } + + /* Check if a service's bus name is in the list of currently + * active names */ + if (strv_contains(names, name)) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + const char *unique; + + /* If it is, determine its current owner */ + r = sd_bus_get_name_creds(bus, name, SD_BUS_CREDS_UNIQUE_NAME, &creds); + if (r < 0) { + log_error_errno(r, "Failed to get bus name owner %s: %m", name); + continue; + } + + r = sd_bus_creds_get_unique_name(creds, &unique); + if (r < 0) { + log_error_errno(r, "Failed to get unique name for %s: %m", name); + continue; + } + + /* Now, let's compare that to the previous bus owner, and + * if it's still the same, all is fine, so just don't + * bother the service. Otherwise, the name has apparently + * changed, so synthesize a name owner changed signal. */ + + if (!streq_ptr(unique, s->bus_name_owner)) + UNIT_VTABLE(u)->bus_name_owner_change(u, name, s->bus_name_owner, unique); + } else { + /* So, the name we're watching is not on the bus. + * This either means it simply hasn't appeared yet, + * or it was lost during the daemon reload. + * Check if the service has a stored name owner, + * and synthesize a name loss signal in this case. */ + + if (s->bus_name_owner) + UNIT_VTABLE(u)->bus_name_owner_change(u, name, s->bus_name_owner, NULL); + } } return 0; @@ -808,7 +850,9 @@ static int bus_setup_api(Manager *m, sd_bus *bus) { if (r < 0) return log_error_errno(r, "Failed to register name: %m"); - bus_list_names(m, bus); + r = manager_sync_bus_names(m, bus); + if (r < 0) + return r; log_debug("Successfully connected to API bus."); return 0; diff --git a/src/core/dbus.h b/src/core/dbus.h index 4f06ad11c4..ff761668f3 100644 --- a/src/core/dbus.h +++ b/src/core/dbus.h @@ -34,6 +34,8 @@ void bus_track_serialize(sd_bus_track *t, FILE *f); int bus_track_deserialize_item(char ***l, const char *line); int bus_track_coldplug(Manager *m, sd_bus_track **t, char ***l); +int manager_sync_bus_names(Manager *m, sd_bus *bus); + int bus_foreach_bus(Manager *m, sd_bus_track *subscribed2, int (*send_message)(sd_bus *bus, void *userdata), void *userdata); int bus_verify_manage_units_async(Manager *m, sd_bus_message *call, sd_bus_error *error); diff --git a/src/core/device.c b/src/core/device.c index bcd4d1146b..807547c87f 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; @@ -315,12 +315,19 @@ static int device_setup_unit(Manager *m, struct udev_device *dev, const char *pa u = manager_get_unit(m, e); - if (u && - sysfs && - DEVICE(u)->sysfs && - !path_equal(DEVICE(u)->sysfs, sysfs)) { - log_unit_debug(u, "Device %s appeared twice with different sysfs paths %s and %s", e, DEVICE(u)->sysfs, sysfs); - return -EEXIST; + /* The device unit can still be present even if the device was + * unplugged: a mount unit can reference it hence preventing + * the GC to have garbaged it. That's desired since the device + * unit may have a dependency on the mount unit which was + * added during the loading of the later. */ + if (u && DEVICE(u)->state == DEVICE_PLUGGED) { + /* This unit is in plugged state: we're sure it's + * attached to a device. */ + if (!path_equal(DEVICE(u)->sysfs, sysfs)) { + log_unit_error(u, "Dev %s appeared twice with different sysfs paths %s and %s", + e, DEVICE(u)->sysfs, sysfs); + return -EEXIST; + } } if (!u) { diff --git a/src/core/execute.c b/src/core/execute.c index 9b76861919..80db62131c 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -183,26 +183,41 @@ static int flags_fds(const int fds[], unsigned n_fds, bool nonblock) { return 0; } -_pure_ static const char *tty_path(const ExecContext *context) { +static const char *exec_context_tty_path(const ExecContext *context) { assert(context); + if (context->stdio_as_fds) + return NULL; + if (context->tty_path) return context->tty_path; return "/dev/console"; } -static void exec_context_tty_reset(const ExecContext *context) { +static void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) { + const char *path; + assert(context); - if (context->tty_vhangup) - terminal_vhangup(tty_path(context)); + path = exec_context_tty_path(context); - if (context->tty_reset) - reset_terminal(tty_path(context)); + if (context->tty_vhangup) { + if (p && p->stdin_fd >= 0) + (void) terminal_vhangup_fd(p->stdin_fd); + else if (path) + (void) terminal_vhangup(path); + } + + if (context->tty_reset) { + if (p && p->stdin_fd >= 0) + (void) reset_terminal_fd(p->stdin_fd, true); + else if (path) + (void) reset_terminal(path); + } - if (context->tty_vt_disallocate && context->tty_path) - vt_disallocate(context->tty_path); + if (context->tty_vt_disallocate && path) + (void) vt_disallocate(path); } static bool is_terminal_output(ExecOutput o) { @@ -400,7 +415,7 @@ static int setup_input( case EXEC_INPUT_TTY_FAIL: { int fd, r; - fd = acquire_terminal(tty_path(context), + fd = acquire_terminal(exec_context_tty_path(context), i == EXEC_INPUT_TTY_FAIL, i == EXEC_INPUT_TTY_FORCE, false, @@ -485,7 +500,7 @@ static int setup_output( } else if (o == EXEC_OUTPUT_INHERIT) { /* If input got downgraded, inherit the original value */ if (i == EXEC_INPUT_NULL && is_terminal_input(context->std_input)) - return open_terminal_as(tty_path(context), O_WRONLY, fileno); + return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno); /* If the input is connected to anything that's not a /dev/null, inherit that... */ if (i != EXEC_INPUT_NULL) @@ -509,7 +524,7 @@ static int setup_output( return dup2(STDIN_FILENO, fileno) < 0 ? -errno : fileno; /* We don't reset the terminal if this is just about output */ - return open_terminal_as(tty_path(context), O_WRONLY, fileno); + return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno); case EXEC_OUTPUT_SYSLOG: case EXEC_OUTPUT_SYSLOG_AND_CONSOLE: @@ -737,12 +752,7 @@ static int enforce_user(const ExecContext *context, uid_t uid) { /* Sets (but doesn't lookup) the uid and make sure we keep the * capabilities while doing so. */ - if (context->capabilities) { - _cleanup_cap_free_ cap_t d = NULL; - static const cap_value_t bits[] = { - CAP_SETUID, /* Necessary so that we can run setresuid() below */ - CAP_SETPCAP /* Necessary so that we can set PR_SET_SECUREBITS later on */ - }; + if (context->capabilities || context->capability_ambient_set != 0) { /* First step: If we need to keep capabilities but * drop privileges we need to make sure we keep our @@ -758,16 +768,24 @@ static int enforce_user(const ExecContext *context, uid_t uid) { /* Second step: set the capabilities. This will reduce * the capabilities to the minimum we need. */ - d = cap_dup(context->capabilities); - if (!d) - return -errno; + if (context->capabilities) { + _cleanup_cap_free_ cap_t d = NULL; + static const cap_value_t bits[] = { + CAP_SETUID, /* Necessary so that we can run setresuid() below */ + CAP_SETPCAP /* Necessary so that we can set PR_SET_SECUREBITS later on */ + }; - if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 || - cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0) - return -errno; + d = cap_dup(context->capabilities); + if (!d) + return -errno; - if (cap_set_proc(d) < 0) - return -errno; + if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 || + cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0) + return -errno; + + if (cap_set_proc(d) < 0) + return -errno; + } } /* Third step: actually set the uids */ @@ -811,8 +829,7 @@ static int setup_pam( _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; pam_handle_t *handle = NULL; sigset_t old_ss; - int pam_code = PAM_SUCCESS; - int err = 0; + int pam_code = PAM_SUCCESS, r; char **e = NULL; bool close_session = false; pid_t pam_pid = 0, parent_pid; @@ -829,8 +846,8 @@ static int setup_pam( * daemon. We do things this way to ensure that the main PID * of the daemon is the one we initially fork()ed. */ - err = barrier_create(&barrier); - if (err < 0) + r = barrier_create(&barrier); + if (r < 0) goto fail; if (log_get_max_level() < LOG_DEBUG) @@ -872,12 +889,13 @@ static int setup_pam( parent_pid = getpid(); pam_pid = fork(); - if (pam_pid < 0) + if (pam_pid < 0) { + r = -errno; goto fail; + } if (pam_pid == 0) { - int sig; - int r = EXIT_PAM; + int sig, ret = EXIT_PAM; /* The child's job is to reset the PAM session on * termination */ @@ -942,11 +960,11 @@ static int setup_pam( goto child_finish; } - r = 0; + ret = 0; child_finish: pam_end(handle, pam_code | flags); - _exit(r); + _exit(ret); } barrier_set_role(&barrier, BARRIER_PARENT); @@ -975,10 +993,9 @@ static int setup_pam( fail: if (pam_code != PAM_SUCCESS) { log_error("PAM failed: %s", pam_strerror(handle, pam_code)); - err = -EPERM; /* PAM errors do not map to errno */ - } else { - err = log_error_errno(err < 0 ? err : errno, "PAM failed: %m"); - } + r = -EPERM; /* PAM errors do not map to errno */ + } else + log_error_errno(r, "PAM failed: %m"); if (handle) { if (close_session) @@ -988,15 +1005,9 @@ fail: } strv_free(e); - closelog(); - if (pam_pid > 1) { - kill(pam_pid, SIGTERM); - kill(pam_pid, SIGCONT); - } - - return err; + return r; } #endif @@ -1236,9 +1247,8 @@ static void do_idle_pipe_dance(int idle_pipe[4]) { static int build_environment( const ExecContext *c, + const ExecParameters *p, unsigned n_fds, - char ** fd_names, - usec_t watchdog_usec, const char *home, const char *username, const char *shell, @@ -1266,7 +1276,7 @@ static int build_environment( return -ENOMEM; our_env[n_env++] = x; - joined = strv_join(fd_names, ":"); + joined = strv_join(p->fd_names, ":"); if (!joined) return -ENOMEM; @@ -1276,12 +1286,12 @@ static int build_environment( our_env[n_env++] = x; } - if (watchdog_usec > 0) { + if (p->watchdog_usec > 0) { if (asprintf(&x, "WATCHDOG_PID="PID_FMT, getpid()) < 0) return -ENOMEM; our_env[n_env++] = x; - if (asprintf(&x, "WATCHDOG_USEC="USEC_FMT, watchdog_usec) < 0) + if (asprintf(&x, "WATCHDOG_USEC="USEC_FMT, p->watchdog_usec) < 0) return -ENOMEM; our_env[n_env++] = x; } @@ -1317,7 +1327,7 @@ static int build_environment( c->std_error == EXEC_OUTPUT_TTY || c->tty_path) { - x = strdup(default_term_for_tty(tty_path(c))); + x = strdup(default_term_for_tty(exec_context_tty_path(c))); if (!x) return -ENOMEM; our_env[n_env++] = x; @@ -1494,7 +1504,7 @@ static int exec_child( return -errno; } - exec_context_tty_reset(context); + exec_context_tty_reset(context, params); if (params->confirm_spawn) { char response; @@ -1856,6 +1866,8 @@ static int exec_child( if (params->apply_permissions) { + int secure_bits = context->secure_bits; + for (i = 0; i < _RLIMIT_MAX; i++) { if (!context->rlimit[i]) continue; @@ -1866,12 +1878,37 @@ static int exec_child( } } - if (context->capability_bounding_set_drop) { - r = capability_bounding_set_drop(context->capability_bounding_set_drop, false); + if (!cap_test_all(context->capability_bounding_set)) { + r = capability_bounding_set_drop(context->capability_bounding_set, false); + if (r < 0) { + *exit_status = EXIT_CAPABILITIES; + return r; + } + } + + /* This is done before enforce_user, but ambient set + * does not survive over setresuid() if keep_caps is not set. */ + if (context->capability_ambient_set != 0) { + r = capability_ambient_set_apply(context->capability_ambient_set, true); if (r < 0) { *exit_status = EXIT_CAPABILITIES; return r; } + + if (context->capabilities) { + + /* The capabilities in ambient set need to be also in the inherited + * set. If they aren't, trying to get them will fail. Add the ambient + * set inherited capabilities to the capability set in the context. + * This is needed because if capabilities are set (using "Capabilities=" + * keyword), they will override whatever we set now. */ + + r = capability_update_inherited_set(context->capabilities, context->capability_ambient_set); + if (r < 0) { + *exit_status = EXIT_CAPABILITIES; + return r; + } + } } if (context->user) { @@ -1880,14 +1917,32 @@ static int exec_child( *exit_status = EXIT_USER; return r; } + if (context->capability_ambient_set != 0) { + + /* Fix the ambient capabilities after user change. */ + r = capability_ambient_set_apply(context->capability_ambient_set, false); + if (r < 0) { + *exit_status = EXIT_CAPABILITIES; + return r; + } + + /* If we were asked to change user and ambient capabilities + * were requested, we had to add keep-caps to the securebits + * so that we would maintain the inherited capability set + * through the setresuid(). Make sure that the bit is added + * also to the context secure_bits so that we don't try to + * drop the bit away next. */ + + secure_bits |= 1<<SECURE_KEEP_CAPS; + } } /* PR_GET_SECUREBITS is not privileged, while * PR_SET_SECUREBITS is. So to suppress * potential EPERMs we'll try not to call * PR_SET_SECUREBITS unless necessary. */ - if (prctl(PR_GET_SECUREBITS) != context->secure_bits) - if (prctl(PR_SET_SECUREBITS, context->secure_bits) < 0) { + if (prctl(PR_GET_SECUREBITS) != secure_bits) + if (prctl(PR_SET_SECUREBITS, secure_bits) < 0) { *exit_status = EXIT_SECUREBITS; return -errno; } @@ -1950,7 +2005,7 @@ static int exec_child( #endif } - r = build_environment(context, n_fds, params->fd_names, params->watchdog_usec, home, username, shell, &our_env); + r = build_environment(context, params, n_fds, home, username, shell, &our_env); if (r < 0) { *exit_status = EXIT_MEMORY; return r; @@ -2114,6 +2169,7 @@ void exec_context_init(ExecContext *c) { c->timer_slack_nsec = NSEC_INFINITY; c->personality = PERSONALITY_INVALID; c->runtime_directory_mode = 0755; + c->capability_bounding_set = CAP_ALL; } void exec_context_done(ExecContext *c) { @@ -2270,7 +2326,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) { @@ -2324,6 +2380,9 @@ static bool tty_may_match_dev_console(const char *tty) { _cleanup_free_ char *active = NULL; char *console; + if (!tty) + return true; + if (startswith(tty, "/dev/")) tty += 5; @@ -2341,11 +2400,14 @@ static bool tty_may_match_dev_console(const char *tty) { } bool exec_context_may_touch_console(ExecContext *ec) { - return (ec->tty_reset || ec->tty_vhangup || ec->tty_vt_disallocate || + + return (ec->tty_reset || + ec->tty_vhangup || + ec->tty_vt_disallocate || is_terminal_input(ec->std_input) || is_terminal_output(ec->std_output) || is_terminal_output(ec->std_error)) && - tty_may_match_dev_console(tty_path(ec)); + tty_may_match_dev_console(exec_context_tty_path(ec)); } static void strv_fprintf(FILE *f, char **l) { @@ -2517,12 +2579,23 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { (c->secure_bits & 1<<SECURE_NOROOT) ? " noroot" : "", (c->secure_bits & 1<<SECURE_NOROOT_LOCKED) ? "noroot-locked" : ""); - if (c->capability_bounding_set_drop) { + if (c->capability_bounding_set != CAP_ALL) { unsigned long l; fprintf(f, "%sCapabilityBoundingSet:", prefix); for (l = 0; l <= cap_last_cap(); l++) - if (!(c->capability_bounding_set_drop & ((uint64_t) 1ULL << (uint64_t) l))) + if (c->capability_bounding_set & (UINT64_C(1) << l)) + fprintf(f, " %s", strna(capability_to_name(l))); + + fputs("\n", f); + } + + if (c->capability_ambient_set != 0) { + unsigned long l; + fprintf(f, "%sAmbientCapabilities:", prefix); + + for (l = 0; l <= cap_last_cap(); l++) + if (c->capability_ambient_set & (UINT64_C(1) << l)) fprintf(f, " %s", strna(capability_to_name(l))); fputs("\n", f); @@ -2623,7 +2696,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))); @@ -2673,7 +2746,7 @@ void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, if (context->utmp_id) utmp_put_dead_process(context->utmp_id, pid, code, status); - exec_context_tty_reset(context); + exec_context_tty_reset(context, NULL); } } diff --git a/src/core/execute.h b/src/core/execute.h index be5be9f531..e4b93b603d 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -122,6 +122,8 @@ struct ExecContext { nsec_t timer_slack_nsec; + bool stdio_as_fds; + char *tty_path; bool tty_reset; @@ -155,7 +157,9 @@ struct ExecContext { char **read_write_dirs, **read_only_dirs, **inaccessible_dirs; unsigned long mount_flags; - uint64_t capability_bounding_set_drop; + uint64_t capability_bounding_set; + + uint64_t capability_ambient_set; cap_t capabilities; int secure_bits; diff --git a/src/core/job.c b/src/core/job.c index 9654590635..b1737c8110 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -35,6 +35,7 @@ #include "parse-util.h" #include "set.h" #include "special.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -404,6 +405,13 @@ JobType job_type_collapse(JobType t, Unit *u) { return JOB_RESTART; + case JOB_TRY_RELOAD: + s = unit_active_state(u); + if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s)) + return JOB_NOP; + + return JOB_RELOAD; + case JOB_RELOAD_OR_START: s = unit_active_state(u); if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s)) @@ -754,7 +762,7 @@ static void job_log_status_message(Unit *u, JobType t, JobResult result) { return; DISABLE_WARNING_FORMAT_NONLITERAL; - snprintf(buf, sizeof(buf), format, unit_description(u)); + xsprintf(buf, format, unit_description(u)); REENABLE_WARNING; switch (t) { @@ -924,14 +932,14 @@ int job_start_timer(Job *j) { j->begin_usec = now(CLOCK_MONOTONIC); - if (j->unit->job_timeout <= 0) + if (j->unit->job_timeout == USEC_INFINITY) return 0; r = sd_event_add_time( j->manager->event, &j->timer_event_source, CLOCK_MONOTONIC, - j->begin_usec + j->unit->job_timeout, 0, + usec_add(j->begin_usec, j->unit->job_timeout), 0, job_dispatch_timer, j); if (r < 0) return r; @@ -1109,17 +1117,16 @@ int job_coldplug(Job *j) { if (j->state == JOB_WAITING) job_add_to_run_queue(j); - if (j->begin_usec == 0 || j->unit->job_timeout == 0) + if (j->begin_usec == 0 || j->unit->job_timeout == USEC_INFINITY) return 0; - if (j->timer_event_source) - j->timer_event_source = sd_event_source_unref(j->timer_event_source); + j->timer_event_source = sd_event_source_unref(j->timer_event_source); r = sd_event_add_time( j->manager->event, &j->timer_event_source, CLOCK_MONOTONIC, - j->begin_usec + j->unit->job_timeout, 0, + usec_add(j->begin_usec, j->unit->job_timeout), 0, job_dispatch_timer, j); if (r < 0) log_debug_errno(r, "Failed to restart timeout for job: %m"); @@ -1158,10 +1165,10 @@ void job_shutdown_magic(Job *j) { asynchronous_sync(); } -int job_get_timeout(Job *j, uint64_t *timeout) { +int job_get_timeout(Job *j, usec_t *timeout) { + usec_t x = USEC_INFINITY, y = USEC_INFINITY; Unit *u = j->unit; - uint64_t x = -1, y = -1; - int r = 0, q = 0; + int r; assert(u); @@ -1169,20 +1176,18 @@ int job_get_timeout(Job *j, uint64_t *timeout) { r = sd_event_source_get_time(j->timer_event_source, &x); if (r < 0) return r; - r = 1; } if (UNIT_VTABLE(u)->get_timeout) { - q = UNIT_VTABLE(u)->get_timeout(u, &y); - if (q < 0) - return q; + r = UNIT_VTABLE(u)->get_timeout(u, &y); + if (r < 0) + return r; } - if (r == 0 && q == 0) + if (x == USEC_INFINITY && y == USEC_INFINITY) return 0; *timeout = MIN(x, y); - return 1; } @@ -1201,6 +1206,7 @@ static const char* const job_type_table[_JOB_TYPE_MAX] = { [JOB_RELOAD_OR_START] = "reload-or-start", [JOB_RESTART] = "restart", [JOB_TRY_RESTART] = "try-restart", + [JOB_TRY_RELOAD] = "try-reload", [JOB_NOP] = "nop", }; @@ -1231,3 +1237,15 @@ static const char* const job_result_table[_JOB_RESULT_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP(job_result, JobResult); + +const char* job_type_to_access_method(JobType t) { + assert(t >= 0); + assert(t < _JOB_TYPE_MAX); + + if (IN_SET(t, JOB_START, JOB_RESTART, JOB_TRY_RESTART)) + return "start"; + else if (t == JOB_STOP) + return "stop"; + else + return "reload"; +} diff --git a/src/core/job.h b/src/core/job.h index 118b24e5b7..4c19ad0c6a 100644 --- a/src/core/job.h +++ b/src/core/job.h @@ -66,6 +66,9 @@ enum JobType { * Thus we never need to merge it with anything. */ JOB_TRY_RESTART = _JOB_TYPE_MAX_IN_TRANSACTION, /* if running, stop and then start */ + /* Similar to JOB_TRY_RESTART but collapses to JOB_RELOAD or JOB_NOP */ + JOB_TRY_RELOAD, + /* JOB_RELOAD_OR_START won't enter into a transaction and cannot result * from transaction merging (there's no way for JOB_RELOAD and * JOB_START to meet in one transaction). It can result from a merge @@ -224,6 +227,8 @@ char *job_dbus_path(Job *j); void job_shutdown_magic(Job *j); +int job_get_timeout(Job *j, usec_t *timeout) _pure_; + const char* job_type_to_string(JobType t) _const_; JobType job_type_from_string(const char *s) _pure_; @@ -236,4 +241,4 @@ JobMode job_mode_from_string(const char *s) _pure_; const char* job_result_to_string(JobResult t) _const_; JobResult job_result_from_string(const char *s) _pure_; -int job_get_timeout(Job *j, uint64_t *timeout) _pure_; +const char* job_type_to_access_method(JobType t); diff --git a/src/core/killall.c b/src/core/killall.c index 77f145b4d1..d0c7c89670 100644 --- a/src/core/killall.c +++ b/src/core/killall.c @@ -37,7 +37,7 @@ #define TIMEOUT_USEC (10 * USEC_PER_SEC) -static bool ignore_proc(pid_t pid) { +static bool ignore_proc(pid_t pid, bool warn_rootfs) { _cleanup_fclose_ FILE *f = NULL; char c; const char *p; @@ -72,7 +72,22 @@ static bool ignore_proc(pid_t pid) { * spree. * * http://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons */ - if (count == 1 && c == '@') + if (c == '@' && warn_rootfs) { + _cleanup_free_ char *comm = NULL; + + r = pid_from_same_root_fs(pid); + if (r < 0) + return true; + + get_process_comm(pid, &comm); + + if (r) + log_notice("Process " PID_FMT " (%s) has been been marked to be excluded from killing. It is " + "running from the root file system, and thus likely to block re-mounting of the " + "root file system to read-only. Please consider moving it into an initrd file " + "system instead.", pid, strna(comm)); + return true; + } else if (c == '@') return true; return false; @@ -171,7 +186,7 @@ static int killall(int sig, Set *pids, bool send_sighup) { if (parse_pid(d->d_name, &pid) < 0) continue; - if (ignore_proc(pid)) + if (ignore_proc(pid, sig == SIGKILL && !in_initrd())) continue; if (sig == SIGKILL) { diff --git a/src/core/load-dropin.c b/src/core/load-dropin.c index 3fa66f91aa..569632e13b 100644 --- a/src/core/load-dropin.c +++ b/src/core/load-dropin.c @@ -65,6 +65,7 @@ int unit_load_dropin(Unit *u) { } } + u->dropin_paths = strv_free(u->dropin_paths); r = unit_find_dropin_paths(u, &u->dropin_paths); if (r <= 0) return 0; diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 0408b9a829..2507db1932 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -47,7 +47,8 @@ $1.SyslogLevel, config_parse_log_level, 0, $1.SyslogLevelPrefix, config_parse_bool, 0, offsetof($1, exec_context.syslog_level_prefix) $1.Capabilities, config_parse_exec_capabilities, 0, offsetof($1, exec_context) $1.SecureBits, config_parse_exec_secure_bits, 0, offsetof($1, exec_context) -$1.CapabilityBoundingSet, config_parse_bounding_set, 0, offsetof($1, exec_context.capability_bounding_set_drop) +$1.CapabilityBoundingSet, config_parse_capability_set, 0, offsetof($1, exec_context.capability_bounding_set) +$1.AmbientCapabilities, config_parse_capability_set, 0, offsetof($1, exec_context.capability_ambient_set) $1.TimerSlackNSec, config_parse_nsec, 0, offsetof($1, exec_context.timer_slack_nsec) $1.NoNewPrivileges, config_parse_no_new_privileges, 0, offsetof($1, exec_context) m4_ifdef(`HAVE_SECCOMP', @@ -59,22 +60,22 @@ $1.RestrictAddressFamilies, config_parse_address_families, 0, $1.SystemCallArchitectures, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 $1.SystemCallErrorNumber, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 $1.RestrictAddressFamilies, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') -$1.LimitCPU, config_parse_sec_limit, RLIMIT_CPU, offsetof($1, exec_context.rlimit) -$1.LimitFSIZE, config_parse_bytes_limit, RLIMIT_FSIZE, offsetof($1, exec_context.rlimit) -$1.LimitDATA, config_parse_bytes_limit, RLIMIT_DATA, offsetof($1, exec_context.rlimit) -$1.LimitSTACK, config_parse_bytes_limit, RLIMIT_STACK, offsetof($1, exec_context.rlimit) -$1.LimitCORE, config_parse_bytes_limit, RLIMIT_CORE, offsetof($1, exec_context.rlimit) -$1.LimitRSS, config_parse_bytes_limit, RLIMIT_RSS, offsetof($1, exec_context.rlimit) +$1.LimitCPU, config_parse_limit, RLIMIT_CPU, offsetof($1, exec_context.rlimit) +$1.LimitFSIZE, config_parse_limit, RLIMIT_FSIZE, offsetof($1, exec_context.rlimit) +$1.LimitDATA, config_parse_limit, RLIMIT_DATA, offsetof($1, exec_context.rlimit) +$1.LimitSTACK, config_parse_limit, RLIMIT_STACK, offsetof($1, exec_context.rlimit) +$1.LimitCORE, config_parse_limit, RLIMIT_CORE, offsetof($1, exec_context.rlimit) +$1.LimitRSS, config_parse_limit, RLIMIT_RSS, offsetof($1, exec_context.rlimit) $1.LimitNOFILE, config_parse_limit, RLIMIT_NOFILE, offsetof($1, exec_context.rlimit) -$1.LimitAS, config_parse_bytes_limit, RLIMIT_AS, offsetof($1, exec_context.rlimit) +$1.LimitAS, config_parse_limit, RLIMIT_AS, offsetof($1, exec_context.rlimit) $1.LimitNPROC, config_parse_limit, RLIMIT_NPROC, offsetof($1, exec_context.rlimit) -$1.LimitMEMLOCK, config_parse_bytes_limit, RLIMIT_MEMLOCK, offsetof($1, exec_context.rlimit) +$1.LimitMEMLOCK, config_parse_limit, RLIMIT_MEMLOCK, offsetof($1, exec_context.rlimit) $1.LimitLOCKS, config_parse_limit, RLIMIT_LOCKS, offsetof($1, exec_context.rlimit) $1.LimitSIGPENDING, config_parse_limit, RLIMIT_SIGPENDING, offsetof($1, exec_context.rlimit) -$1.LimitMSGQUEUE, config_parse_bytes_limit, RLIMIT_MSGQUEUE, offsetof($1, exec_context.rlimit) +$1.LimitMSGQUEUE, config_parse_limit, RLIMIT_MSGQUEUE, offsetof($1, exec_context.rlimit) $1.LimitNICE, config_parse_limit, RLIMIT_NICE, offsetof($1, exec_context.rlimit) $1.LimitRTPRIO, config_parse_limit, RLIMIT_RTPRIO, offsetof($1, exec_context.rlimit) -$1.LimitRTTIME, config_parse_usec_limit, RLIMIT_RTTIME, offsetof($1, exec_context.rlimit) +$1.LimitRTTIME, config_parse_limit, RLIMIT_RTTIME, offsetof($1, exec_context.rlimit) $1.ReadWriteDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_write_dirs) $1.ReadOnlyDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_only_dirs) $1.InaccessibleDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.inaccessible_dirs) @@ -213,6 +214,7 @@ Service.RestartSec, config_parse_sec, 0, Service.TimeoutSec, config_parse_service_timeout, 0, offsetof(Service, timeout_start_usec) Service.TimeoutStartSec, config_parse_service_timeout, 0, offsetof(Service, timeout_start_usec) Service.TimeoutStopSec, config_parse_service_timeout, 0, offsetof(Service, timeout_stop_usec) +Service.RuntimeMaxSec, config_parse_sec, 0, offsetof(Service, runtime_max_usec) Service.WatchdogSec, config_parse_sec, 0, offsetof(Service, watchdog_usec) Service.StartLimitInterval, config_parse_sec, 0, offsetof(Service, start_limit.interval) Service.StartLimitBurst, config_parse_unsigned, 0, offsetof(Service, start_limit.burst) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index cb553e1252..3b37cc4cda 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -38,6 +38,7 @@ #include "bus-internal.h" #include "bus-util.h" #include "cap-list.h" +#include "capability-util.h" #include "cgroup.h" #include "conf-parser.h" #include "cpu-set-util.h" @@ -53,6 +54,7 @@ #include "parse-util.h" #include "path-util.h" #include "process-util.h" +#include "rlimit-util.h" #ifdef HAVE_SECCOMP #include "seccomp-util.h" #endif @@ -574,7 +576,9 @@ int config_parse_exec( void *data, void *userdata) { + _cleanup_free_ char *cmd = NULL; ExecCommand **e = data; + Unit *u = userdata; const char *p; bool semicolon; int r; @@ -583,6 +587,7 @@ int config_parse_exec( assert(lvalue); assert(rvalue); assert(e); + assert(u); e += ltype; rvalue += strspn(rvalue, WHITESPACE); @@ -593,7 +598,13 @@ int config_parse_exec( return 0; } - p = rvalue; + r = unit_full_printf(u, rvalue, &cmd); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); + return 0; + } + + p = cmd; do { _cleanup_free_ char *path = NULL, *firstword = NULL; bool separate_argv0 = false, ignore = false; @@ -1024,7 +1035,7 @@ int config_parse_exec_secure_bits(const char *unit, return 0; } -int config_parse_bounding_set( +int config_parse_capability_set( const char *unit, const char *filename, unsigned line, @@ -1036,8 +1047,8 @@ int config_parse_bounding_set( void *data, void *userdata) { - uint64_t *capability_bounding_set_drop = data; - uint64_t capability_bounding_set, sum = 0; + uint64_t *capability_set = data; + uint64_t sum = 0, initial = 0; bool invert = false; const char *p; @@ -1051,10 +1062,9 @@ int config_parse_bounding_set( rvalue++; } - /* Note that we store this inverted internally, since the - * kernel wants it like this. But we actually expose it - * non-inverted everywhere to have a fully normalized - * interface. */ + if (strcmp(lvalue, "CapabilityBoundingSet") == 0) + initial = CAP_ALL; /* initialized to all bits on */ + /* else "AmbientCapabilities" initialized to all bits off */ p = rvalue; for (;;) { @@ -1073,135 +1083,22 @@ int config_parse_bounding_set( cap = capability_from_name(word); if (cap < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse capability in bounding set, ignoring: %s", word); + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse capability in bounding/ambient set, ignoring: %s", word); continue; } sum |= ((uint64_t) UINT64_C(1)) << (uint64_t) cap; } - capability_bounding_set = invert ? ~sum : sum; - if (*capability_bounding_set_drop != 0 && capability_bounding_set != 0) - *capability_bounding_set_drop = ~(~*capability_bounding_set_drop | capability_bounding_set); - else - *capability_bounding_set_drop = ~capability_bounding_set; - - return 0; -} - -static int rlim_parse_u64(const char *val, rlim_t *res) { - int r = 0; - - if (streq(val, "infinity")) - *res = RLIM_INFINITY; - else { - uint64_t u; - - /* setrlimit(2) suggests rlim_t is always 64bit on Linux. */ - assert_cc(sizeof(rlim_t) == sizeof(uint64_t)); - - r = safe_atou64(val, &u); - if (r >= 0 && u >= (uint64_t) RLIM_INFINITY) - r = -ERANGE; - if (r == 0) - *res = (rlim_t) u; - } - return r; -} - -static int rlim_parse_size(const char *val, rlim_t *res) { - int r = 0; - - if (streq(val, "infinity")) - *res = RLIM_INFINITY; - else { - uint64_t u; - - r = parse_size(val, 1024, &u); - if (r >= 0 && u >= (uint64_t) RLIM_INFINITY) - r = -ERANGE; - if (r == 0) - *res = (rlim_t) u; - } - return r; -} - -static int rlim_parse_sec(const char *val, rlim_t *res) { - int r = 0; - - if (streq(val, "infinity")) - *res = RLIM_INFINITY; - else { - usec_t t; - - r = parse_sec(val, &t); - if (r < 0) - return r; - if (t == USEC_INFINITY) - *res = RLIM_INFINITY; - else - *res = (rlim_t) (DIV_ROUND_UP(t, USEC_PER_SEC)); - - } - return r; -} - -static int rlim_parse_usec(const char *val, rlim_t *res) { - int r = 0; - - if (streq(val, "infinity")) - *res = RLIM_INFINITY; - else { - usec_t t; - - r = parse_time(val, &t, 1); - if (r < 0) - return r; - if (t == USEC_INFINITY) - *res = RLIM_INFINITY; - else - *res = (rlim_t) t; - } - return r; -} + sum = invert ? ~sum : sum; -static int parse_rlimit_range( - const char *unit, - const char *filename, - unsigned line, - const char *value, - struct rlimit **rl, - int (*rlim_parser)(const char *, rlim_t *)) { - - const char *whole_value = value; - rlim_t soft, hard; - _cleanup_free_ char *sword = NULL, *hword = NULL; - int nwords, r; - - assert(value); - - /* <value> or <soft:hard> */ - nwords = extract_many_words(&value, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &sword, &hword, NULL); - r = nwords < 0 ? nwords : nwords == 0 ? -EINVAL : 0; - - if (r == 0) - r = rlim_parser(sword, &soft); - if (r == 0 && nwords == 2) - r = rlim_parser(hword, &hard); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", whole_value); - return 0; - } - if (nwords == 2 && soft > hard) - return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid resource value ("RLIM_FMT" > "RLIM_FMT"), ignoring: %s", soft, hard, whole_value); + if (sum == 0 || *capability_set == initial) + /* "" or uninitialized data -> replace */ + *capability_set = sum; + else + /* previous data -> merge */ + *capability_set |= sum; - if (!*rl) { - *rl = new(struct rlimit, 1); - if (!*rl) - return log_oom(); - } - (*rl)->rlim_cur = soft; - (*rl)->rlim_max = nwords == 2 ? hard : soft; return 0; } @@ -1217,88 +1114,35 @@ int config_parse_limit( void *data, void *userdata) { - struct rlimit **rl = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - rl += ltype; - return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_u64); -} - -int config_parse_bytes_limit( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - struct rlimit **rl = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - rl += ltype; - return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_size); -} - -int config_parse_sec_limit( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - struct rlimit **rl = data; + struct rlimit **rl = data, d = {}; + int r; assert(filename); assert(lvalue); assert(rvalue); assert(data); - rl += ltype; - return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_sec); -} - -int config_parse_usec_limit( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - struct rlimit **rl = data; + r = rlimit_parse(ltype, rvalue, &d); + if (r == -EILSEQ) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Soft resource limit chosen higher than hard limit, ignoring: %s", rvalue); + return 0; + } + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue); + return 0; + } - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); + if (rl[ltype]) + *rl[ltype] = d; + else { + rl[ltype] = newdup(struct rlimit, &d, 1); + if (!rl[ltype]) + return log_oom(); + } - rl += ltype; - return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_usec); + return 0; } - - #ifdef HAVE_SYSV_COMPAT int config_parse_sysv_priority(const char *unit, const char *filename, @@ -1899,6 +1743,15 @@ int config_parse_service_timeout(const char *unit, } else if (streq(lvalue, "TimeoutStartSec")) s->start_timeout_defined = true; + /* Traditionally, these options accepted 0 to disable the timeouts. However, a timeout of 0 suggests it happens + * immediately, hence fix this to become USEC_INFINITY instead. This is in-line with how we internally handle + * all other timeouts. */ + + if (s->timeout_start_usec <= 0) + s->timeout_start_usec = USEC_INFINITY; + if (s->timeout_stop_usec <= 0) + s->timeout_stop_usec = USEC_INFINITY; + return 0; } @@ -4002,7 +3855,7 @@ void unit_dump_config_items(FILE *f) { { config_parse_log_level, "LEVEL" }, { config_parse_exec_capabilities, "CAPABILITIES" }, { config_parse_exec_secure_bits, "SECUREBITS" }, - { config_parse_bounding_set, "BOUNDINGSET" }, + { config_parse_capability_set, "BOUNDINGSET" }, { config_parse_limit, "LIMIT" }, { config_parse_unit_deps, "UNIT [...]" }, { config_parse_exec, "PATH [ARGUMENT [...]]" }, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index a451fc164a..20dd84ba95 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -56,11 +56,8 @@ int config_parse_exec_cpu_sched_prio(const char *unit, const char *filename, uns int config_parse_exec_cpu_affinity(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_exec_capabilities(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_exec_secure_bits(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_bounding_set(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_capability_set(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_bytes_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_sec_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_usec_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_sysv_priority(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_kill_signal(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_exec_mount_flags(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/core/machine-id-setup.c b/src/core/machine-id-setup.c index 145ba2a28d..09b0449c80 100644 --- a/src/core/machine-id-setup.c +++ b/src/core/machine-id-setup.c @@ -198,7 +198,7 @@ static int generate_machine_id(char id[34], const char *root) { return 0; } -int machine_id_setup(const char *root) { +int machine_id_setup(const char *root, sd_id128_t machine_id) { const char *etc_machine_id, *run_machine_id; _cleanup_close_ int fd = -1; bool writable = true; @@ -248,15 +248,22 @@ int machine_id_setup(const char *root) { } } - if (read_machine_id(fd, id) >= 0) - return 0; + /* A machine id argument overrides all other machined-ids */ + if (!sd_id128_is_null(machine_id)) { + sd_id128_to_string(machine_id, id); + id[32] = '\n'; + id[33] = 0; + } else { + if (read_machine_id(fd, id) >= 0) + return 0; - /* Hmm, so, the id currently stored is not useful, then let's - * generate one */ + /* Hmm, so, the id currently stored is not useful, then let's + * generate one */ - r = generate_machine_id(id, root); - if (r < 0) - return r; + r = generate_machine_id(id, root); + if (r < 0) + return r; + } if (writable) if (write_machine_id(fd, id) >= 0) diff --git a/src/core/machine-id-setup.h b/src/core/machine-id-setup.h index f7707c3bf9..a2168a8d4a 100644 --- a/src/core/machine-id-setup.h +++ b/src/core/machine-id-setup.h @@ -22,4 +22,4 @@ ***/ int machine_id_commit(const char *root); -int machine_id_setup(const char *root); +int machine_id_setup(const char *root, sd_id128_t machine_id); diff --git a/src/core/main.c b/src/core/main.c index f9de54028e..fb27e897e4 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -117,7 +117,7 @@ static usec_t arg_runtime_watchdog = 0; static usec_t arg_shutdown_watchdog = 10 * USEC_PER_MINUTE; static char **arg_default_environment = NULL; static struct rlimit *arg_default_rlimit[_RLIMIT_MAX] = {}; -static uint64_t arg_capability_bounding_set_drop = 0; +static uint64_t arg_capability_bounding_set = CAP_ALL; static nsec_t arg_timer_slack_nsec = NSEC_INFINITY; static usec_t arg_default_timer_accuracy_usec = 1 * USEC_PER_MINUTE; static Set* arg_syscall_archs = NULL; @@ -127,6 +127,7 @@ static bool arg_default_blockio_accounting = false; static bool arg_default_memory_accounting = false; static bool arg_default_tasks_accounting = true; static uint64_t arg_default_tasks_max = UINT64_C(512); +static sd_id128_t arg_machine_id = {}; static void pager_open_if_enabled(void) { @@ -300,6 +301,17 @@ static int parse_crash_chvt(const char *value) { return 0; } +static int set_machine_id(const char *m) { + + if (sd_id128_from_string(m, &arg_machine_id) < 0) + return -EINVAL; + + if (sd_id128_is_null(arg_machine_id)) + return -EINVAL; + + return 0; +} + static int parse_proc_cmdline_item(const char *key, const char *value) { int r; @@ -388,6 +400,12 @@ static int parse_proc_cmdline_item(const char *key, const char *value) { } else log_warning("Environment variable name '%s' is not valid. Ignoring.", value); + } else if (streq(key, "systemd.machine_id") && value) { + + r = set_machine_id(value); + if (r < 0) + log_warning("MachineID '%s' is not valid. Ignoring.", value); + } else if (streq(key, "quiet") && !value) { if (arg_show_status == _SHOW_STATUS_UNSET) @@ -644,7 +662,7 @@ static int parse_config_file(void) { { "Manager", "JoinControllers", config_parse_join_controllers, 0, &arg_join_controllers }, { "Manager", "RuntimeWatchdogSec", config_parse_sec, 0, &arg_runtime_watchdog }, { "Manager", "ShutdownWatchdogSec", config_parse_sec, 0, &arg_shutdown_watchdog }, - { "Manager", "CapabilityBoundingSet", config_parse_bounding_set, 0, &arg_capability_bounding_set_drop }, + { "Manager", "CapabilityBoundingSet", config_parse_capability_set, 0, &arg_capability_bounding_set }, #ifdef HAVE_SECCOMP { "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs }, #endif @@ -658,22 +676,22 @@ static int parse_config_file(void) { { "Manager", "DefaultStartLimitInterval", config_parse_sec, 0, &arg_default_start_limit_interval }, { "Manager", "DefaultStartLimitBurst", config_parse_unsigned, 0, &arg_default_start_limit_burst }, { "Manager", "DefaultEnvironment", config_parse_environ, 0, &arg_default_environment }, - { "Manager", "DefaultLimitCPU", config_parse_sec_limit, 0, &arg_default_rlimit[RLIMIT_CPU] }, - { "Manager", "DefaultLimitFSIZE", config_parse_bytes_limit, 0, &arg_default_rlimit[RLIMIT_FSIZE] }, - { "Manager", "DefaultLimitDATA", config_parse_bytes_limit, 0, &arg_default_rlimit[RLIMIT_DATA] }, - { "Manager", "DefaultLimitSTACK", config_parse_bytes_limit, 0, &arg_default_rlimit[RLIMIT_STACK] }, - { "Manager", "DefaultLimitCORE", config_parse_bytes_limit, 0, &arg_default_rlimit[RLIMIT_CORE] }, - { "Manager", "DefaultLimitRSS", config_parse_bytes_limit, 0, &arg_default_rlimit[RLIMIT_RSS] }, - { "Manager", "DefaultLimitNOFILE", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_NOFILE] }, - { "Manager", "DefaultLimitAS", config_parse_bytes_limit, 0, &arg_default_rlimit[RLIMIT_AS] }, - { "Manager", "DefaultLimitNPROC", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_NPROC] }, - { "Manager", "DefaultLimitMEMLOCK", config_parse_bytes_limit, 0, &arg_default_rlimit[RLIMIT_MEMLOCK] }, - { "Manager", "DefaultLimitLOCKS", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_LOCKS] }, - { "Manager", "DefaultLimitSIGPENDING", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_SIGPENDING] }, - { "Manager", "DefaultLimitMSGQUEUE", config_parse_bytes_limit, 0, &arg_default_rlimit[RLIMIT_MSGQUEUE] }, - { "Manager", "DefaultLimitNICE", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_NICE] }, - { "Manager", "DefaultLimitRTPRIO", config_parse_limit, 0, &arg_default_rlimit[RLIMIT_RTPRIO] }, - { "Manager", "DefaultLimitRTTIME", config_parse_usec_limit, 0, &arg_default_rlimit[RLIMIT_RTTIME] }, + { "Manager", "DefaultLimitCPU", config_parse_limit, RLIMIT_CPU, arg_default_rlimit }, + { "Manager", "DefaultLimitFSIZE", config_parse_limit, RLIMIT_FSIZE, arg_default_rlimit }, + { "Manager", "DefaultLimitDATA", config_parse_limit, RLIMIT_DATA, arg_default_rlimit }, + { "Manager", "DefaultLimitSTACK", config_parse_limit, RLIMIT_STACK, arg_default_rlimit }, + { "Manager", "DefaultLimitCORE", config_parse_limit, RLIMIT_CORE, arg_default_rlimit }, + { "Manager", "DefaultLimitRSS", config_parse_limit, RLIMIT_RSS, arg_default_rlimit }, + { "Manager", "DefaultLimitNOFILE", config_parse_limit, RLIMIT_NOFILE, arg_default_rlimit }, + { "Manager", "DefaultLimitAS", config_parse_limit, RLIMIT_AS, arg_default_rlimit }, + { "Manager", "DefaultLimitNPROC", config_parse_limit, RLIMIT_NPROC, arg_default_rlimit }, + { "Manager", "DefaultLimitMEMLOCK", config_parse_limit, RLIMIT_MEMLOCK, arg_default_rlimit }, + { "Manager", "DefaultLimitLOCKS", config_parse_limit, RLIMIT_LOCKS, arg_default_rlimit }, + { "Manager", "DefaultLimitSIGPENDING", config_parse_limit, RLIMIT_SIGPENDING, arg_default_rlimit }, + { "Manager", "DefaultLimitMSGQUEUE", config_parse_limit, RLIMIT_MSGQUEUE, arg_default_rlimit }, + { "Manager", "DefaultLimitNICE", config_parse_limit, RLIMIT_NICE, arg_default_rlimit }, + { "Manager", "DefaultLimitRTPRIO", config_parse_limit, RLIMIT_RTPRIO, arg_default_rlimit }, + { "Manager", "DefaultLimitRTTIME", config_parse_limit, RLIMIT_RTTIME, arg_default_rlimit }, { "Manager", "DefaultCPUAccounting", config_parse_bool, 0, &arg_default_cpu_accounting }, { "Manager", "DefaultBlockIOAccounting", config_parse_bool, 0, &arg_default_blockio_accounting }, { "Manager", "DefaultMemoryAccounting", config_parse_bool, 0, &arg_default_memory_accounting }, @@ -692,8 +710,14 @@ static int parse_config_file(void) { CONF_PATHS_NULSTR("systemd/system.conf.d") : CONF_PATHS_NULSTR("systemd/user.conf.d"); - config_parse_many(fn, conf_dirs_nulstr, "Manager\0", - config_item_table_lookup, items, false, NULL); + config_parse_many(fn, conf_dirs_nulstr, "Manager\0", config_item_table_lookup, items, false, NULL); + + /* Traditionally "0" was used to turn off the default unit timeouts. Fix this up so that we used USEC_INFINITY + * like everywhere else. */ + if (arg_default_timeout_start_usec <= 0) + arg_default_timeout_start_usec = USEC_INFINITY; + if (arg_default_timeout_stop_usec <= 0) + arg_default_timeout_stop_usec = USEC_INFINITY; return 0; } @@ -743,7 +767,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_DESERIALIZE, ARG_SWITCHED_ROOT, ARG_DEFAULT_STD_OUTPUT, - ARG_DEFAULT_STD_ERROR + ARG_DEFAULT_STD_ERROR, + ARG_MACHINE_ID }; static const struct option options[] = { @@ -769,6 +794,7 @@ static int parse_argv(int argc, char *argv[]) { { "switched-root", no_argument, NULL, ARG_SWITCHED_ROOT }, { "default-standard-output", required_argument, NULL, ARG_DEFAULT_STD_OUTPUT, }, { "default-standard-error", required_argument, NULL, ARG_DEFAULT_STD_ERROR, }, + { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, {} }; @@ -964,6 +990,14 @@ static int parse_argv(int argc, char *argv[]) { arg_switched_root = true; break; + case ARG_MACHINE_ID: + r = set_machine_id(optarg); + if (r < 0) { + log_error("MachineID '%s' is not valid.", optarg); + return r; + } + break; + case 'h': arg_action = ACTION_HELP; if (arg_no_pager < 0) @@ -1335,7 +1369,11 @@ int main(int argc, char *argv[]) { initrd_timestamp = userspace_timestamp; if (!skip_setup) { - mount_setup_early(); + r = mount_setup_early(); + if (r < 0) { + error_message = "Failed to early mount API filesystems"; + goto finish; + } dual_timestamp_get(&security_start_timestamp); if (mac_selinux_setup(&loaded_policy) < 0) { error_message = "Failed to load SELinux policy"; @@ -1386,8 +1424,14 @@ int main(int argc, char *argv[]) { * saving time change. All kernel local time concepts will be treated * as UTC that way. */ - clock_reset_timewarp(); + (void) clock_reset_timewarp(); } + + r = clock_apply_epoch(); + if (r < 0) + log_error_errno(r, "Current system time is before build time, but cannot correct: %m"); + else if (r > 0) + log_info("System time before build time, advancing clock."); } /* Set the default for later on, but don't actually @@ -1617,7 +1661,7 @@ int main(int argc, char *argv[]) { status_welcome(); hostname_setup(); - machine_id_setup(NULL); + machine_id_setup(NULL, arg_machine_id); loopback_setup(); bump_unix_max_dgram_qlen(); @@ -1631,14 +1675,14 @@ int main(int argc, char *argv[]) { if (prctl(PR_SET_TIMERSLACK, arg_timer_slack_nsec) < 0) log_error_errno(errno, "Failed to adjust timer slack: %m"); - if (arg_capability_bounding_set_drop) { - r = capability_bounding_set_drop_usermode(arg_capability_bounding_set_drop); + if (!cap_test_all(arg_capability_bounding_set)) { + r = capability_bounding_set_drop_usermode(arg_capability_bounding_set); if (r < 0) { log_emergency_errno(r, "Failed to drop capability bounding set of usermode helpers: %m"); error_message = "Failed to drop capability bounding set of usermode helpers"; goto finish; } - r = capability_bounding_set_drop(arg_capability_bounding_set_drop, true); + r = capability_bounding_set_drop(arg_capability_bounding_set, true); if (r < 0) { log_emergency_errno(r, "Failed to drop capability bounding set: %m"); error_message = "Failed to drop capability bounding set"; @@ -1940,6 +1984,15 @@ finish: (void) clearenv(); assert(i <= args_size); + + /* + * We want valgrind to print its memory usage summary before reexecution. + * Valgrind won't do this is on its own on exec(), but it will do it on exit(). + * Hence, to ensure we get a summary here, fork() off a child, let it exit() cleanly, + * so that it prints the summary, and wait() for it in the parent, before proceeding into the exec(). + */ + valgrind_summary_hack(); + (void) execv(args[0], (char* const*) args); } diff --git a/src/core/manager.c b/src/core/manager.c index 34dd715e93..e8fea376ff 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; @@ -380,6 +380,9 @@ static int enable_special_signals(Manager *m) { assert(m); + if (m->test_run) + return 0; + /* Enable that we get SIGINT on control-alt-del. In containers * this will fail with EPERM (older) or EINVAL (newer), so * ignore that. */ @@ -986,7 +989,7 @@ Manager* manager_free(Manager *m) { free(m->switch_root_init); for (i = 0; i < _RLIMIT_MAX; i++) - free(m->rlimit[i]); + m->rlimit[i] = mfree(m->rlimit[i]); assert(hashmap_isempty(m->units_requiring_mounts_for)); hashmap_free(m->units_requiring_mounts_for); @@ -1885,23 +1888,21 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t switch (sfsi.ssi_signo - SIGRTMIN) { case 20: - log_debug("Enabling showing of status."); manager_set_show_status(m, SHOW_STATUS_YES); break; case 21: - log_debug("Disabling showing of status."); manager_set_show_status(m, SHOW_STATUS_NO); break; case 22: log_set_max_level(LOG_DEBUG); - log_notice("Setting log level to debug."); + log_info("Setting log level to debug."); break; case 23: log_set_max_level(LOG_INFO); - log_notice("Setting log level to info."); + log_info("Setting log level to info."); break; case 24: @@ -2576,6 +2577,10 @@ int manager_reload(Manager *m) { /* Third, fire things up! */ manager_coldplug(m); + /* Sync current state of bus names with our set of listening units */ + if (m->api_bus) + manager_sync_bus_names(m, m->api_bus); + assert(m->n_reloading > 0); m->n_reloading--; @@ -2918,6 +2923,8 @@ int manager_set_default_rlimits(Manager *m, struct rlimit **default_rlimit) { assert(m); for (i = 0; i < _RLIMIT_MAX; i++) { + m->rlimit[i] = mfree(m->rlimit[i]); + if (!default_rlimit[i]) continue; @@ -2961,6 +2968,9 @@ void manager_set_show_status(Manager *m, ShowStatus mode) { if (m->running_as != MANAGER_SYSTEM) return; + if (m->show_status != mode) + log_debug("%s showing of status.", + mode == SHOW_STATUS_NO ? "Disabling" : "Enabling"); m->show_status = mode; if (mode > 0) @@ -3087,18 +3097,18 @@ ManagerState manager_state(Manager *m) { /* Is the special shutdown target queued? If so, we are in shutdown state */ u = manager_get_unit(m, SPECIAL_SHUTDOWN_TARGET); - if (u && u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_TRY_RESTART, JOB_RELOAD_OR_START)) + if (u && u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START)) return MANAGER_STOPPING; /* Are the rescue or emergency targets active or queued? If so we are in maintenance state */ u = manager_get_unit(m, SPECIAL_RESCUE_TARGET); if (u && (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)) || - (u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_TRY_RESTART, JOB_RELOAD_OR_START)))) + (u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START)))) return MANAGER_MAINTENANCE; u = manager_get_unit(m, SPECIAL_EMERGENCY_TARGET); if (u && (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)) || - (u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_TRY_RESTART, JOB_RELOAD_OR_START)))) + (u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START)))) return MANAGER_MAINTENANCE; /* Are there any failed units? If so, we are in degraded mode */ diff --git a/src/core/mount-setup.c b/src/core/mount-setup.c index 2b8d590ed1..321a52c30e 100644 --- a/src/core/mount-setup.c +++ b/src/core/mount-setup.c @@ -158,11 +158,13 @@ static int mount_one(const MountPoint *p, bool relabel) { /* Relabel first, just in case */ if (relabel) - label_fix(p->where, true, true); + (void) label_fix(p->where, true, true); r = path_is_mount_point(p->where, AT_SYMLINK_FOLLOW); - if (r < 0 && r != -ENOENT) - return r; + if (r < 0 && r != -ENOENT) { + log_full_errno((p->mode & MNT_FATAL) ? LOG_ERR : LOG_DEBUG, r, "Failed to determine whether %s is a mount point: %m", p->where); + return (p->mode & MNT_FATAL) ? r : 0; + } if (r > 0) return 0; @@ -173,9 +175,9 @@ static int mount_one(const MountPoint *p, bool relabel) { /* The access mode here doesn't really matter too much, since * the mounted file system will take precedence anyway. */ if (relabel) - mkdir_p_label(p->where, 0755); + (void) mkdir_p_label(p->where, 0755); else - mkdir_p(p->where, 0755); + (void) mkdir_p(p->where, 0755); log_debug("Mounting %s to %s of type %s with options %s.", p->what, @@ -188,29 +190,25 @@ static int mount_one(const MountPoint *p, bool relabel) { p->type, p->flags, p->options) < 0) { - log_full((p->mode & MNT_FATAL) ? LOG_ERR : LOG_DEBUG, "Failed to mount %s at %s: %m", p->type, p->where); + log_full_errno((p->mode & MNT_FATAL) ? LOG_ERR : LOG_DEBUG, errno, "Failed to mount %s at %s: %m", p->type, p->where); return (p->mode & MNT_FATAL) ? -errno : 0; } /* Relabel again, since we now mounted something fresh here */ if (relabel) - label_fix(p->where, false, false); + (void) label_fix(p->where, false, false); return 1; } -int mount_setup_early(void) { +static int mount_points_setup(unsigned n, bool loaded_policy) { unsigned i; int r = 0; - assert_cc(N_EARLY_MOUNT <= ELEMENTSOF(mount_table)); - - /* Do a minimal mount of /proc and friends to enable the most - * basic stuff, such as SELinux */ - for (i = 0; i < N_EARLY_MOUNT; i ++) { + for (i = 0; i < n; i ++) { int j; - j = mount_one(mount_table + i, false); + j = mount_one(mount_table + i, loaded_policy); if (j != 0 && r >= 0) r = j; } @@ -218,6 +216,14 @@ int mount_setup_early(void) { return r; } +int mount_setup_early(void) { + assert_cc(N_EARLY_MOUNT <= ELEMENTSOF(mount_table)); + + /* Do a minimal mount of /proc and friends to enable the most + * basic stuff, such as SELinux */ + return mount_points_setup(N_EARLY_MOUNT, false); +} + int mount_cgroup_controllers(char ***join_controllers) { _cleanup_set_free_free_ Set *controllers = NULL; int r; @@ -304,13 +310,18 @@ int mount_cgroup_controllers(char ***join_controllers) { return log_oom(); r = symlink(options, t); - if (r < 0 && errno != EEXIST) - return log_error_errno(errno, "Failed to create symlink %s: %m", t); + if (r >= 0) { #ifdef SMACK_RUN_LABEL - r = mac_smack_copy(t, options); - if (r < 0 && r != -EOPNOTSUPP) - return log_error_errno(r, "Failed to copy smack label from %s to %s: %m", options, t); + _cleanup_free_ char *src; + src = strappend("/sys/fs/cgroup/", options); + if (!src) + return log_oom(); + r = mac_smack_copy(t, src); + if (r < 0 && r != -EOPNOTSUPP) + return log_error_errno(r, "Failed to copy smack label from %s to %s: %m", src, t); #endif + } else if (errno != EEXIST) + return log_error_errno(errno, "Failed to create symlink %s: %m", t); } } } @@ -347,16 +358,9 @@ static int nftw_cb( #endif int mount_setup(bool loaded_policy) { - unsigned i; int r = 0; - for (i = 0; i < ELEMENTSOF(mount_table); i ++) { - int j; - - j = mount_one(mount_table + i, loaded_policy); - if (j != 0 && r >= 0) - r = j; - } + r = mount_points_setup(ELEMENTSOF(mount_table), loaded_policy); if (r < 0) return r; diff --git a/src/core/mount.c b/src/core/mount.c index 2ad4ad4f42..e0bd09bbbb 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -152,29 +152,27 @@ static void mount_init(Unit *u) { u->ignore_on_isolate = true; } -static int mount_arm_timer(Mount *m) { +static int mount_arm_timer(Mount *m, usec_t usec) { int r; assert(m); - if (m->timeout_usec <= 0) { - m->timer_event_source = sd_event_source_unref(m->timer_event_source); - return 0; - } - if (m->timer_event_source) { - r = sd_event_source_set_time(m->timer_event_source, now(CLOCK_MONOTONIC) + m->timeout_usec); + r = sd_event_source_set_time(m->timer_event_source, usec); if (r < 0) return r; return sd_event_source_set_enabled(m->timer_event_source, SD_EVENT_ONESHOT); } + if (usec == USEC_INFINITY) + return 0; + r = sd_event_add_time( UNIT(m)->manager->event, &m->timer_event_source, CLOCK_MONOTONIC, - now(CLOCK_MONOTONIC) + m->timeout_usec, 0, + usec, 0, mount_dispatch_timer, m); if (r < 0) return r; @@ -653,7 +651,7 @@ static int mount_coldplug(Unit *u) { if (r < 0) return r; - r = mount_arm_timer(m); + r = mount_arm_timer(m, usec_add(u->state_change_timestamp.monotonic, m->timeout_usec)); if (r < 0) return r; } @@ -725,11 +723,11 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { r = unit_setup_exec_runtime(UNIT(m)); if (r < 0) - goto fail; + return r; - r = mount_arm_timer(m); + r = mount_arm_timer(m, usec_add(now(CLOCK_MONOTONIC), m->timeout_usec)); if (r < 0) - goto fail; + return r; exec_params.environment = UNIT(m)->manager->environment; exec_params.confirm_spawn = UNIT(m)->manager->confirm_spawn; @@ -745,21 +743,16 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { m->exec_runtime, &pid); if (r < 0) - goto fail; + return r; r = unit_watch_pid(UNIT(m), pid); if (r < 0) /* FIXME: we need to do something here */ - goto fail; + return r; *_pid = pid; return 0; - -fail: - m->timer_event_source = sd_event_source_unref(m->timer_event_source); - - return r; } static void mount_enter_dead(Mount *m, MountResult f) { @@ -805,7 +798,7 @@ static void mount_enter_signal(Mount *m, MountState state, MountResult f) { goto fail; if (r > 0) { - r = mount_arm_timer(m); + r = mount_arm_timer(m, usec_add(now(CLOCK_MONOTONIC), m->timeout_usec)); if (r < 0) goto fail; @@ -1563,17 +1556,21 @@ static void mount_shutdown(Manager *m) { m->mount_monitor = NULL; } -static int mount_get_timeout(Unit *u, uint64_t *timeout) { +static int mount_get_timeout(Unit *u, usec_t *timeout) { Mount *m = MOUNT(u); + usec_t t; int r; if (!m->timer_event_source) return 0; - r = sd_event_source_get_time(m->timer_event_source, timeout); + r = sd_event_source_get_time(m->timer_event_source, &t); if (r < 0) return r; + if (t == USEC_INFINITY) + return 0; + *timeout = t; return 1; } diff --git a/src/core/scope.c b/src/core/scope.c index 1953af1f88..7dd437d204 100644 --- a/src/core/scope.c +++ b/src/core/scope.c @@ -66,29 +66,27 @@ static void scope_done(Unit *u) { s->timer_event_source = sd_event_source_unref(s->timer_event_source); } -static int scope_arm_timer(Scope *s) { +static int scope_arm_timer(Scope *s, usec_t usec) { int r; assert(s); - if (s->timeout_stop_usec <= 0) { - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - return 0; - } - if (s->timer_event_source) { - r = sd_event_source_set_time(s->timer_event_source, now(CLOCK_MONOTONIC) + s->timeout_stop_usec); + r = sd_event_source_set_time(s->timer_event_source, usec); if (r < 0) return r; return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); } + if (usec == USEC_INFINITY) + return 0; + r = sd_event_add_time( UNIT(s)->manager->event, &s->timer_event_source, CLOCK_MONOTONIC, - now(CLOCK_MONOTONIC) + s->timeout_stop_usec, 0, + usec, 0, scope_dispatch_timer, s); if (r < 0) return r; @@ -190,20 +188,19 @@ static int scope_coldplug(Unit *u) { assert(s); assert(s->state == SCOPE_DEAD); - if (s->deserialized_state != s->state) { - - if (IN_SET(s->deserialized_state, SCOPE_STOP_SIGKILL, SCOPE_STOP_SIGTERM)) { - r = scope_arm_timer(s); - if (r < 0) - return r; - } - - if (!IN_SET(s->deserialized_state, SCOPE_DEAD, SCOPE_FAILED)) - unit_watch_all_pids(UNIT(s)); + if (s->deserialized_state == s->state) + return 0; - scope_set_state(s, s->deserialized_state); + if (IN_SET(s->deserialized_state, SCOPE_STOP_SIGKILL, SCOPE_STOP_SIGTERM)) { + r = scope_arm_timer(s, usec_add(u->state_change_timestamp.monotonic, s->timeout_stop_usec)); + if (r < 0) + return r; } + if (!IN_SET(s->deserialized_state, SCOPE_DEAD, SCOPE_FAILED)) + unit_watch_all_pids(UNIT(s)); + + scope_set_state(s, s->deserialized_state); return 0; } @@ -261,7 +258,7 @@ static void scope_enter_signal(Scope *s, ScopeState state, ScopeResult f) { r = 1; if (r > 0) { - r = scope_arm_timer(s); + r = scope_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_stop_usec)); if (r < 0) goto fail; @@ -348,17 +345,21 @@ static int scope_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { return unit_kill_common(u, who, signo, -1, -1, error); } -static int scope_get_timeout(Unit *u, uint64_t *timeout) { +static int scope_get_timeout(Unit *u, usec_t *timeout) { Scope *s = SCOPE(u); + usec_t t; int r; if (!s->timer_event_source) return 0; - r = sd_event_source_get_time(s->timer_event_source, timeout); + r = sd_event_source_get_time(s->timer_event_source, &t); if (r < 0) return r; + if (t == USEC_INFINITY) + return 0; + *timeout = t; return 1; } diff --git a/src/core/service.c b/src/core/service.c index 41a729c421..02ce1a566a 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -112,6 +112,7 @@ static void service_init(Unit *u) { s->timeout_start_usec = u->manager->default_timeout_start_usec; s->timeout_stop_usec = u->manager->default_timeout_stop_usec; s->restart_usec = u->manager->default_restart_usec; + s->runtime_max_usec = USEC_INFINITY; s->type = _SERVICE_TYPE_INVALID; s->socket_fd = -1; s->bus_endpoint_fd = -1; @@ -216,7 +217,7 @@ static void service_start_watchdog(Service *s) { return; if (s->watchdog_event_source) { - r = sd_event_source_set_time(s->watchdog_event_source, s->watchdog_timestamp.monotonic + s->watchdog_usec); + r = sd_event_source_set_time(s->watchdog_event_source, usec_add(s->watchdog_timestamp.monotonic, s->watchdog_usec)); if (r < 0) { log_unit_warning_errno(UNIT(s), r, "Failed to reset watchdog timer: %m"); return; @@ -228,7 +229,7 @@ static void service_start_watchdog(Service *s) { UNIT(s)->manager->event, &s->watchdog_event_source, CLOCK_MONOTONIC, - s->watchdog_timestamp.monotonic + s->watchdog_usec, 0, + usec_add(s->watchdog_timestamp.monotonic, s->watchdog_usec), 0, service_dispatch_watchdog, s); if (r < 0) { log_unit_warning_errno(UNIT(s), r, "Failed to add watchdog timer: %m"); @@ -323,6 +324,8 @@ static void service_done(Unit *u) { s->bus_name = mfree(s->bus_name); } + s->bus_name_owner = mfree(s->bus_name_owner); + s->bus_endpoint_fd = safe_close(s->bus_endpoint_fd); service_close_socket_fd(s); service_connection_unref(s); @@ -431,18 +434,21 @@ static int service_arm_timer(Service *s, usec_t usec) { assert(s); if (s->timer_event_source) { - r = sd_event_source_set_time(s->timer_event_source, now(CLOCK_MONOTONIC) + usec); + r = sd_event_source_set_time(s->timer_event_source, usec); if (r < 0) return r; return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); } + if (usec == USEC_INFINITY) + return 0; + r = sd_event_add_time( UNIT(s)->manager->event, &s->timer_event_source, CLOCK_MONOTONIC, - now(CLOCK_MONOTONIC) + usec, 0, + usec, 0, service_dispatch_timer, s); if (r < 0) return r; @@ -507,6 +513,9 @@ static int service_verify(Service *s) { if (!s->usb_function_descriptors && s->usb_function_strings) log_unit_warning(UNIT(s), "Service has USBFunctionStrings= setting, but no USBFunctionDescriptors=. Ignoring."); + if (s->runtime_max_usec != USEC_INFINITY && s->type == SERVICE_ONESHOT) + log_unit_warning(UNIT(s), "MaxRuntimeSec= has no effect in combination with Type=oneshot. Ignoring."); + return 0; } @@ -622,7 +631,7 @@ static int service_add_extras(Service *s) { /* Oneshot services have disabled start timeout by default */ if (s->type == SERVICE_ONESHOT && !s->start_timeout_defined) - s->timeout_start_usec = 0; + s->timeout_start_usec = USEC_INFINITY; service_fix_output(s); @@ -880,6 +889,7 @@ static void service_set_state(Service *s, ServiceState state) { if (!IN_SET(state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, + SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, @@ -952,6 +962,37 @@ static void service_set_state(Service *s, ServiceState state) { unit_notify(UNIT(s), table[old_state], table[state], s->reload_result == SERVICE_SUCCESS); } +static usec_t service_coldplug_timeout(Service *s) { + assert(s); + + switch (s->deserialized_state) { + + case SERVICE_START_PRE: + case SERVICE_START: + case SERVICE_START_POST: + case SERVICE_RELOAD: + return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_start_usec); + + case SERVICE_RUNNING: + return usec_add(UNIT(s)->active_enter_timestamp.monotonic, s->runtime_max_usec); + + case SERVICE_STOP: + case SERVICE_STOP_SIGABRT: + case SERVICE_STOP_SIGTERM: + case SERVICE_STOP_SIGKILL: + case SERVICE_STOP_POST: + case SERVICE_FINAL_SIGTERM: + case SERVICE_FINAL_SIGKILL: + return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_stop_usec); + + case SERVICE_AUTO_RESTART: + return usec_add(UNIT(s)->inactive_enter_timestamp.monotonic, s->restart_usec); + + default: + return USEC_INFINITY; + } +} + static int service_coldplug(Unit *u) { Service *s = SERVICE(u); int r; @@ -962,31 +1003,9 @@ static int service_coldplug(Unit *u) { if (s->deserialized_state == s->state) return 0; - if (IN_SET(s->deserialized_state, - SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, - SERVICE_RELOAD, - SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { - - usec_t k; - - k = IN_SET(s->deserialized_state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD) ? s->timeout_start_usec : s->timeout_stop_usec; - - /* For the start/stop timeouts 0 means off */ - if (k > 0) { - r = service_arm_timer(s, k); - if (r < 0) - return r; - } - } - - if (s->deserialized_state == SERVICE_AUTO_RESTART) { - - /* The restart timeouts 0 means immediately */ - r = service_arm_timer(s, s->restart_usec); - if (r < 0) - return r; - } + r = service_arm_timer(s, service_coldplug_timeout(s)); + if (r < 0) + return r; if (s->main_pid > 0 && pid_is_unwaited(s->main_pid) && @@ -1173,7 +1192,7 @@ static int service_spawn( r = unit_setup_exec_runtime(UNIT(s)); if (r < 0) - goto fail; + return r; if (pass_fds || s->exec_context.std_input == EXEC_INPUT_SOCKET || @@ -1182,55 +1201,42 @@ static int service_spawn( r = service_collect_fds(s, &fds, &fd_names); if (r < 0) - goto fail; + return r; n_fds = r; } - if (timeout > 0) { - r = service_arm_timer(s, timeout); - if (r < 0) - goto fail; - } else - s->timer_event_source = sd_event_source_unref(s->timer_event_source); + r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), timeout)); + if (r < 0) + return r; r = unit_full_printf_strv(UNIT(s), c->argv, &argv); if (r < 0) - goto fail; + return r; our_env = new0(char*, 6); - if (!our_env) { - r = -ENOMEM; - goto fail; - } + if (!our_env) + return -ENOMEM; if (is_control ? s->notify_access == NOTIFY_ALL : s->notify_access != NOTIFY_NONE) - if (asprintf(our_env + n_env++, "NOTIFY_SOCKET=%s", UNIT(s)->manager->notify_socket) < 0) { - r = -ENOMEM; - goto fail; - } + if (asprintf(our_env + n_env++, "NOTIFY_SOCKET=%s", UNIT(s)->manager->notify_socket) < 0) + return -ENOMEM; if (s->main_pid > 0) - if (asprintf(our_env + n_env++, "MAINPID="PID_FMT, s->main_pid) < 0) { - r = -ENOMEM; - goto fail; - } + if (asprintf(our_env + n_env++, "MAINPID="PID_FMT, s->main_pid) < 0) + return -ENOMEM; if (UNIT(s)->manager->running_as != MANAGER_SYSTEM) - if (asprintf(our_env + n_env++, "MANAGERPID="PID_FMT, getpid()) < 0) { - r = -ENOMEM; - goto fail; - } + if (asprintf(our_env + n_env++, "MANAGERPID="PID_FMT, getpid()) < 0) + return -ENOMEM; if (s->socket_fd >= 0) { union sockaddr_union sa; socklen_t salen = sizeof(sa); r = getpeername(s->socket_fd, &sa.sa, &salen); - if (r < 0) { - r = -errno; - goto fail; - } + if (r < 0) + return -errno; if (IN_SET(sa.sa.sa_family, AF_INET, AF_INET6)) { _cleanup_free_ char *addr = NULL; @@ -1239,34 +1245,26 @@ static int service_spawn( r = sockaddr_pretty(&sa.sa, salen, true, false, &addr); if (r < 0) - goto fail; + return r; t = strappend("REMOTE_ADDR=", addr); - if (!t) { - r = -ENOMEM; - goto fail; - } + if (!t) + return -ENOMEM; our_env[n_env++] = t; port = sockaddr_port(&sa.sa); - if (port < 0) { - r = port; - goto fail; - } + if (port < 0) + return port; - if (asprintf(&t, "REMOTE_PORT=%u", port) < 0) { - r = -ENOMEM; - goto fail; - } + if (asprintf(&t, "REMOTE_PORT=%u", port) < 0) + return -ENOMEM; our_env[n_env++] = t; } } final_env = strv_env_merge(2, UNIT(s)->manager->environment, our_env, NULL); - if (!final_env) { - r = -ENOMEM; - goto fail; - } + if (!final_env) + return -ENOMEM; if (is_control && UNIT(s)->cgroup_path) { path = strjoina(UNIT(s)->cgroup_path, "/control"); @@ -1278,7 +1276,7 @@ static int service_spawn( r = bus_kernel_create_endpoint(UNIT(s)->manager->running_as == MANAGER_SYSTEM ? "system" : "user", UNIT(s)->id, &bus_endpoint_path); if (r < 0) - goto fail; + return r; /* Pass the fd to the exec_params so that the child process can upload the policy. * Keep a reference to the fd in the service, so the endpoint is kept alive as long @@ -1312,22 +1310,16 @@ static int service_spawn( s->exec_runtime, &pid); if (r < 0) - goto fail; + return r; r = unit_watch_pid(UNIT(s), pid); if (r < 0) /* FIXME: we need to do something here */ - goto fail; + return r; *_pid = pid; return 0; - -fail: - if (timeout) - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - - return r; } static int main_pid_good(Service *s) { @@ -1435,7 +1427,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) if (allow_restart && service_shall_restart(s)) { - r = service_arm_timer(s, s->restart_usec); + r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->restart_usec)); if (r < 0) goto fail; @@ -1456,7 +1448,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) * out-of-date, and some software might be confused by it, so * let's remove it. */ if (s->pid_file) - unlink_noerrno(s->pid_file); + (void) unlink(s->pid_file); return; @@ -1543,11 +1535,9 @@ static void service_enter_signal(Service *s, ServiceState state, ServiceResult f goto fail; if (r > 0) { - if (s->timeout_stop_usec > 0) { - r = service_arm_timer(s, s->timeout_stop_usec); - if (r < 0) - goto fail; - } + r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_stop_usec)); + if (r < 0) + goto fail; service_set_state(s, state); } else if (IN_SET(state, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM) && s->kill_context.send_sigkill) @@ -1575,8 +1565,7 @@ static void service_enter_stop_by_notify(Service *s) { unit_watch_all_pids(UNIT(s)); - if (s->timeout_stop_usec > 0) - service_arm_timer(s, s->timeout_stop_usec); + service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_stop_usec)); /* The service told us it's stopping, so it's as if we SIGTERM'd it. */ service_set_state(s, SERVICE_STOP_SIGTERM); @@ -1646,6 +1635,8 @@ static void service_enter_running(Service *s, ServiceResult f) { if (f != SERVICE_SUCCESS) s->result = f; + service_unwatch_control_pid(s); + if (service_good(s)) { /* If there are any queued up sd_notify() @@ -1654,8 +1645,10 @@ static void service_enter_running(Service *s, ServiceResult f) { service_enter_reload_by_notify(s); else if (s->notify_state == NOTIFY_STOPPING) service_enter_stop_by_notify(s); - else + else { service_set_state(s, SERVICE_RUNNING); + service_arm_timer(s, usec_add(UNIT(s)->active_enter_timestamp.monotonic, s->runtime_max_usec)); + } } else if (s->remain_after_exit) service_set_state(s, SERVICE_EXITED); @@ -1709,6 +1702,7 @@ static void service_kill_control_processes(Service *s) { static void service_enter_start(Service *s) { ExecCommand *c; + usec_t timeout; pid_t pid; int r; @@ -1740,9 +1734,16 @@ static void service_enter_start(Service *s) { return; } + if (IN_SET(s->type, SERVICE_SIMPLE, SERVICE_IDLE)) + /* For simple + idle this is the main process. We don't apply any timeout here, but + * service_enter_running() will later apply the .runtime_max_usec timeout. */ + timeout = USEC_INFINITY; + else + timeout = s->timeout_start_usec; + r = service_spawn(s, c, - IN_SET(s->type, SERVICE_FORKING, SERVICE_DBUS, SERVICE_NOTIFY, SERVICE_ONESHOT) ? s->timeout_start_usec : 0, + timeout, true, true, true, @@ -1752,7 +1753,7 @@ static void service_enter_start(Service *s) { if (r < 0) goto fail; - if (s->type == SERVICE_SIMPLE || s->type == SERVICE_IDLE) { + if (IN_SET(s->type, SERVICE_SIMPLE, SERVICE_IDLE)) { /* For simple services we immediately start * the START_POST binaries. */ @@ -1767,9 +1768,7 @@ static void service_enter_start(Service *s) { s->control_pid = pid; service_set_state(s, SERVICE_START); - } else if (s->type == SERVICE_ONESHOT || - s->type == SERVICE_DBUS || - s->type == SERVICE_NOTIFY) { + } else if (IN_SET(s->type, SERVICE_ONESHOT, SERVICE_DBUS, SERVICE_NOTIFY)) { /* For oneshot services we wait until the start * process exited, too, but it is our main process. */ @@ -1838,7 +1837,7 @@ static void service_enter_restart(Service *s) { /* Don't restart things if we are going down anyway */ log_unit_info(UNIT(s), "Stop job pending for unit, delaying automatic restart."); - r = service_arm_timer(s, s->restart_usec); + r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->restart_usec)); if (r < 0) goto fail; @@ -1868,9 +1867,7 @@ fail: static void service_enter_reload_by_notify(Service *s) { assert(s); - if (s->timeout_start_usec > 0) - service_arm_timer(s, s->timeout_start_usec); - + service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_start_usec)); service_set_state(s, SERVICE_RELOAD); } @@ -1911,6 +1908,7 @@ fail: } static void service_run_next_control(Service *s) { + usec_t timeout; int r; assert(s); @@ -1922,9 +1920,14 @@ static void service_run_next_control(Service *s) { s->control_command = s->control_command->command_next; service_unwatch_control_pid(s); + if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) + timeout = s->timeout_start_usec; + else + timeout = s->timeout_stop_usec; + r = service_spawn(s, s->control_command, - IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD) ? s->timeout_start_usec : s->timeout_stop_usec, + timeout, false, !s->permissions_start_only, !s->root_directory_start_only, @@ -2122,6 +2125,7 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) { unit_serialize_item(u, f, "main-pid-known", yes_no(s->main_pid_known)); unit_serialize_item(u, f, "bus-name-good", yes_no(s->bus_name_good)); + unit_serialize_item(u, f, "bus-name-owner", s->bus_name_owner); r = unit_serialize_item_escaped(u, f, "status-text", s->status_text); if (r < 0) @@ -2249,6 +2253,10 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, log_unit_debug(u, "Failed to parse bus-name-good value: %s", value); else s->bus_name_good = b; + } else if (streq(key, "bus-name-owner")) { + r = free_and_strdup(&s->bus_name_owner, value); + if (r < 0) + log_unit_error_errno(u, r, "Unable to deserialize current bus owner %s: %m", value); } else if (streq(key, "status-text")) { char *t; @@ -2356,6 +2364,7 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, else { asynchronous_close(s->stdin_fd); s->stdin_fd = fdset_remove(fds, fd); + s->exec_context.stdio_as_fds = true; } } else if (streq(key, "stdout-fd")) { int fd; @@ -2365,6 +2374,7 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, else { asynchronous_close(s->stdout_fd); s->stdout_fd = fdset_remove(fds, fd); + s->exec_context.stdio_as_fds = true; } } else if (streq(key, "stderr-fd")) { int fd; @@ -2374,6 +2384,7 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, else { asynchronous_close(s->stderr_fd); s->stderr_fd = fdset_remove(fds, fd); + s->exec_context.stdio_as_fds = true; } } else log_unit_debug(u, "Unknown serialization key: %s", key); @@ -2779,7 +2790,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { case SERVICE_START_POST: if (f != SERVICE_SUCCESS) { - service_enter_stop(s, f); + service_enter_signal(s, SERVICE_STOP_SIGTERM, f); break; } @@ -2869,12 +2880,16 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us case SERVICE_START_POST: log_unit_warning(UNIT(s), "Start-post operation timed out. Stopping."); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_RUNNING: + log_unit_warning(UNIT(s), "Service reached runtime time limit. Stopping."); service_enter_stop(s, SERVICE_FAILURE_TIMEOUT); break; case SERVICE_RELOAD: - log_unit_warning(UNIT(s), "Reload operation timed out. Stopping."); - service_unwatch_control_pid(s); + log_unit_warning(UNIT(s), "Reload operation timed out. Killing reload process."); service_kill_control_processes(s); s->reload_result = SERVICE_FAILURE_TIMEOUT; service_enter_running(s, SERVICE_SUCCESS); @@ -3096,17 +3111,21 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds) unit_add_to_dbus_queue(u); } -static int service_get_timeout(Unit *u, uint64_t *timeout) { +static int service_get_timeout(Unit *u, usec_t *timeout) { Service *s = SERVICE(u); + uint64_t t; int r; if (!s->timer_event_source) return 0; - r = sd_event_source_get_time(s->timer_event_source, timeout); + r = sd_event_source_get_time(s->timer_event_source, &t); if (r < 0) return r; + if (t == USEC_INFINITY) + return 0; + *timeout = t; return 1; } @@ -3134,6 +3153,13 @@ static void service_bus_name_owner_change( s->bus_name_good = !!new_owner; + /* Track the current owner, so we can reconstruct changes after a daemon reload */ + r = free_and_strdup(&s->bus_name_owner, new_owner); + if (r < 0) { + log_unit_error_errno(u, r, "Unable to set new bus name owner %s: %m", new_owner); + return; + } + if (s->type == SERVICE_DBUS) { /* service_enter_running() will figure out what to @@ -3187,7 +3213,7 @@ int service_set_socket_fd(Service *s, int fd, Socket *sock, bool selinux_context if (s->state != SERVICE_DEAD) return -EAGAIN; - if (getpeername_pretty(fd, &peer) >= 0) { + if (getpeername_pretty(fd, true, &peer) >= 0) { if (UNIT(s)->description) { _cleanup_free_ char *a; diff --git a/src/core/service.h b/src/core/service.h index d0faad88e0..24408940d4 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -118,6 +118,7 @@ struct Service { usec_t restart_usec; usec_t timeout_start_usec; usec_t timeout_stop_usec; + usec_t runtime_max_usec; dual_timestamp watchdog_timestamp; usec_t watchdog_usec; @@ -172,6 +173,7 @@ struct Service { bool reset_cpu_usage:1; char *bus_name; + char *bus_name_owner; /* unique name of the current owner */ char *status_text; int status_errno; diff --git a/src/core/smack-setup.c b/src/core/smack-setup.c index 0661ff9ecd..c9374ca0e8 100644 --- a/src/core/smack-setup.c +++ b/src/core/smack-setup.c @@ -197,6 +197,75 @@ static int write_cipso2_rules(const char* srcdir) { return r; } +static int write_netlabel_rules(const char* srcdir) { + _cleanup_fclose_ FILE *dst = NULL; + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *entry; + char buf[NAME_MAX]; + int dfd = -1; + int r = 0; + + dst = fopen("/sys/fs/smackfs/netlabel", "we"); + if (!dst) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to open /sys/fs/smackfs/netlabel: %m"); + return -errno; /* negative error */ + } + + /* write rules to dst from every file in the directory */ + dir = opendir(srcdir); + if (!dir) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to opendir %s: %m", srcdir); + return errno; /* positive on purpose */ + } + + dfd = dirfd(dir); + assert(dfd >= 0); + + FOREACH_DIRENT(entry, dir, return 0) { + int fd; + _cleanup_fclose_ FILE *policy = NULL; + + fd = openat(dfd, entry->d_name, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + if (r == 0) + r = -errno; + log_warning_errno(errno, "Failed to open %s: %m", entry->d_name); + continue; + } + + policy = fdopen(fd, "re"); + if (!policy) { + if (r == 0) + r = -errno; + safe_close(fd); + log_error_errno(errno, "Failed to open %s: %m", entry->d_name); + continue; + } + + /* load2 write rules in the kernel require a line buffered stream */ + FOREACH_LINE(buf, policy, + log_error_errno(errno, "Failed to read line from %s: %m", + entry->d_name)) { + if (!fputs(buf, dst)) { + if (r == 0) + r = -EINVAL; + log_error_errno(errno, "Failed to write line to /sys/fs/smackfs/netlabel"); + break; + } + if (fflush(dst)) { + if (r == 0) + r = -errno; + log_error_errno(errno, "Failed to flush writes to /sys/fs/smackfs/netlabel: %m"); + break; + } + } + } + + return r; +} + #endif int mac_smack_setup(bool *loaded_policy) { @@ -225,8 +294,18 @@ int mac_smack_setup(bool *loaded_policy) { #ifdef SMACK_RUN_LABEL r = write_string_file("/proc/self/attr/current", SMACK_RUN_LABEL, 0); - if (r) - log_warning_errno(r, "Failed to set SMACK label \"%s\" on self: %m", SMACK_RUN_LABEL); + if (r < 0) + log_warning_errno(r, "Failed to set SMACK label \"" SMACK_RUN_LABEL "\" on self: %m"); + r = write_string_file("/sys/fs/smackfs/ambient", SMACK_RUN_LABEL, 0); + if (r < 0) + log_warning_errno(r, "Failed to set SMACK ambient label \"" SMACK_RUN_LABEL "\": %m"); + r = write_string_file("/sys/fs/smackfs/netlabel", + "0.0.0.0/0 " SMACK_RUN_LABEL, 0); + if (r < 0) + log_warning_errno(r, "Failed to set SMACK netlabel rule \"0.0.0.0/0 " SMACK_RUN_LABEL "\": %m"); + r = write_string_file("/sys/fs/smackfs/netlabel", "127.0.0.1 -CIPSO", 0); + if (r < 0) + log_warning_errno(r, "Failed to set SMACK netlabel rule \"127.0.0.1 -CIPSO\": %m"); #endif r = write_cipso2_rules("/etc/smack/cipso.d/"); @@ -236,13 +315,29 @@ int mac_smack_setup(bool *loaded_policy) { return 0; case ENOENT: log_debug("Smack/CIPSO access rules directory '/etc/smack/cipso.d/' not found"); - return 0; + break; case 0: log_info("Successfully loaded Smack/CIPSO policies."); break; default: log_warning_errno(r, "Failed to load Smack/CIPSO access rules, ignoring: %m"); + break; + } + + r = write_netlabel_rules("/etc/smack/netlabel.d/"); + switch(r) { + case -ENOENT: + log_debug("Smack/CIPSO is not enabled in the kernel."); return 0; + case ENOENT: + log_debug("Smack network host rules directory '/etc/smack/netlabel.d/' not found"); + break; + case 0: + log_info("Successfully loaded Smack network host rules."); + break; + default: + log_warning_errno(r, "Failed to load Smack network host rules: %m, ignoring."); + break; } *loaded_policy = true; diff --git a/src/core/socket.c b/src/core/socket.c index 7beec3644e..f0d65577e3 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -28,9 +28,9 @@ #include <sys/epoll.h> #include <sys/stat.h> #include <unistd.h> +#include <linux/sctp.h> #include "sd-event.h" - #include "alloc-util.h" #include "bus-error.h" #include "bus-util.h" @@ -156,41 +156,41 @@ static void socket_done(Unit *u) { s->tcp_congestion = mfree(s->tcp_congestion); s->bind_to_device = mfree(s->bind_to_device); - free(s->smack); - free(s->smack_ip_in); - free(s->smack_ip_out); + s->smack = mfree(s->smack); + s->smack_ip_in = mfree(s->smack_ip_in); + s->smack_ip_out = mfree(s->smack_ip_out); strv_free(s->symlinks); - free(s->user); - free(s->group); + s->user = mfree(s->user); + s->group = mfree(s->group); + + s->fdname = mfree(s->fdname); s->timer_event_source = sd_event_source_unref(s->timer_event_source); } -static int socket_arm_timer(Socket *s) { +static int socket_arm_timer(Socket *s, usec_t usec) { int r; assert(s); - if (s->timeout_usec <= 0) { - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - return 0; - } - if (s->timer_event_source) { - r = sd_event_source_set_time(s->timer_event_source, now(CLOCK_MONOTONIC) + s->timeout_usec); + r = sd_event_source_set_time(s->timer_event_source, usec); if (r < 0) return r; return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); } + if (usec == USEC_INFINITY) + return 0; + r = sd_event_add_time( UNIT(s)->manager->event, &s->timer_event_source, CLOCK_MONOTONIC, - now(CLOCK_MONOTONIC) + s->timeout_usec, 0, + usec, 0, socket_dispatch_timer, s); if (r < 0) return r; @@ -875,8 +875,14 @@ static void socket_apply_socket_options(Socket *s, int fd) { if (s->no_delay) { int b = s->no_delay; - if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &b, sizeof(b)) < 0) - log_unit_warning_errno(UNIT(s), errno, "TCP_NODELAY failed: %m"); + + if (s->socket_protocol == IPPROTO_SCTP) { + if (setsockopt(fd, SOL_SCTP, SCTP_NODELAY, &b, sizeof(b)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SCTP_NODELAY failed: %m"); + } else { + if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &b, sizeof(b)) < 0) + log_unit_warning_errno(UNIT(s), errno, "TCP_NODELAY failed: %m"); + } } if (s->broadcast) { @@ -1486,7 +1492,7 @@ static int socket_coldplug(Unit *u) { if (r < 0) return r; - r = socket_arm_timer(s); + r = socket_arm_timer(s, usec_add(u->state_change_timestamp.monotonic, s->timeout_usec)); if (r < 0) return r; } @@ -1499,6 +1505,7 @@ static int socket_coldplug(Unit *u) { SOCKET_STOP_PRE, SOCKET_STOP_PRE_SIGTERM, SOCKET_STOP_PRE_SIGKILL)) { + r = socket_open_fds(s); if (r < 0) return r; @@ -1540,15 +1547,15 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { r = unit_setup_exec_runtime(UNIT(s)); if (r < 0) - goto fail; + return r; - r = socket_arm_timer(s); + r = socket_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); if (r < 0) - goto fail; + return r; r = unit_full_printf_strv(UNIT(s), c->argv, &argv); if (r < 0) - goto fail; + return r; exec_params.argv = argv; exec_params.environment = UNIT(s)->manager->environment; @@ -1565,26 +1572,22 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { s->exec_runtime, &pid); if (r < 0) - goto fail; + return r; r = unit_watch_pid(UNIT(s), pid); if (r < 0) /* FIXME: we need to do something here */ - goto fail; + return r; *_pid = pid; return 0; - -fail: - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - return r; } static int socket_chown(Socket *s, pid_t *_pid) { pid_t pid; int r; - r = socket_arm_timer(s); + r = socket_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); if (r < 0) goto fail; @@ -1727,7 +1730,7 @@ static void socket_enter_signal(Socket *s, SocketState state, SocketResult f) { goto fail; if (r > 0) { - r = socket_arm_timer(s); + r = socket_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); if (r < 0) goto fail; @@ -2767,17 +2770,21 @@ static int socket_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { return unit_kill_common(u, who, signo, -1, SOCKET(u)->control_pid, error); } -static int socket_get_timeout(Unit *u, uint64_t *timeout) { +static int socket_get_timeout(Unit *u, usec_t *timeout) { Socket *s = SOCKET(u); + usec_t t; int r; if (!s->timer_event_source) return 0; - r = sd_event_source_get_time(s->timer_event_source, timeout); + r = sd_event_source_get_time(s->timer_event_source, &t); if (r < 0) return r; + if (t == USEC_INFINITY) + return 0; + *timeout = t; return 1; } diff --git a/src/core/swap.c b/src/core/swap.c index 5568898bd7..b663a029eb 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -160,29 +160,27 @@ static void swap_done(Unit *u) { s->timer_event_source = sd_event_source_unref(s->timer_event_source); } -static int swap_arm_timer(Swap *s) { +static int swap_arm_timer(Swap *s, usec_t usec) { int r; assert(s); - if (s->timeout_usec <= 0) { - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - return 0; - } - if (s->timer_event_source) { - r = sd_event_source_set_time(s->timer_event_source, now(CLOCK_MONOTONIC) + s->timeout_usec); + r = sd_event_source_set_time(s->timer_event_source, usec); if (r < 0) return r; return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); } + if (usec == USEC_INFINITY) + return 0; + r = sd_event_add_time( UNIT(s)->manager->event, &s->timer_event_source, CLOCK_MONOTONIC, - now(CLOCK_MONOTONIC) + s->timeout_usec, 0, + usec, 0, swap_dispatch_timer, s); if (r < 0) return r; @@ -552,7 +550,7 @@ static int swap_coldplug(Unit *u) { if (r < 0) return r; - r = swap_arm_timer(s); + r = swap_arm_timer(s, usec_add(u->state_change_timestamp.monotonic, s->timeout_usec)); if (r < 0) return r; } @@ -633,7 +631,7 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { if (r < 0) goto fail; - r = swap_arm_timer(s); + r = swap_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); if (r < 0) goto fail; @@ -710,7 +708,7 @@ static void swap_enter_signal(Swap *s, SwapState state, SwapResult f) { goto fail; if (r > 0) { - r = swap_arm_timer(s); + r = swap_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); if (r < 0) goto fail; @@ -1398,17 +1396,21 @@ static int swap_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { return unit_kill_common(u, who, signo, -1, SWAP(u)->control_pid, error); } -static int swap_get_timeout(Unit *u, uint64_t *timeout) { +static int swap_get_timeout(Unit *u, usec_t *timeout) { Swap *s = SWAP(u); + usec_t t; int r; if (!s->timer_event_source) return 0; - r = sd_event_source_get_time(s->timer_event_source, timeout); + r = sd_event_source_get_time(s->timer_event_source, &t); if (r < 0) return r; + if (t == USEC_INFINITY) + return 0; + *timeout = t; return 1; } diff --git a/src/core/transaction.c b/src/core/transaction.c index 2f163190e9..0d53e4bac0 100644 --- a/src/core/transaction.c +++ b/src/core/transaction.c @@ -27,6 +27,7 @@ #include "bus-error.h" #include "terminal-util.h" #include "transaction.h" +#include "dbus-unit.h" static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies); @@ -860,30 +861,12 @@ int transaction_add_job_and_dependencies( if (!IN_SET(unit->load_state, UNIT_LOADED, UNIT_ERROR, UNIT_NOT_FOUND, UNIT_MASKED)) return sd_bus_error_setf(e, BUS_ERROR_LOAD_FAILED, "Unit %s is not loaded properly.", unit->id); - if (type != JOB_STOP && unit->load_state == UNIT_ERROR) { - if (unit->load_error == -ENOENT || unit->manager->test_run) - return sd_bus_error_setf(e, BUS_ERROR_LOAD_FAILED, - "Unit %s failed to load: %s.", - unit->id, - strerror(-unit->load_error)); - else - return sd_bus_error_setf(e, BUS_ERROR_LOAD_FAILED, - "Unit %s failed to load: %s. " - "See system logs and 'systemctl status %s' for details.", - unit->id, - strerror(-unit->load_error), - unit->id); + if (type != JOB_STOP) { + r = bus_unit_check_load_state(unit, e); + if (r < 0) + return r; } - if (type != JOB_STOP && unit->load_state == UNIT_NOT_FOUND) - return sd_bus_error_setf(e, BUS_ERROR_LOAD_FAILED, - "Unit %s failed to load: %s.", - unit->id, strerror(-unit->load_error)); - - if (type != JOB_STOP && unit->load_state == UNIT_MASKED) - return sd_bus_error_setf(e, BUS_ERROR_UNIT_MASKED, - "Unit %s is masked.", unit->id); - if (!unit_job_is_applicable(unit, type)) return sd_bus_error_setf(e, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, "Job type %s is not applicable for unit %s.", @@ -949,9 +932,10 @@ int transaction_add_job_and_dependencies( SET_FOREACH(dep, ret->unit->dependencies[UNIT_WANTS], i) { r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, false, false, false, ignore_order, e); if (r < 0) { + /* unit masked and unit not found are not considered as errors. */ log_unit_full(dep, - r == -EBADR /* unit masked */ ? LOG_DEBUG : LOG_WARNING, r, - "Cannot add dependency job, ignoring: %s", + r == -EBADR || r == -ENOENT ? LOG_DEBUG : LOG_WARNING, + r, "Cannot add dependency job, ignoring: %s", bus_error_message(e, r)); sd_bus_error_free(e); } @@ -1026,7 +1010,13 @@ int transaction_add_job_and_dependencies( if (type == JOB_RELOAD) { SET_FOREACH(dep, ret->unit->dependencies[UNIT_PROPAGATES_RELOAD_TO], i) { - r = transaction_add_job_and_dependencies(tr, JOB_RELOAD, dep, ret, false, false, false, ignore_order, e); + JobType nt; + + nt = job_type_collapse(JOB_TRY_RELOAD, dep); + if (nt == JOB_NOP) + continue; + + r = transaction_add_job_and_dependencies(tr, nt, dep, ret, false, false, false, ignore_order, e); if (r < 0) { log_unit_warning(dep, "Cannot add dependency reload job, ignoring: %s", diff --git a/src/core/unit.c b/src/core/unit.c index f935b6a601..0c1efc0e16 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -51,6 +51,7 @@ #include "set.h" #include "special.h" #include "stat-util.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "unit-name.h" @@ -98,6 +99,7 @@ Unit *unit_new(Manager *m, size_t size) { u->unit_file_preset = -1; u->on_failure_job_mode = JOB_REPLACE; u->cgroup_inotify_wd = -1; + u->job_timeout = USEC_INFINITY; RATELIMIT_INIT(u->auto_stop_ratelimit, 10 * USEC_PER_SEC, 16); @@ -868,6 +870,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { Iterator i; const char *prefix2; char + timestamp0[FORMAT_TIMESTAMP_MAX], timestamp1[FORMAT_TIMESTAMP_MAX], timestamp2[FORMAT_TIMESTAMP_MAX], timestamp3[FORMAT_TIMESTAMP_MAX], @@ -889,6 +892,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { "%s\tInstance: %s\n" "%s\tUnit Load State: %s\n" "%s\tUnit Active State: %s\n" + "%s\nState Change Timestamp: %s\n" "%s\tInactive Exit Timestamp: %s\n" "%s\tActive Enter Timestamp: %s\n" "%s\tActive Exit Timestamp: %s\n" @@ -906,6 +910,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { prefix, strna(u->instance), prefix, unit_load_state_to_string(u->load_state), prefix, unit_active_state_to_string(unit_active_state(u)), + prefix, strna(format_timestamp(timestamp0, sizeof(timestamp0), u->state_change_timestamp.realtime)), prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->inactive_exit_timestamp.realtime)), prefix, strna(format_timestamp(timestamp2, sizeof(timestamp2), u->active_enter_timestamp.realtime)), prefix, strna(format_timestamp(timestamp3, sizeof(timestamp3), u->active_exit_timestamp.realtime)), @@ -946,7 +951,7 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { STRV_FOREACH(j, u->dropin_paths) fprintf(f, "%s\tDropIn Path: %s\n", prefix, *j); - if (u->job_timeout > 0) + if (u->job_timeout != USEC_INFINITY) fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->job_timeout, 0)); if (u->job_timeout_action != FAILURE_ACTION_NONE) @@ -1412,7 +1417,7 @@ static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { format = unit_get_status_message_format(u, t); DISABLE_WARNING_FORMAT_NONLITERAL; - snprintf(buf, sizeof(buf), format, unit_description(u)); + xsprintf(buf, format, unit_description(u)); REENABLE_WARNING; mid = t == JOB_START ? SD_MESSAGE_UNIT_STARTING : @@ -1820,19 +1825,17 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su /* Update timestamps for state changes */ if (m->n_reloading <= 0) { - dual_timestamp ts; - - dual_timestamp_get(&ts); + dual_timestamp_get(&u->state_change_timestamp); if (UNIT_IS_INACTIVE_OR_FAILED(os) && !UNIT_IS_INACTIVE_OR_FAILED(ns)) - u->inactive_exit_timestamp = ts; + u->inactive_exit_timestamp = u->state_change_timestamp; else if (!UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_INACTIVE_OR_FAILED(ns)) - u->inactive_enter_timestamp = ts; + u->inactive_enter_timestamp = u->state_change_timestamp; if (!UNIT_IS_ACTIVE_OR_RELOADING(os) && UNIT_IS_ACTIVE_OR_RELOADING(ns)) - u->active_enter_timestamp = ts; + u->active_enter_timestamp = u->state_change_timestamp; else if (UNIT_IS_ACTIVE_OR_RELOADING(os) && !UNIT_IS_ACTIVE_OR_RELOADING(ns)) - u->active_exit_timestamp = ts; + u->active_exit_timestamp = u->state_change_timestamp; } /* Keep track of failed units */ @@ -1893,6 +1896,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su case JOB_RELOAD: case JOB_RELOAD_OR_START: + case JOB_TRY_RELOAD: if (u->job->state == JOB_RUNNING) { if (ns == UNIT_ACTIVE) @@ -2106,6 +2110,7 @@ bool unit_job_is_applicable(Unit *u, JobType j) { return unit_can_start(u); case JOB_RELOAD: + case JOB_TRY_RELOAD: return unit_can_reload(u); case JOB_RELOAD_OR_START: @@ -2550,10 +2555,13 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { } } + dual_timestamp_serialize(f, "state-change-timestamp", &u->state_change_timestamp); + dual_timestamp_serialize(f, "inactive-exit-timestamp", &u->inactive_exit_timestamp); dual_timestamp_serialize(f, "active-enter-timestamp", &u->active_enter_timestamp); dual_timestamp_serialize(f, "active-exit-timestamp", &u->active_exit_timestamp); dual_timestamp_serialize(f, "inactive-enter-timestamp", &u->inactive_enter_timestamp); + dual_timestamp_serialize(f, "condition-timestamp", &u->condition_timestamp); dual_timestamp_serialize(f, "assert-timestamp", &u->assert_timestamp); @@ -2692,7 +2700,7 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { /* End marker */ if (isempty(l)) - return 0; + break; k = strcspn(l, "="); @@ -2732,6 +2740,9 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { } else /* legacy for pre-44 */ log_unit_warning(u, "Update from too old systemd versions are unsupported, cannot deserialize job: %s", v); continue; + } else if (streq(l, "state-change-timestamp")) { + dual_timestamp_deserialize(v, &u->state_change_timestamp); + continue; } else if (streq(l, "inactive-exit-timestamp")) { dual_timestamp_deserialize(v, &u->inactive_exit_timestamp); continue; @@ -2838,6 +2849,15 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { log_unit_warning(u, "Failed to deserialize unit parameter '%s', ignoring.", l); } } + + /* Versions before 228 did not carry a state change timestamp. In this case, take the current time. This is + * useful, so that timeouts based on this timestamp don't trigger too early, and is in-line with the logic from + * before 228 where the base for timeouts was not peristet across reboots. */ + + if (!dual_timestamp_is_set(&u->state_change_timestamp)) + dual_timestamp_get(&u->state_change_timestamp); + + return 0; } int unit_add_node_link(Unit *u, const char *what, bool wants, UnitDependency dep) { @@ -3119,7 +3139,7 @@ int unit_kill_common( killed = true; } - if (r == 0 && !killed && IN_SET(who, KILL_ALL_FAIL, KILL_CONTROL_FAIL, KILL_ALL_FAIL)) + if (r == 0 && !killed && IN_SET(who, KILL_ALL_FAIL, KILL_CONTROL_FAIL)) return -ESRCH; return r; @@ -3231,7 +3251,7 @@ int unit_patch_contexts(Unit *u) { ec->no_new_privileges = true; if (ec->private_devices) - ec->capability_bounding_set_drop |= (uint64_t) 1ULL << (uint64_t) CAP_MKNOD; + ec->capability_bounding_set &= ~(UINT64_C(1) << CAP_MKNOD); } cc = unit_get_cgroup_context(u); diff --git a/src/core/unit.h b/src/core/unit.h index 3eb3484fb7..8712e03133 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -121,6 +121,10 @@ struct Unit { dual_timestamp condition_timestamp; dual_timestamp assert_timestamp; + /* Updated whenever the low-level state changes */ + dual_timestamp state_change_timestamp; + + /* Updated whenever the (high-level) active state enters or leaves the active or inactive states */ dual_timestamp inactive_exit_timestamp; dual_timestamp active_enter_timestamp; dual_timestamp active_exit_timestamp; @@ -375,7 +379,8 @@ struct UnitVTable { /* Called whenever CLOCK_REALTIME made a jump */ void (*time_change)(Unit *u); - int (*get_timeout)(Unit *u, uint64_t *timeout); + /* Returns the next timeout of a unit */ + int (*get_timeout)(Unit *u, usec_t *timeout); /* This is called for each unit type and should be used to * enumerate existing devices and load them. However, 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/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index 87b8b77f22..1468dc8df6 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -463,8 +463,6 @@ static int parse_fstab(bool initrd) { "x-systemd.automount\0"); if (initrd) post = SPECIAL_INITRD_FS_TARGET; - else if (mount_in_initrd(me)) - post = SPECIAL_INITRD_ROOT_FS_TARGET; else if (mount_is_network(me)) post = SPECIAL_REMOTE_FS_TARGET; else @@ -576,7 +574,7 @@ static int add_sysroot_usr_mount(void) { false, false, false, - SPECIAL_INITRD_ROOT_FS_TARGET, + SPECIAL_INITRD_FS_TARGET, "/proc/cmdline"); } 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/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index ce8cecc5cb..9086ca5dd7 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -515,14 +515,15 @@ static int add_boot(const char *what) { return log_error_errno(errno ?: EIO, "Failed to probe %s: %m", what); (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); - if (!streq(fstype, "vfat")) { + if (!streq_ptr(fstype, "vfat")) { log_debug("Partition for /boot is not a FAT filesystem, ignoring."); return 0; } + errno = 0; r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &uuid, NULL); if (r != 0) { - log_debug_errno(r, "Partition for /boot does not have a UUID, ignoring. %m"); + log_debug_errno(errno, "Partition for /boot does not have a UUID, ignoring."); return 0; } @@ -635,16 +636,19 @@ static int enumerate_partitions(dev_t devnum) { if (r == 1) return 0; /* no results */ else if (r == -2) { - log_warning("%s: probe gave ambiguous results, ignoring", node); + log_warning("%s: probe gave ambiguous results, ignoring.", node); return 0; } else if (r != 0) return log_error_errno(errno ?: EIO, "%s: failed to probe: %m", node); errno = 0; r = blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL); - if (r != 0) - return log_error_errno(errno ?: EIO, - "%s: failed to determine partition table type: %m", node); + if (r != 0) { + if (errno == 0) + return 0; /* No partition table found. */ + + return log_error_errno(errno, "%s: failed to determine partition table type: %m", node); + } /* We only do this all for GPT... */ if (!streq_ptr(pttype, "gpt")) { diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index d383041d39..84605fa267 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -212,7 +212,7 @@ try_dmi: unreliable enough, so let's not do any additional guesswork on top of that. - See the SMBIOS Specification 4.0 section 7.4.1 for + See the SMBIOS Specification 3.0 section 7.4.1 for details about the values listed here: https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.0.0.pdf diff --git a/src/import/aufs-util.c b/src/import/aufs-util.c index 82f519958c..b44dbb14ea 100644 --- a/src/import/aufs-util.c +++ b/src/import/aufs-util.c @@ -69,7 +69,7 @@ int aufs_resolve(const char *path) { errno = 0; r = nftw(path, nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL); if (r == FTW_STOP) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; return 0; } diff --git a/src/import/import-common.c b/src/import/import-common.c index a8551ca9e8..8a48bd7bf9 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -134,7 +134,7 @@ int import_fork_tar_x(const char *path, pid_t *ret) { if (unshare(CLONE_NEWNET) < 0) log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); - r = capability_bounding_set_drop(~retain, true); + r = capability_bounding_set_drop(retain, true); if (r < 0) log_error_errno(r, "Failed to drop capabilities, ignoring: %m"); @@ -208,7 +208,7 @@ int import_fork_tar_c(const char *path, pid_t *ret) { if (unshare(CLONE_NEWNET) < 0) log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); - r = capability_bounding_set_drop(~retain, true); + r = capability_bounding_set_drop(retain, true); if (r < 0) log_error_errno(r, "Failed to drop capabilities, ignoring: %m"); diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index 006791a542..f5fe165fa3 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -45,6 +45,8 @@ #include "sigbus.h" #include "util.h" +#define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC) + static char *arg_key_pem = NULL; static char *arg_cert_pem = NULL; static char *arg_trust_pem = NULL; @@ -181,11 +183,13 @@ static ssize_t request_reader_entries( } else if (r == 0) { if (m->follow) { - r = sd_journal_wait(m->journal, (uint64_t) -1); + r = sd_journal_wait(m->journal, (uint64_t) JOURNAL_WAIT_TIMEOUT); if (r < 0) { log_error_errno(r, "Couldn't wait for journal event: %m"); return MHD_CONTENT_READER_END_WITH_ERROR; } + if (r == SD_JOURNAL_NOP) + break; continue; } @@ -241,6 +245,8 @@ static ssize_t request_reader_entries( } n = m->size - pos; + if (n < 1) + return 0; if (n > max) n = max; @@ -694,7 +700,7 @@ static int request_handler_file( if (fstat(fd, &st) < 0) return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n"); - response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0); + response = MHD_create_response_from_fd_at_offset64(st.st_size, fd, 0); if (!response) return respond_oom(connection); @@ -834,7 +840,7 @@ static int request_handler( assert(method); if (!streq(method, "GET")) - return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE, + return mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, "Unsupported method.\n"); diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c index b2f5fbf6b4..cfe111fd91 100644 --- a/src/journal-remote/journal-remote.c +++ b/src/journal-remote/journal-remote.c @@ -150,7 +150,7 @@ static int spawn_curl(const char* url) { return r; } -static int spawn_getter(const char *getter, const char *url) { +static int spawn_getter(const char *getter) { int r; _cleanup_strv_free_ char **words = NULL; @@ -159,10 +159,6 @@ static int spawn_getter(const char *getter, const char *url) { if (r < 0) return log_error_errno(r, "Failed to split getter option: %m"); - r = strv_extend(&words, url); - if (r < 0) - return log_error_errno(r, "Failed to create command line: %m"); - r = spawn_child(words[0], words); if (r < 0) log_error_errno(r, "Failed to spawn getter %s: %m", getter); @@ -447,7 +443,7 @@ static int add_raw_socket(RemoteServer *s, int fd) { static int setup_raw_socket(RemoteServer *s, const char *address) { int fd; - fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM | SOCK_CLOEXEC); + fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM, SOCK_CLOEXEC); if (fd < 0) return fd; @@ -587,7 +583,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")) @@ -621,7 +617,7 @@ static int request_handler( if (r < 0) return code; } else { - r = getnameinfo_pretty(fd, &hostname); + r = getpeername_pretty(fd, false, &hostname); if (r < 0) return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Cannot check remote hostname"); @@ -765,7 +761,7 @@ static int setup_microhttpd_socket(RemoteServer *s, const char *trust) { int fd; - fd = make_socket_fd(LOG_DEBUG, address, SOCK_STREAM | SOCK_CLOEXEC); + fd = make_socket_fd(LOG_DEBUG, address, SOCK_STREAM, SOCK_CLOEXEC); if (fd < 0) return fd; @@ -879,7 +875,7 @@ static int remoteserver_init(RemoteServer *s, } else if (sd_is_socket(fd, AF_UNSPEC, 0, false)) { char *hostname; - r = getnameinfo_pretty(fd, &hostname); + r = getpeername_pretty(fd, false, &hostname); if (r < 0) return log_error_errno(r, "Failed to retrieve remote name: %m"); @@ -897,18 +893,32 @@ static int remoteserver_init(RemoteServer *s, fd); } - if (arg_url) { - const char *url, *hostname; + if (arg_getter) { + log_info("Spawning getter %s...", arg_getter); + fd = spawn_getter(arg_getter); + if (fd < 0) + return fd; + + r = add_source(s, fd, (char*) arg_output, false); + if (r < 0) + return r; + } - url = strjoina(arg_url, "/entries"); + if (arg_url) { + const char *url; + char *hostname, *p; - if (arg_getter) { - log_info("Spawning getter %s...", url); - fd = spawn_getter(arg_getter, url); - } else { - log_info("Spawning curl %s...", url); - fd = spawn_curl(url); + if (!strstr(arg_url, "/entries")) { + if (endswith(arg_url, "/")) + url = strjoina(arg_url, "entries"); + else + url = strjoina(arg_url, "/entries"); } + else + url = strdupa(arg_url); + + log_info("Spawning curl %s...", url); + fd = spawn_curl(url); if (fd < 0) return fd; @@ -917,7 +927,13 @@ static int remoteserver_init(RemoteServer *s, startswith(arg_url, "http://") ?: arg_url; - r = add_source(s, fd, (char*) hostname, false); + hostname = strdupa(hostname); + if ((p = strchr(hostname, '/'))) + *p = '\0'; + if ((p = strchr(hostname, ':'))) + *p = '\0'; + + r = add_source(s, fd, hostname, false); if (r < 0) return r; } @@ -1181,6 +1197,7 @@ static DEFINE_CONFIG_PARSE_ENUM(config_parse_write_split_mode, static int parse_config(void) { const ConfigTableItem items[] = { + { "Remote", "Seal", config_parse_bool, 0, &arg_seal }, { "Remote", "SplitMode", config_parse_write_split_mode, 0, &arg_split_mode }, { "Remote", "ServerKeyFile", config_parse_path, 0, &arg_key }, { "Remote", "ServerCertificateFile", config_parse_path, 0, &arg_cert }, diff --git a/src/journal-remote/journal-remote.conf.in b/src/journal-remote/journal-remote.conf.in index 3e32f34def..7122d63362 100644 --- a/src/journal-remote/journal-remote.conf.in +++ b/src/journal-remote/journal-remote.conf.in @@ -1,4 +1,5 @@ [Remote] +# Seal=false # SplitMode=host # ServerKeyFile=@CERTIFICATEROOT@/private/journal-remote.pem # ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-remote.pem diff --git a/src/journal-remote/journal-upload-journal.c b/src/journal-remote/journal-upload-journal.c index a6d7c3b7e8..f9d2385215 100644 --- a/src/journal-remote/journal-upload-journal.c +++ b/src/journal-remote/journal-upload-journal.c @@ -312,6 +312,9 @@ void close_journal_input(Uploader *u) { static int process_journal_input(Uploader *u, int skip) { int r; + if (u->uploading) + return 0; + r = sd_journal_next_skip(u->journal, skip); if (r < 0) return log_error_errno(r, "Failed to skip to next entry: %m"); @@ -349,10 +352,8 @@ static int dispatch_journal_input(sd_event_source *event, assert(u); - if (u->uploading) { - log_warning("dispatch_journal_input called when uploading, ignoring."); + if (u->uploading) return 0; - } log_debug("Detected journal input, checking for new data."); return check_journal_input(u); 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/cat.c b/src/journal/cat.c index 7fd4198df8..07c3df522c 100644 --- a/src/journal/cat.c +++ b/src/journal/cat.c @@ -34,7 +34,7 @@ #include "syslog-util.h" #include "util.h" -static char *arg_identifier = NULL; +static const char *arg_identifier = NULL; static int arg_priority = LOG_INFO; static bool arg_level_prefix = true; @@ -82,14 +82,10 @@ static int parse_argv(int argc, char *argv[]) { return version(); case 't': - free(arg_identifier); if (isempty(optarg)) arg_identifier = NULL; - else { - arg_identifier = strdup(optarg); - if (!arg_identifier) - return log_oom(); - } + else + arg_identifier = optarg; break; case 'p': diff --git a/src/journal/compress.c b/src/journal/compress.c index 1a3d2cdd80..78935fee74 100644 --- a/src/journal/compress.c +++ b/src/journal/compress.c @@ -58,7 +58,8 @@ static const char* const object_compressed_table[_OBJECT_COMPRESSED_MAX] = { DEFINE_STRING_TABLE_LOOKUP(object_compressed, int); -int compress_blob_xz(const void *src, uint64_t src_size, void *dst, size_t *dst_size) { +int compress_blob_xz(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size) { #ifdef HAVE_XZ static const lzma_options_lzma opt = { 1u << 20u, NULL, 0, LZMA_LC_DEFAULT, LZMA_LP_DEFAULT, @@ -74,6 +75,7 @@ int compress_blob_xz(const void *src, uint64_t src_size, void *dst, size_t *dst_ assert(src); assert(src_size > 0); assert(dst); + assert(dst_alloc_size > 0); assert(dst_size); /* Returns < 0 if we couldn't compress the data or the @@ -83,7 +85,7 @@ int compress_blob_xz(const void *src, uint64_t src_size, void *dst, size_t *dst_ return -ENOBUFS; ret = lzma_stream_buffer_encode((lzma_filter*) filters, LZMA_CHECK_NONE, NULL, - src, src_size, dst, &out_pos, src_size - 1); + src, src_size, dst, &out_pos, dst_alloc_size); if (ret != LZMA_OK) return -ENOBUFS; @@ -94,13 +96,15 @@ int compress_blob_xz(const void *src, uint64_t src_size, void *dst, size_t *dst_ #endif } -int compress_blob_lz4(const void *src, uint64_t src_size, void *dst, size_t *dst_size) { +int compress_blob_lz4(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size) { #ifdef HAVE_LZ4 int r; assert(src); assert(src_size > 0); assert(dst); + assert(dst_alloc_size > 0); assert(dst_size); /* Returns < 0 if we couldn't compress the data or the @@ -109,7 +113,7 @@ int compress_blob_lz4(const void *src, uint64_t src_size, void *dst, size_t *dst if (src_size < 9) return -ENOBUFS; - r = LZ4_compress_limitedOutput(src, dst + 8, src_size, src_size - 8 - 1); + r = LZ4_compress_limitedOutput(src, dst + 8, src_size, (int) dst_alloc_size - 8); if (r <= 0) return -ENOBUFS; @@ -306,6 +310,7 @@ int decompress_startswith_lz4(const void *src, uint64_t src_size, * prefix */ int r; + size_t size; assert(src); assert(src_size > 0); @@ -322,10 +327,18 @@ int decompress_startswith_lz4(const void *src, uint64_t src_size, r = LZ4_decompress_safe_partial(src + 8, *buffer, src_size - 8, prefix_len + 1, *buffer_size); + if (r >= 0) + size = (unsigned) r; + else { + /* lz4 always tries to decode full "sequence", so in + * pathological cases might need to decompress the + * full field. */ + r = decompress_blob_lz4(src, src_size, buffer, buffer_size, &size, 0); + if (r < 0) + return r; + } - if (r < 0) - return -EBADMSG; - if ((unsigned) r >= prefix_len + 1) + if (size >= prefix_len + 1) return memcmp(*buffer, prefix, prefix_len) == 0 && ((const uint8_t*) *buffer)[prefix_len] == extra; else @@ -438,7 +451,7 @@ int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) { _cleanup_(LZ4F_freeCompressionContextp) LZ4F_compressionContext_t ctx = NULL; _cleanup_free_ char *buf = NULL; char *src = NULL; - size_t size, n, total_in = 0, total_out = 0, offset = 0, frame_size; + size_t size, n, total_in = 0, total_out, offset = 0, frame_size; struct stat st; int r; static const LZ4F_compressOptions_t options = { @@ -461,7 +474,7 @@ int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes) { if (!buf) return -ENOMEM; - n = offset = LZ4F_compressBegin(ctx, buf, size, &preferences); + n = offset = total_out = LZ4F_compressBegin(ctx, buf, size, &preferences); if (LZ4F_isError(n)) return -EINVAL; @@ -599,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; @@ -726,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/compress.h b/src/journal/compress.h index 9a065eb763..758598730a 100644 --- a/src/journal/compress.h +++ b/src/journal/compress.h @@ -28,17 +28,20 @@ const char* object_compressed_to_string(int compression); int object_compressed_from_string(const char *compression); -int compress_blob_xz(const void *src, uint64_t src_size, void *dst, size_t *dst_size); -int compress_blob_lz4(const void *src, uint64_t src_size, void *dst, size_t *dst_size); +int compress_blob_xz(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size); +int compress_blob_lz4(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size); -static inline int compress_blob(const void *src, uint64_t src_size, void *dst, size_t *dst_size) { +static inline int compress_blob(const void *src, uint64_t src_size, + void *dst, size_t dst_alloc_size, size_t *dst_size) { int r; #ifdef HAVE_LZ4 - r = compress_blob_lz4(src, src_size, dst, dst_size); + r = compress_blob_lz4(src, src_size, dst, dst_alloc_size, dst_size); if (r == 0) return OBJECT_COMPRESSED_LZ4; #else - r = compress_blob_xz(src, src_size, dst, dst_size); + r = compress_blob_xz(src, src_size, dst, dst_alloc_size, dst_size); if (r == 0) return OBJECT_COMPRESSED_XZ; #endif diff --git a/src/journal/coredump.c b/src/journal/coredump.c index f750ddfcbd..8298b02439 100644 --- a/src/journal/coredump.c +++ b/src/journal/coredump.c @@ -297,7 +297,8 @@ static int save_external_coredump( const char *info[_INFO_LEN], uid_t uid, char **ret_filename, - int *ret_fd, + int *ret_node_fd, + int *ret_data_fd, uint64_t *ret_size) { _cleanup_free_ char *fn = NULL, *tmp = NULL; @@ -307,7 +308,8 @@ static int save_external_coredump( assert(info); assert(ret_filename); - assert(ret_fd); + assert(ret_node_fd); + assert(ret_data_fd); assert(ret_size); r = make_filename(info, &fn); @@ -386,11 +388,12 @@ static int save_external_coredump( unlink_noerrno(tmp); *ret_filename = fn_compressed; /* compressed */ - *ret_fd = fd; /* uncompressed */ + *ret_node_fd = fd_compressed; /* compressed */ + *ret_data_fd = fd; /* uncompressed */ *ret_size = (uint64_t) st.st_size; /* uncompressed */ fn_compressed = NULL; - fd = -1; + fd = fd_compressed = -1; return 0; @@ -400,12 +403,14 @@ static int save_external_coredump( uncompressed: #endif + r = fix_permissions(fd, tmp, fn, info, uid); if (r < 0) goto fail; *ret_filename = fn; - *ret_fd = fd; + *ret_data_fd = fd; + *ret_node_fd = -1; *ret_size = (uint64_t) st.st_size; fn = NULL; @@ -527,7 +532,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; @@ -554,7 +559,7 @@ int main(int argc, char* argv[]) { _cleanup_free_ char *exe = NULL, *comm = NULL, *filename = NULL; const char *info[_INFO_LEN]; - _cleanup_close_ int coredump_fd = -1; + _cleanup_close_ int coredump_fd = -1, coredump_node_fd = -1; struct iovec iovec[26]; uint64_t coredump_size; @@ -633,7 +638,7 @@ int main(int argc, char* argv[]) { if (arg_storage != COREDUMP_STORAGE_NONE) arg_storage = COREDUMP_STORAGE_EXTERNAL; - r = save_external_coredump(info, uid, &filename, &coredump_fd, &coredump_size); + r = save_external_coredump(info, uid, &filename, &coredump_node_fd, &coredump_fd, &coredump_size); if (r < 0) goto finish; @@ -789,13 +794,15 @@ int main(int argc, char* argv[]) { IOVEC_SET_STRING(iovec[j++], core_timestamp); IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1"); + + assert_cc(2 == LOG_CRIT); IOVEC_SET_STRING(iovec[j++], "PRIORITY=2"); /* Vacuum before we write anything again */ coredump_vacuum(-1, arg_keep_free, arg_max_use); /* Always stream the coredump to disk, if that's possible */ - r = save_external_coredump(info, uid, &filename, &coredump_fd, &coredump_size); + r = save_external_coredump(info, uid, &filename, &coredump_node_fd, &coredump_fd, &coredump_size); if (r < 0) /* skip whole core dumping part */ goto log; @@ -815,7 +822,7 @@ int main(int argc, char* argv[]) { } /* Vacuum again, but exclude the coredump we just created */ - coredump_vacuum(coredump_fd, arg_keep_free, arg_max_use); + coredump_vacuum(coredump_node_fd >= 0 ? coredump_node_fd : coredump_fd, arg_keep_free, arg_max_use); /* Now, let's drop privileges to become the user who owns the * segfaulted process and allocate the coredump memory under diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c index 6f09301521..2973176c8d 100644 --- a/src/journal/journal-file.c +++ b/src/journal/journal-file.c @@ -39,6 +39,7 @@ #include "lookup3.h" #include "parse-util.h" #include "random-util.h" +#include "sd-event.h" #include "string-util.h" #include "xattr-util.h" @@ -149,6 +150,17 @@ JournalFile* journal_file_close(JournalFile *f) { journal_file_append_tag(f); #endif + if (f->post_change_timer) { + int enabled; + + if (sd_event_source_get_enabled(f->post_change_timer, &enabled) >= 0) + if (enabled == SD_EVENT_ONESHOT) + journal_file_post_change(f); + + (void) sd_event_source_set_enabled(f->post_change_timer, SD_EVENT_OFF); + sd_event_source_unref(f->post_change_timer); + } + journal_file_set_offline(f); if (f->mmap && f->fd >= 0) @@ -1084,7 +1096,7 @@ static int journal_file_append_data( if (JOURNAL_FILE_COMPRESS(f) && size >= COMPRESSION_SIZE_THRESHOLD) { size_t rsize = 0; - compression = compress_blob(data, size, o->data.payload, &rsize); + compression = compress_blob(data, size, o->data.payload, size - 1, &rsize); if (compression >= 0) { o->object.size = htole64(offsetof(Object, data.payload) + rsize); @@ -1397,7 +1409,84 @@ void journal_file_post_change(JournalFile *f) { __sync_synchronize(); if (ftruncate(f->fd, f->last_stat.st_size) < 0) - log_error_errno(errno, "Failed to truncate file to its own size: %m"); + log_debug_errno(errno, "Failed to truncate file to its own size: %m"); +} + +static int post_change_thunk(sd_event_source *timer, uint64_t usec, void *userdata) { + assert(userdata); + + journal_file_post_change(userdata); + + return 1; +} + +static void schedule_post_change(JournalFile *f) { + sd_event_source *timer; + int enabled, r; + uint64_t now; + + assert(f); + assert(f->post_change_timer); + + timer = f->post_change_timer; + + r = sd_event_source_get_enabled(timer, &enabled); + if (r < 0) { + log_debug_errno(r, "Failed to get ftruncate timer state: %m"); + goto fail; + } + + if (enabled == SD_EVENT_ONESHOT) + return; + + r = sd_event_now(sd_event_source_get_event(timer), CLOCK_MONOTONIC, &now); + if (r < 0) { + log_debug_errno(r, "Failed to get clock's now for scheduling ftruncate: %m"); + goto fail; + } + + r = sd_event_source_set_time(timer, now+f->post_change_timer_period); + if (r < 0) { + log_debug_errno(r, "Failed to set time for scheduling ftruncate: %m"); + goto fail; + } + + r = sd_event_source_set_enabled(timer, SD_EVENT_ONESHOT); + if (r < 0) { + log_debug_errno(r, "Failed to enable scheduled ftruncate: %m"); + goto fail; + } + + return; + +fail: + /* On failure, let's simply post the change immediately. */ + journal_file_post_change(f); +} + +/* Enable coalesced change posting in a timer on the provided sd_event instance */ +int journal_file_enable_post_change_timer(JournalFile *f, sd_event *e, usec_t t) { + _cleanup_(sd_event_source_unrefp) sd_event_source *timer = NULL; + int r; + + assert(f); + assert_return(!f->post_change_timer, -EINVAL); + assert(e); + assert(t); + + r = sd_event_add_time(e, &timer, CLOCK_MONOTONIC, 0, 0, post_change_thunk, f); + if (r < 0) + return r; + + r = sd_event_source_set_enabled(timer, SD_EVENT_OFF); + if (r < 0) + return r; + + f->post_change_timer = timer; + timer = NULL; + f->post_change_timer_period = t; + + return r; } static int entry_item_cmp(const void *_a, const void *_b) { @@ -1465,7 +1554,10 @@ int journal_file_append_entry(JournalFile *f, const dual_timestamp *ts, const st if (mmap_cache_got_sigbus(f->mmap, f->fd)) r = -EIO; - journal_file_post_change(f); + if (f->post_change_timer) + schedule_post_change(f); + else + journal_file_post_change(f); return r; } @@ -2767,6 +2859,16 @@ int journal_file_open( goto fail; } + if (template && template->post_change_timer) { + r = journal_file_enable_post_change_timer( + f, + sd_event_source_get_event(template->post_change_timer), + template->post_change_timer_period); + + if (r < 0) + goto fail; + } + *ret = f; return 0; diff --git a/src/journal/journal-file.h b/src/journal/journal-file.h index 46c1f3278e..7970ebe738 100644 --- a/src/journal/journal-file.h +++ b/src/journal/journal-file.h @@ -33,6 +33,7 @@ #include "journal-def.h" #include "macro.h" #include "mmap-cache.h" +#include "sd-event.h" #include "sparse-endian.h" typedef struct JournalMetrics { @@ -101,6 +102,9 @@ typedef struct JournalFile { JournalMetrics metrics; MMapCache *mmap; + sd_event_source *post_change_timer; + usec_t post_change_timer_period; + OrderedHashmap *chain_cache; #if defined(HAVE_XZ) || defined(HAVE_LZ4) @@ -224,6 +228,7 @@ void journal_file_print_header(JournalFile *f); int journal_file_rotate(JournalFile **f, bool compress, bool seal); void journal_file_post_change(JournalFile *f); +int journal_file_enable_post_change_timer(JournalFile *f, sd_event *e, usec_t t); void journal_reset_metrics(JournalMetrics *m); void journal_default_metrics(JournalMetrics *m, int fd); diff --git a/src/journal/journal-internal.h b/src/journal/journal-internal.h index 9ff4fea7fb..a55d1bcc47 100644 --- a/src/journal/journal-internal.h +++ b/src/journal/journal-internal.h @@ -103,18 +103,29 @@ struct sd_journal { unsigned current_invalidate_counter, last_invalidate_counter; usec_t last_process_usec; + /* Iterating through unique fields and their data values */ char *unique_field; JournalFile *unique_file; uint64_t unique_offset; + /* Iterating through known fields */ + JournalFile *fields_file; + uint64_t fields_offset; + uint64_t fields_hash_table_index; + char *fields_buffer; + size_t fields_buffer_allocated; + int flags; - bool on_network; - bool no_new_files; - bool unique_file_lost; /* File we were iterating over got - removed, and there were no more - files, so sd_j_enumerate_unique - will return a value equal to 0. */ + bool on_network:1; + bool no_new_files:1; + bool unique_file_lost:1; /* File we were iterating over got + removed, and there were no more + files, so sd_j_enumerate_unique + will return a value equal to 0. */ + bool fields_file_lost:1; + bool has_runtime_files:1; + bool has_persistent_files:1; size_t data_threshold; diff --git a/src/journal/journal-send.c b/src/journal/journal-send.c index 44fa11a00e..def4caab92 100644 --- a/src/journal/journal-send.c +++ b/src/journal/journal-send.c @@ -372,6 +372,7 @@ static int fill_iovec_perror_and_send(const char *message, int skip, struct iove xsprintf(error, "ERRNO=%i", _saved_errno_); + assert_cc(3 == LOG_ERR); IOVEC_SET_STRING(iov[skip+0], "PRIORITY=3"); IOVEC_SET_STRING(iov[skip+1], buffer); IOVEC_SET_STRING(iov[skip+2], error); diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index d009b2e93b..20f7082175 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -69,6 +69,8 @@ #include "strv.h" #include "syslog-util.h" #include "terminal-util.h" +#include "udev.h" +#include "udev-util.h" #include "unit-name.h" #include "user-util.h" @@ -136,6 +138,8 @@ static enum { ACTION_SYNC, ACTION_ROTATE, ACTION_VACUUM, + ACTION_LIST_FIELDS, + ACTION_LIST_FIELD_NAMES, } arg_action = ACTION_SHOW; typedef struct BootId { @@ -145,6 +149,84 @@ typedef struct BootId { LIST_FIELDS(struct BootId, boot_list); } BootId; +static int add_matches_for_device(sd_journal *j, const char *devpath) { + int r; + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_udev_device_unref_ struct udev_device *device = NULL; + struct udev_device *d = NULL; + struct stat st; + + assert(j); + assert(devpath); + + if (!path_startswith(devpath, "/dev/")) { + log_error("Devpath does not start with /dev/"); + return -EINVAL; + } + + udev = udev_new(); + if (!udev) + return log_oom(); + + r = stat(devpath, &st); + if (r < 0) + log_error_errno(errno, "Couldn't stat file: %m"); + + d = device = udev_device_new_from_devnum(udev, S_ISBLK(st.st_mode) ? 'b' : 'c', st.st_rdev); + if (!device) + return log_error_errno(errno, "Failed to get udev device from devnum %u:%u: %m", major(st.st_rdev), minor(st.st_rdev)); + + while (d) { + _cleanup_free_ char *match = NULL; + const char *subsys, *sysname, *devnode; + + subsys = udev_device_get_subsystem(d); + if (!subsys) { + d = udev_device_get_parent(d); + continue; + } + + sysname = udev_device_get_sysname(d); + if (!sysname) { + d = udev_device_get_parent(d); + continue; + } + + match = strjoin("_KERNEL_DEVICE=+", subsys, ":", sysname, NULL); + if (!match) + return log_oom(); + + r = sd_journal_add_match(j, match, 0); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + + devnode = udev_device_get_devnode(d); + if (devnode) { + _cleanup_free_ char *match1 = NULL; + + r = stat(devnode, &st); + if (r < 0) + return log_error_errno(r, "Failed to stat() device node \"%s\": %m", devnode); + + r = asprintf(&match1, "_KERNEL_DEVICE=%c%u:%u", S_ISBLK(st.st_mode) ? 'b' : 'c', major(st.st_rdev), minor(st.st_rdev)); + if (r < 0) + return log_oom(); + + r = sd_journal_add_match(j, match1, 0); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + } + + d = udev_device_get_parent(d); + } + + r = add_match_this_boot(j, arg_machine); + if (r < 0) + return log_error_errno(r, "Failed to add match for the current boot: %m"); + + return 0; +} + static void pager_open_if_enabled(void) { if (arg_no_pager) @@ -244,6 +326,7 @@ static void help(void) { "\nCommands:\n" " -h --help Show this help text\n" " --version Show package version\n" + " -N --fields List all field names currently used\n" " -F --field=FIELD List all values that a specified field takes\n" " --disk-usage Show total disk usage of all journal files\n" " --vacuum-size=BYTES Reduce disk usage below specified size\n" @@ -340,6 +423,7 @@ static int parse_argv(int argc, char *argv[]) { { "unit", required_argument, NULL, 'u' }, { "user-unit", required_argument, NULL, ARG_USER_UNIT }, { "field", required_argument, NULL, 'F' }, + { "fields", no_argument, NULL, 'N' }, { "catalog", no_argument, NULL, 'x' }, { "list-catalog", no_argument, NULL, ARG_LIST_CATALOG }, { "dump-catalog", no_argument, NULL, ARG_DUMP_CATALOG }, @@ -361,7 +445,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:c:S:U:t:u:F:xrM:", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:c:S:U:t:u:NF:xrM:", options, NULL)) >= 0) switch (c) { @@ -698,9 +782,14 @@ static int parse_argv(int argc, char *argv[]) { break; case 'F': + arg_action = ACTION_LIST_FIELDS; arg_field = optarg; break; + case 'N': + arg_action = ACTION_LIST_FIELD_NAMES; + break; + case 'x': arg_catalog = true; break; @@ -825,13 +914,12 @@ static int add_matches(sd_journal *j, char **args) { have_term = false; } else if (path_is_absolute(*i)) { - _cleanup_free_ char *p, *t = NULL, *t2 = NULL; + _cleanup_free_ char *p, *t = NULL, *t2 = NULL, *interpreter = NULL; const char *path; - _cleanup_free_ char *interpreter = NULL; struct stat st; p = canonicalize_file_name(*i); - path = p ? p : *i; + path = p ?: *i; if (lstat(path, &st) < 0) return log_error_errno(errno, "Couldn't stat file: %m"); @@ -845,34 +933,37 @@ static int add_matches(sd_journal *j, char **args) { return log_oom(); t = strappend("_COMM=", comm); + if (!t) + return log_oom(); /* Append _EXE only if the interpreter is not a link. Otherwise, it might be outdated often. */ - if (lstat(interpreter, &st) == 0 && - !S_ISLNK(st.st_mode)) { + if (lstat(interpreter, &st) == 0 && !S_ISLNK(st.st_mode)) { t2 = strappend("_EXE=", interpreter); if (!t2) return log_oom(); } - } else + } else { t = strappend("_EXE=", path); - } else if (S_ISCHR(st.st_mode)) - (void) asprintf(&t, "_KERNEL_DEVICE=c%u:%u", major(st.st_rdev), minor(st.st_rdev)); - else if (S_ISBLK(st.st_mode)) - (void) asprintf(&t, "_KERNEL_DEVICE=b%u:%u", major(st.st_rdev), minor(st.st_rdev)); - else { + if (!t) + return log_oom(); + } + + r = sd_journal_add_match(j, t, 0); + + if (r >=0 && t2) + r = sd_journal_add_match(j, t2, 0); + + } else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) { + r = add_matches_for_device(j, path); + if (r < 0) + return r; + } else { log_error("File is neither a device node, nor regular file, nor executable: %s", *i); return -EINVAL; } - if (!t) - return log_oom(); - - r = sd_journal_add_match(j, t, 0); - if (t2) - r = sd_journal_add_match(j, t2, 0); have_term = true; - } else { r = sd_journal_add_match(j, *i, 0); have_term = true; @@ -1136,10 +1227,11 @@ static int add_boot(sd_journal *j) { const char *reason = (r == 0) ? "No such boot ID in journal" : strerror(-r); if (sd_id128_is_null(arg_boot_id)) - log_error("Failed to look up boot %+i: %s", arg_boot_offset, reason); + log_error("Data from the specified boot (%+i) is not available: %s", + arg_boot_offset, reason); else - log_error("Failed to look up boot ID "SD_ID128_FORMAT_STR"%+i: %s", - SD_ID128_FORMAT_VAL(arg_boot_id), arg_boot_offset, reason); + log_error("Data from the specified boot ("SD_ID128_FORMAT_STR") is not available: %s", + SD_ID128_FORMAT_VAL(arg_boot_id), reason); return r == 0 ? -ENODATA : r; } @@ -2002,6 +2094,8 @@ int main(int argc, char *argv[]) { case ACTION_DISK_USAGE: case ACTION_LIST_BOOTS: case ACTION_VACUUM: + case ACTION_LIST_FIELDS: + case ACTION_LIST_FIELD_NAMES: /* These ones require access to the journal files, continue below. */ break; @@ -2084,13 +2178,33 @@ int main(int argc, char *argv[]) { goto finish; } + case ACTION_LIST_FIELD_NAMES: { + const char *field; + + SD_JOURNAL_FOREACH_FIELD(j, field) { + printf("%s\n", field); + n_shown ++; + } + + r = 0; + goto finish; + } + case ACTION_SHOW: + case ACTION_LIST_FIELDS: break; default: assert_not_reached("Unknown action"); } + if (arg_boot_offset != 0 && + sd_journal_has_runtime_files(j) > 0 && + sd_journal_has_persistent_files(j) == 0) { + log_info("Specifying boot ID has no effect, no persistent journal was found"); + r = 0; + goto finish; + } /* add_boot() must be called first! * It may need to seek the journal to find parent boot IDs. */ r = add_boot(j); @@ -2131,10 +2245,12 @@ int main(int argc, char *argv[]) { log_debug("Journal filter: %s", filter); } - if (arg_field) { + if (arg_action == ACTION_LIST_FIELDS) { const void *data; size_t size; + assert(arg_field); + r = sd_journal_set_data_threshold(j, 0); if (r < 0) { log_error_errno(r, "Failed to unset data size threshold: %m"); @@ -2336,7 +2452,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-audit.c b/src/journal/journald-audit.c index 3c13fe0d67..28970131e7 100644 --- a/src/journal/journald-audit.c +++ b/src/journal/journald-audit.c @@ -397,8 +397,8 @@ static void process_audit_string(Server *s, int type, const char *data, size_t s sprintf(id_field, "_AUDIT_ID=%" PRIu64, id); IOVEC_SET_STRING(iov[n_iov++], id_field); - assert_cc(32 == LOG_AUTH); - IOVEC_SET_STRING(iov[n_iov++], "SYSLOG_FACILITY=32"); + assert_cc(4 == LOG_FAC(LOG_AUTH)); + IOVEC_SET_STRING(iov[n_iov++], "SYSLOG_FACILITY=4"); IOVEC_SET_STRING(iov[n_iov++], "SYSLOG_IDENTIFIER=audit"); type_name = audit_type_name_alloca(type); diff --git a/src/journal/journald-kmsg.c b/src/journal/journald-kmsg.c index e048e04716..1306ad6974 100644 --- a/src/journal/journald-kmsg.c +++ b/src/journal/journald-kmsg.c @@ -158,8 +158,10 @@ static void dev_kmsg_record(Server *s, const char *p, size_t l) { /* Did we lose any? */ if (serial > *s->kernel_seqnum) - server_driver_message(s, SD_MESSAGE_JOURNAL_MISSED, "Missed %"PRIu64" kernel messages", - serial - *s->kernel_seqnum); + server_driver_message(s, SD_MESSAGE_JOURNAL_MISSED, + LOG_MESSAGE("Missed %"PRIu64" kernel messages", + serial - *s->kernel_seqnum), + NULL); /* Make sure we never read this one again. Note that * we always store the next message serial we expect diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c index 371df5b37f..f80a6ebfe5 100644 --- a/src/journal/journald-native.c +++ b/src/journal/journald-native.c @@ -495,5 +495,9 @@ int server_open_native_socket(Server*s) { if (r < 0) return log_error_errno(r, "Failed to add native server fd to event loop: %m"); + r = sd_event_source_set_priority(s->native_event_source, SD_EVENT_PRIORITY_NORMAL+5); + if (r < 0) + return log_error_errno(r, "Failed to adjust native event source priority: %m"); + return 0; } diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index a8a9b72080..8ff7ef943b 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -67,9 +67,11 @@ #include "selinux-util.h" #include "signal-util.h" #include "socket-util.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "user-util.h" +#include "log.h" #define USER_JOURNALS_MAX 1024 @@ -82,6 +84,9 @@ #define NOTIFY_SNDBUF_SIZE (8*1024*1024) +/* The period to insert between posting changes for coalescing */ +#define POST_CHANGE_TIMER_INTERVAL_USEC (250*USEC_PER_MSEC) + static int determine_space_for( Server *s, JournalMetrics *metrics, @@ -142,7 +147,7 @@ static int determine_space_for( sum += (uint64_t) st.st_blocks * 512UL; } - /* If request, then let's bump the min_use limit to the + /* If requested, then let's bump the min_use limit to the * current usage on disk. We do this when starting up and * first opening the journal files. This way sudden spikes in * disk usage will not cause journald to vacuum files without @@ -162,19 +167,31 @@ static int determine_space_for( if (verbose) { char fb1[FORMAT_BYTES_MAX], fb2[FORMAT_BYTES_MAX], fb3[FORMAT_BYTES_MAX], fb4[FORMAT_BYTES_MAX], fb5[FORMAT_BYTES_MAX], fb6[FORMAT_BYTES_MAX]; + format_bytes(fb1, sizeof(fb1), sum); + format_bytes(fb2, sizeof(fb2), metrics->max_use); + format_bytes(fb3, sizeof(fb3), metrics->keep_free); + format_bytes(fb4, sizeof(fb4), ss_avail); + format_bytes(fb5, sizeof(fb5), s->cached_space_limit); + format_bytes(fb6, sizeof(fb6), s->cached_space_available); server_driver_message(s, SD_MESSAGE_JOURNAL_USAGE, - "%s (%s) is currently using %s.\n" - "Maximum allowed usage is set to %s.\n" - "Leaving at least %s free (of currently available %s of space).\n" - "Enforced usage limit is thus %s, of which %s are still available.", - name, path, - format_bytes(fb1, sizeof(fb1), sum), - format_bytes(fb2, sizeof(fb2), metrics->max_use), - format_bytes(fb3, sizeof(fb3), metrics->keep_free), - format_bytes(fb4, sizeof(fb4), ss_avail), - format_bytes(fb5, sizeof(fb5), s->cached_space_limit), - format_bytes(fb6, sizeof(fb6), s->cached_space_available)); + LOG_MESSAGE("%s (%s) is %s, max %s, %s free.", + name, path, fb1, fb5, fb6), + "JOURNAL_NAME=%s", name, + "JOURNAL_PATH=%s", path, + "CURRENT_USE=%"PRIu64, sum, + "CURRENT_USE_PRETTY=%s", fb1, + "MAX_USE=%"PRIu64, metrics->max_use, + "MAX_USE_PRETTY=%s", fb2, + "DISK_KEEP_FREE=%"PRIu64, metrics->keep_free, + "DISK_KEEP_FREE_PRETTY=%s", fb3, + "DISK_AVAILABLE=%"PRIu64, ss_avail, + "DISK_AVAILABLE_PRETTY=%s", fb4, + "LIMIT=%"PRIu64, s->cached_space_limit, + "LIMIT_PRETTY=%s", fb5, + "AVAILABLE=%"PRIu64, s->cached_space_available, + "AVAILABLE_PRETTY=%s", fb6, + NULL); } if (available) @@ -220,6 +237,39 @@ static void server_add_acls(JournalFile *f, uid_t uid) { #endif } +static int open_journal( + Server *s, + bool reliably, + const char *fname, + int flags, + bool seal, + JournalMetrics *metrics, + JournalFile *template, + JournalFile **ret) { + int r; + JournalFile *f; + + assert(s); + assert(fname); + assert(ret); + + if (reliably) + r = journal_file_open_reliably(fname, flags, 0640, s->compress, seal, metrics, s->mmap, template, &f); + else + r = journal_file_open(fname, flags, 0640, s->compress, seal, metrics, s->mmap, template, &f); + if (r < 0) + return r; + + r = journal_file_enable_post_change_timer(f, s->event, POST_CHANGE_TIMER_INTERVAL_USEC); + if (r < 0) { + journal_file_close(f); + return r; + } + + *ret = f; + return r; +} + static JournalFile* find_journal(Server *s, uid_t uid) { _cleanup_free_ char *p = NULL; int r; @@ -258,7 +308,7 @@ static JournalFile* find_journal(Server *s, uid_t uid) { journal_file_close(f); } - r = journal_file_open_reliably(p, O_RDWR|O_CREAT, 0640, s->compress, s->seal, &s->system_metrics, s->mmap, NULL, &f); + r = open_journal(s, true, p, O_RDWR|O_CREAT, s->seal, &s->system_metrics, NULL, &f); if (r < 0) return s->system_journal; @@ -808,37 +858,56 @@ static void dispatch_message_real( void server_driver_message(Server *s, sd_id128_t message_id, const char *format, ...) { char mid[11 + 32 + 1]; - char buffer[16 + LINE_MAX + 1]; - struct iovec iovec[N_IOVEC_META_FIELDS + 6]; - int n = 0; + struct iovec iovec[N_IOVEC_META_FIELDS + 5 + N_IOVEC_PAYLOAD_FIELDS]; + unsigned n = 0, m; + int r; va_list ap; struct ucred ucred = {}; assert(s); assert(format); + assert_cc(3 == LOG_FAC(LOG_DAEMON)); IOVEC_SET_STRING(iovec[n++], "SYSLOG_FACILITY=3"); IOVEC_SET_STRING(iovec[n++], "SYSLOG_IDENTIFIER=systemd-journald"); - IOVEC_SET_STRING(iovec[n++], "PRIORITY=6"); IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=driver"); - - memcpy(buffer, "MESSAGE=", 8); - va_start(ap, format); - vsnprintf(buffer + 8, sizeof(buffer) - 8, format, ap); - va_end(ap); - IOVEC_SET_STRING(iovec[n++], buffer); + assert_cc(6 == LOG_INFO); + IOVEC_SET_STRING(iovec[n++], "PRIORITY=6"); if (!sd_id128_equal(message_id, SD_ID128_NULL)) { snprintf(mid, sizeof(mid), LOG_MESSAGE_ID(message_id)); IOVEC_SET_STRING(iovec[n++], mid); } + m = n; + + va_start(ap, format); + r = log_format_iovec(iovec, ELEMENTSOF(iovec), &n, false, 0, format, ap); + /* Error handling below */ + va_end(ap); + ucred.pid = getpid(); ucred.uid = getuid(); ucred.gid = getgid(); - dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0); + if (r >= 0) + dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0); + + while (m < n) + free(iovec[m++].iov_base); + + if (r < 0) { + /* We failed to format the message. Emit a warning instead. */ + char buf[LINE_MAX]; + + xsprintf(buf, "MESSAGE=Entry printing failed: %s", strerror(-r)); + + n = 3; + IOVEC_SET_STRING(iovec[n++], "PRIORITY=4"); + IOVEC_SET_STRING(iovec[n++], buf); + dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0); + } } void server_dispatch_message( @@ -901,7 +970,8 @@ void server_dispatch_message( /* Write a suppression message if we suppressed something */ if (rl > 1) server_driver_message(s, SD_MESSAGE_JOURNAL_DROPPED, - "Suppressed %u messages from %s", rl - 1, path); + LOG_MESSAGE("Suppressed %u messages from %s", rl - 1, path), + NULL); finish: dispatch_message_real(s, iovec, n, m, ucred, tv, label, label_len, unit_id, priority, object_pid); @@ -930,7 +1000,7 @@ static int system_journal_open(Server *s, bool flush_requested) { (void) mkdir(fn, 0755); fn = strjoina(fn, "/system.journal"); - r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, s->compress, s->seal, &s->system_metrics, s->mmap, NULL, &s->system_journal); + r = open_journal(s, true, fn, O_RDWR|O_CREAT, s->seal, &s->system_metrics, NULL, &s->system_journal); if (r >= 0) { server_add_acls(s->system_journal, 0); (void) determine_space_for(s, &s->system_metrics, "/var/log/journal/", "System journal", true, true, NULL, NULL); @@ -953,7 +1023,7 @@ static int system_journal_open(Server *s, bool flush_requested) { * if it already exists, so that we can flush * it into the system journal */ - r = journal_file_open(fn, O_RDWR, 0640, s->compress, false, &s->runtime_metrics, s->mmap, NULL, &s->runtime_journal); + r = open_journal(s, false, fn, O_RDWR, false, &s->runtime_metrics, NULL, &s->runtime_journal); if (r < 0) { if (r != -ENOENT) log_warning_errno(r, "Failed to open runtime journal: %m"); @@ -970,7 +1040,7 @@ static int system_journal_open(Server *s, bool flush_requested) { (void) mkdir("/run/log/journal", 0755); (void) mkdir_parents(fn, 0750); - r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, s->compress, false, &s->runtime_metrics, s->mmap, NULL, &s->runtime_journal); + r = open_journal(s, true, fn, O_RDWR|O_CREAT, false, &s->runtime_metrics, NULL, &s->runtime_journal); if (r < 0) return log_error_errno(r, "Failed to open runtime journal: %m"); } @@ -1073,7 +1143,11 @@ finish: sd_journal_close(j); - server_driver_message(s, SD_ID128_NULL, "Time spent on flushing to /var is %s for %u entries.", format_timespan(ts, sizeof(ts), now(CLOCK_MONOTONIC) - start, 0), n); + server_driver_message(s, SD_ID128_NULL, + LOG_MESSAGE("Time spent on flushing to /var is %s for %u entries.", + format_timespan(ts, sizeof(ts), now(CLOCK_MONOTONIC) - start, 0), + n), + NULL); return r; } @@ -1330,7 +1404,7 @@ static int server_parse_proc_cmdline(Server *s) { p = line; for(;;) { - _cleanup_free_ char *word; + _cleanup_free_ char *word = NULL; r = extract_first_word(&p, &word, NULL, 0); if (r < 0) diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h index 1822765228..49bbee0646 100644 --- a/src/journal/journald-server.h +++ b/src/journal/journald-server.h @@ -157,9 +157,10 @@ struct Server { #define N_IOVEC_KERNEL_FIELDS 64 #define N_IOVEC_UDEV_FIELDS 32 #define N_IOVEC_OBJECT_FIELDS 12 +#define N_IOVEC_PAYLOAD_FIELDS 15 void server_dispatch_message(Server *s, struct iovec *iovec, unsigned n, unsigned m, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len, const char *unit_id, int priority, pid_t object_pid); -void server_driver_message(Server *s, sd_id128_t message_id, const char *format, ...) _printf_(3,4); +void server_driver_message(Server *s, sd_id128_t message_id, const char *format, ...) _printf_(3,0) _sentinel_; /* gperf lookup function */ const struct ConfigPerfItem* journald_gperf_lookup(const char *key, unsigned length); diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c index 131fcdac42..90884b6929 100644 --- a/src/journal/journald-stream.c +++ b/src/journal/journald-stream.c @@ -733,7 +733,7 @@ int server_open_stdout_socket(Server *s) { if (r < 0) return log_error_errno(r, "Failed to add stdout server fd to event source: %m"); - r = sd_event_source_set_priority(s->stdout_event_source, SD_EVENT_PRIORITY_NORMAL+10); + r = sd_event_source_set_priority(s->stdout_event_source, SD_EVENT_PRIORITY_NORMAL+5); if (r < 0) return log_error_errno(r, "Failed to adjust priority of stdout server event source: %m"); diff --git a/src/journal/journald-syslog.c b/src/journal/journald-syslog.c index cfc50d889b..9f2ccdcc77 100644 --- a/src/journal/journald-syslog.c +++ b/src/journal/journald-syslog.c @@ -326,7 +326,7 @@ void server_process_syslog_message( size_t label_len) { char syslog_priority[sizeof("PRIORITY=") + DECIMAL_STR_MAX(int)], - syslog_facility[sizeof("SYSLOG_FACILITY") + DECIMAL_STR_MAX(int)]; + syslog_facility[sizeof("SYSLOG_FACILITY=") + DECIMAL_STR_MAX(int)]; const char *message = NULL, *syslog_identifier = NULL, *syslog_pid = NULL; struct iovec iovec[N_IOVEC_META_FIELDS + 6]; unsigned n = 0; @@ -357,11 +357,11 @@ void server_process_syslog_message( IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=syslog"); - sprintf(syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK); + xsprintf(syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK); IOVEC_SET_STRING(iovec[n++], syslog_priority); if (priority & LOG_FACMASK) { - sprintf(syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)); + xsprintf(syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)); IOVEC_SET_STRING(iovec[n++], syslog_facility); } @@ -430,6 +430,10 @@ int server_open_syslog_socket(Server *s) { if (r < 0) return log_error_errno(r, "Failed to add syslog server fd to event loop: %m"); + r = sd_event_source_set_priority(s->syslog_event_source, SD_EVENT_PRIORITY_NORMAL+5); + if (r < 0) + return log_error_errno(r, "Failed to adjust syslog event source priority: %m"); + return 0; } @@ -444,7 +448,10 @@ void server_maybe_warn_forward_syslog_missed(Server *s) { if (s->last_warn_forward_syslog_missed + WARN_FORWARD_SYSLOG_MISSED_USEC > n) return; - server_driver_message(s, SD_MESSAGE_FORWARD_SYSLOG_MISSED, "Forwarding to syslog missed %u messages.", s->n_forward_syslog_missed); + server_driver_message(s, SD_MESSAGE_FORWARD_SYSLOG_MISSED, + LOG_MESSAGE("Forwarding to syslog missed %u messages.", + s->n_forward_syslog_missed), + NULL); s->n_forward_syslog_missed = 0; s->last_warn_forward_syslog_missed = n; diff --git a/src/journal/journald.c b/src/journal/journald.c index b9f5c099e1..293b788d03 100644 --- a/src/journal/journald.c +++ b/src/journal/journald.c @@ -58,7 +58,9 @@ int main(int argc, char *argv[]) { server_flush_dev_kmsg(&server); log_debug("systemd-journald running as pid "PID_FMT, getpid()); - server_driver_message(&server, SD_MESSAGE_JOURNAL_START, "Journal started"); + server_driver_message(&server, SD_MESSAGE_JOURNAL_START, + LOG_MESSAGE("Journal started"), + NULL); for (;;) { usec_t t = USEC_INFINITY, n; @@ -109,7 +111,9 @@ int main(int argc, char *argv[]) { } log_debug("systemd-journald stopped as pid "PID_FMT, getpid()); - server_driver_message(&server, SD_MESSAGE_JOURNAL_STOP, "Journal stopped"); + server_driver_message(&server, SD_MESSAGE_JOURNAL_STOP, + LOG_MESSAGE("Journal stopped"), + NULL); finish: server_done(&server); diff --git a/src/journal/mmap-cache.c b/src/journal/mmap-cache.c index eb4b092e80..a69672ce21 100644 --- a/src/journal/mmap-cache.c +++ b/src/journal/mmap-cache.c @@ -40,9 +40,9 @@ typedef struct FileDescriptor FileDescriptor; struct Window { MMapCache *cache; - bool invalidated; - bool keep_always; - bool in_unused; + bool invalidated:1; + bool keep_always:1; + bool in_unused:1; int prot; void *ptr; @@ -78,7 +78,6 @@ struct MMapCache { unsigned n_hit, n_missed; - Hashmap *fds; Context *contexts[MMAP_CACHE_MAX_CONTEXTS]; @@ -408,7 +407,7 @@ static int try_context( if (c->window->fd->sigbus) return -EIO; - c->window->keep_always |= keep_always; + c->window->keep_always = c->window->keep_always || keep_always; *ret = (uint8_t*) c->window->ptr + (offset - c->window->offset); return 1; @@ -454,7 +453,7 @@ static int find_mmap( return -ENOMEM; context_attach_window(c, w); - w->keep_always += keep_always; + w->keep_always = w->keep_always || keep_always; *ret = (uint8_t*) w->ptr + (offset - w->offset); return 1; diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c index 5cde7f17f7..7a3aaf0cab 100644 --- a/src/journal/sd-journal.c +++ b/src/journal/sd-journal.c @@ -1292,6 +1292,12 @@ static int add_file(sd_journal *j, const char *prefix, const char *filename) { return 0; path = strjoina(prefix, "/", filename); + + if (!j->has_runtime_files && path_startswith(path, "/run/log/journal")) + j->has_runtime_files = true; + else if (!j->has_persistent_files && path_startswith(path, "/var/log/journal")) + j->has_persistent_files = true; + return add_any_file(j, path); } @@ -1332,6 +1338,13 @@ static void remove_file_real(sd_journal *j, JournalFile *f) { j->unique_file_lost = true; } + if (j->fields_file == f) { + j->fields_file = ordered_hashmap_next(j->files, j->fields_file->path); + j->fields_offset = 0; + if (!j->fields_file) + j->fields_file_lost = true; + } + journal_file_close(f); j->current_invalidate_counter ++; @@ -1800,6 +1813,7 @@ _public_ void sd_journal_close(sd_journal *j) { free(j->path); free(j->prefix); free(j->unique_field); + free(j->fields_buffer); free(j); } @@ -1940,10 +1954,14 @@ _public_ int sd_journal_get_data(sd_journal *j, const char *field, const void ** compression = o->object.flags & OBJECT_COMPRESSION_MASK; if (compression) { #if defined(HAVE_XZ) || defined(HAVE_LZ4) - if (decompress_startswith(compression, + r = decompress_startswith(compression, o->data.payload, l, &f->compress_buffer, &f->compress_buffer_size, - field, field_length, '=')) { + field, field_length, '='); + if (r < 0) + log_debug_errno(r, "Cannot decompress %s object of length %zu at offset "OFSfmt": %m", + object_compressed_to_string(compression), l, p); + else if (r > 0) { size_t rsize; @@ -2502,24 +2520,20 @@ _public_ int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_ * traversed files. */ found = false; ORDERED_HASHMAP_FOREACH(of, j->files, i) { - Object *oo; - uint64_t op; - if (of == j->unique_file) break; - /* Skip this file it didn't have any fields - * indexed */ - if (JOURNAL_HEADER_CONTAINS(of->header, n_fields) && - le64toh(of->header->n_fields) <= 0) + /* Skip this file it didn't have any fields indexed */ + if (JOURNAL_HEADER_CONTAINS(of->header, n_fields) && le64toh(of->header->n_fields) <= 0) continue; - r = journal_file_find_data_object_with_hash(of, odata, ol, le64toh(o->data.hash), &oo, &op); + r = journal_file_find_data_object_with_hash(of, odata, ol, le64toh(o->data.hash), NULL, NULL); if (r < 0) return r; - - if (r > 0) + if (r > 0) { found = true; + break; + } } if (found) @@ -2542,6 +2556,154 @@ _public_ void sd_journal_restart_unique(sd_journal *j) { j->unique_file_lost = false; } +_public_ int sd_journal_enumerate_fields(sd_journal *j, const char **field) { + int r; + + assert_return(j, -EINVAL); + assert_return(!journal_pid_changed(j), -ECHILD); + assert_return(field, -EINVAL); + + if (!j->fields_file) { + if (j->fields_file_lost) + return 0; + + j->fields_file = ordered_hashmap_first(j->files); + if (!j->fields_file) + return 0; + + j->fields_hash_table_index = 0; + j->fields_offset = 0; + } + + for (;;) { + JournalFile *f, *of; + Iterator i; + uint64_t m; + Object *o; + size_t sz; + bool found; + + f = j->fields_file; + + if (j->fields_offset == 0) { + bool eof = false; + + /* We are not yet positioned at any field. Let's pick the first one */ + r = journal_file_map_field_hash_table(f); + if (r < 0) + return r; + + m = le64toh(f->header->field_hash_table_size) / sizeof(HashItem); + for (;;) { + if (j->fields_hash_table_index >= m) { + /* Reached the end of the hash table, go to the next file. */ + eof = true; + break; + } + + j->fields_offset = le64toh(f->field_hash_table[j->fields_hash_table_index].head_hash_offset); + + if (j->fields_offset != 0) + break; + + /* Empty hash table bucket, go to next one */ + j->fields_hash_table_index++; + } + + if (eof) { + /* Proceed with next file */ + j->fields_file = ordered_hashmap_next(j->files, f->path); + if (!j->fields_file) { + *field = NULL; + return 0; + } + + j->fields_offset = 0; + j->fields_hash_table_index = 0; + continue; + } + + } else { + /* We are already positioned at a field. If so, let's figure out the next field from it */ + + r = journal_file_move_to_object(f, OBJECT_FIELD, j->fields_offset, &o); + if (r < 0) + return r; + + j->fields_offset = le64toh(o->field.next_hash_offset); + if (j->fields_offset == 0) { + /* Reached the end of the hash table chain */ + j->fields_hash_table_index++; + continue; + } + } + + /* We use OBJECT_UNUSED here, so that the iteator below doesn't remove our mmap window */ + r = journal_file_move_to_object(f, OBJECT_UNUSED, j->fields_offset, &o); + if (r < 0) + return r; + + /* Because we used OBJECT_UNUSED above, we need to do our type check manually */ + if (o->object.type != OBJECT_FIELD) { + log_debug("%s:offset " OFSfmt ": object has type %i, expected %i", f->path, j->fields_offset, o->object.type, OBJECT_FIELD); + return -EBADMSG; + } + + sz = le64toh(o->object.size) - offsetof(Object, field.payload); + + /* Let's see if we already returned this field name before. */ + found = false; + ORDERED_HASHMAP_FOREACH(of, j->files, i) { + if (of == f) + break; + + /* Skip this file it didn't have any fields indexed */ + if (JOURNAL_HEADER_CONTAINS(of->header, n_fields) && le64toh(of->header->n_fields) <= 0) + continue; + + r = journal_file_find_field_object_with_hash(of, o->field.payload, sz, le64toh(o->field.hash), NULL, NULL); + if (r < 0) + return r; + if (r > 0) { + found = true; + break; + } + } + + if (found) + continue; + + /* Check if this is really a valid string containing no NUL byte */ + if (memchr(o->field.payload, 0, sz)) + return -EBADMSG; + + if (sz > j->data_threshold) + sz = j->data_threshold; + + if (!GREEDY_REALLOC(j->fields_buffer, j->fields_buffer_allocated, sz + 1)) + return -ENOMEM; + + memcpy(j->fields_buffer, o->field.payload, sz); + j->fields_buffer[sz] = 0; + + if (!field_is_valid(j->fields_buffer)) + return -EBADMSG; + + *field = j->fields_buffer; + return 1; + } +} + +_public_ void sd_journal_restart_fields(sd_journal *j) { + if (!j) + return; + + j->fields_file = NULL; + j->fields_hash_table_index = 0; + j->fields_offset = 0; + j->fields_file_lost = false; +} + _public_ int sd_journal_reliable_fd(sd_journal *j) { assert_return(j, -EINVAL); assert_return(!journal_pid_changed(j), -ECHILD); @@ -2626,3 +2788,15 @@ _public_ int sd_journal_get_data_threshold(sd_journal *j, size_t *sz) { *sz = j->data_threshold; return 0; } + +_public_ int sd_journal_has_runtime_files(sd_journal *j) { + assert_return(j, -EINVAL); + + return j->has_runtime_files; +} + +_public_ int sd_journal_has_persistent_files(sd_journal *j) { + assert_return(j, -EINVAL); + + return j->has_persistent_files; +} diff --git a/src/journal/test-compress-benchmark.c b/src/journal/test-compress-benchmark.c index 93ea9c6318..baed0d82a4 100644 --- a/src/journal/test-compress-benchmark.c +++ b/src/journal/test-compress-benchmark.c @@ -27,7 +27,8 @@ #include "string-util.h" #include "util.h" -typedef int (compress_t)(const void *src, uint64_t src_size, void *dst, size_t *dst_size); +typedef int (compress_t)(const void *src, uint64_t src_size, void *dst, + size_t dst_alloc_size, size_t *dst_size); typedef int (decompress_t)(const void *src, uint64_t src_size, void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max); @@ -111,8 +112,8 @@ static void test_compress_decompress(const char* label, const char* type, memzero(buf, MIN(size + 1000, MAX_SIZE)); - r = compress(text, size, buf, &j); - /* assume compression must be successful except for small inputs */ + r = compress(text, size, buf, size, &j); + /* assume compression must be successful except for small or random inputs */ assert_se(r == 0 || (size < 2048 && r == -ENOBUFS) || streq(type, "random")); /* check for overwrites */ diff --git a/src/journal/test-compress.c b/src/journal/test-compress.c index b9d90a8988..68c9a4d76c 100644 --- a/src/journal/test-compress.c +++ b/src/journal/test-compress.c @@ -17,6 +17,10 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#ifdef HAVE_LZ4 +#include <lz4.h> +#endif + #include "alloc-util.h" #include "compress.h" #include "fd-util.h" @@ -38,7 +42,7 @@ #endif typedef int (compress_blob_t)(const void *src, uint64_t src_size, - void *dst, size_t *dst_size); + void *dst, size_t dst_alloc_size, size_t *dst_size); typedef int (decompress_blob_t)(const void *src, uint64_t src_size, void **dst, size_t *dst_alloc_size, size_t* dst_size, size_t dst_max); @@ -57,15 +61,14 @@ static void test_compress_decompress(int compression, size_t data_len, bool may_fail) { char compressed[512]; - size_t csize = 512; - size_t usize = 0; + size_t csize, usize = 0; _cleanup_free_ char *decompressed = NULL; int r; log_info("/* testing %s %s blob compression/decompression */", object_compressed_to_string(compression), data); - r = compress(data, data_len, compressed, &csize); + r = compress(data, data_len, compressed, sizeof(compressed), &csize); if (r == -ENOBUFS) { log_info_errno(r, "compression failed: %m"); assert_se(may_fail); @@ -101,43 +104,45 @@ static void test_decompress_startswith(int compression, size_t data_len, bool may_fail) { - char compressed[512]; - size_t csize = 512; - size_t usize = 0; - _cleanup_free_ char *decompressed = NULL; + char *compressed; + _cleanup_free_ char *compressed1 = NULL, *compressed2 = NULL, *decompressed = NULL; + size_t csize, usize = 0, len; int r; - log_info("/* testing decompress_startswith with %s on %s text*/", + log_info("/* testing decompress_startswith with %s on %.20s text*/", object_compressed_to_string(compression), data); - r = compress(data, data_len, compressed, &csize); +#define BUFSIZE_1 512 +#define BUFSIZE_2 20000 + + compressed = compressed1 = malloc(BUFSIZE_1); + assert_se(compressed1); + r = compress(data, data_len, compressed, BUFSIZE_1, &csize); if (r == -ENOBUFS) { log_info_errno(r, "compression failed: %m"); assert_se(may_fail); - return; + + compressed = compressed2 = malloc(BUFSIZE_2); + assert_se(compressed2); + r = compress(data, data_len, compressed, BUFSIZE_2, &csize); + assert(r == 0); } assert_se(r == 0); - assert_se(decompress_sw(compressed, - csize, - (void **) &decompressed, - &usize, - data, strlen(data), '\0') > 0); - assert_se(decompress_sw(compressed, - csize, - (void **) &decompressed, - &usize, - data, strlen(data), 'w') == 0); - assert_se(decompress_sw(compressed, - csize, - (void **) &decompressed, - &usize, - "barbarbar", 9, ' ') == 0); - assert_se(decompress_sw(compressed, - csize, - (void **) &decompressed, - &usize, - data, strlen(data), '\0') > 0); + len = strlen(data); + + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, '\0'); + assert_se(r > 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, 'w'); + assert_se(r == 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, "barbarbar", 9, ' '); + assert_se(r == 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len - 1, data[len-1]); + assert_se(r > 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len - 1, 'w'); + assert_se(r == 0); + r = decompress_sw(compressed, csize, (void **) &decompressed, &usize, data, len, '\0'); + assert_se(r > 0); } static void test_compress_stream(int compression, @@ -199,6 +204,44 @@ static void test_compress_stream(int compression, assert_se(unlink(pattern2) == 0); } +#ifdef HAVE_LZ4 +static void test_lz4_decompress_partial(void) { + char buf[20000]; + size_t buf_size = sizeof(buf), compressed; + int r; + _cleanup_free_ char *huge = NULL; + +#define HUGE_SIZE (4096*1024) + huge = malloc(HUGE_SIZE); + memset(huge, 'x', HUGE_SIZE); + memcpy(huge, "HUGE=", 5); + + r = LZ4_compress_limitedOutput(huge, buf, HUGE_SIZE, buf_size); + assert_se(r >= 0); + compressed = r; + log_info("Compressed %i → %zu", HUGE_SIZE, compressed); + + r = LZ4_decompress_safe(buf, huge, r, HUGE_SIZE); + assert_se(r >= 0); + log_info("Decompressed → %i", r); + + r = LZ4_decompress_safe_partial(buf, huge, + compressed, + 12, HUGE_SIZE); + assert_se(r >= 0); + log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE, r); + + /* We expect this to fail, because that's how current lz4 works. If this + * call succeeds, then lz4 has been fixed, and we need to change our code. + */ + r = LZ4_decompress_safe_partial(buf, huge, + compressed, + 12, HUGE_SIZE-1); + assert_se(r < 0); + log_info("Decompressed partial %i/%i → %i", 12, HUGE_SIZE-1, r); +} +#endif + int main(int argc, char *argv[]) { const char text[] = "text\0foofoofoofoo AAAA aaaaaaaaa ghost busters barbarbar FFF" @@ -206,6 +249,11 @@ int main(int argc, char *argv[]) { char data[512] = "random\0"; + char huge[4096*1024]; + memset(huge, 'x', sizeof(huge)); + memcpy(huge, "HUGE=", 5); + char_array_0(huge); + log_set_max_level(LOG_DEBUG); random_bytes(data + 7, sizeof(data) - 7); @@ -215,12 +263,17 @@ int main(int argc, char *argv[]) { text, sizeof(text), false); test_compress_decompress(OBJECT_COMPRESSED_XZ, compress_blob_xz, decompress_blob_xz, data, sizeof(data), true); + test_decompress_startswith(OBJECT_COMPRESSED_XZ, compress_blob_xz, decompress_startswith_xz, text, sizeof(text), false); test_decompress_startswith(OBJECT_COMPRESSED_XZ, compress_blob_xz, decompress_startswith_xz, data, sizeof(data), true); + test_decompress_startswith(OBJECT_COMPRESSED_XZ, + compress_blob_xz, decompress_startswith_xz, + huge, sizeof(huge), true); + test_compress_stream(OBJECT_COMPRESSED_XZ, "xzcat", compress_stream_xz, decompress_stream_xz, argv[0]); #else @@ -232,15 +285,21 @@ int main(int argc, char *argv[]) { text, sizeof(text), false); test_compress_decompress(OBJECT_COMPRESSED_LZ4, compress_blob_lz4, decompress_blob_lz4, data, sizeof(data), true); + test_decompress_startswith(OBJECT_COMPRESSED_LZ4, compress_blob_lz4, decompress_startswith_lz4, text, sizeof(text), false); test_decompress_startswith(OBJECT_COMPRESSED_LZ4, compress_blob_lz4, decompress_startswith_lz4, data, sizeof(data), true); + test_decompress_startswith(OBJECT_COMPRESSED_LZ4, + compress_blob_lz4, decompress_startswith_lz4, + huge, sizeof(huge), true); test_compress_stream(OBJECT_COMPRESSED_LZ4, "lz4cat", compress_stream_lz4, decompress_stream_lz4, argv[0]); + + test_lz4_decompress_partial(); #else log_info("/* LZ4 test skipped */"); #endif diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index 1de7f3639c..9f0d96e57d 100644 --- a/src/libsystemd-network/dhcp-option.c +++ b/src/libsystemd-network/dhcp-option.c @@ -34,14 +34,14 @@ static int option_append(uint8_t options[], size_t size, size_t *offset, assert(options); assert(offset); - if (code != DHCP_OPTION_END) + if (code != SD_DHCP_OPTION_END) /* always make sure there is space for an END option */ size --; switch (code) { - case DHCP_OPTION_PAD: - case DHCP_OPTION_END: + case SD_DHCP_OPTION_PAD: + case SD_DHCP_OPTION_END: if (size < *offset + 1) return -ENOBUFS; @@ -91,7 +91,7 @@ int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, else if (r == -ENOBUFS && (file || sname)) { /* did not fit, but we have more buffers to try close the options array and move the offset to its end */ - r = option_append(message->options, size, offset, DHCP_OPTION_END, 0, NULL); + r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL); if (r < 0) return r; @@ -112,7 +112,7 @@ int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, } else if (r == -ENOBUFS && sname) { /* did not fit, but we have more buffers to try close the file array and move the offset to its end */ - r = option_append(message->options, size, offset, DHCP_OPTION_END, 0, NULL); + r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL); if (r < 0) return r; @@ -152,10 +152,10 @@ static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overlo code = options[offset ++]; switch (code) { - case DHCP_OPTION_PAD: + case SD_DHCP_OPTION_PAD: continue; - case DHCP_OPTION_END: + case SD_DHCP_OPTION_END: return 0; } @@ -170,7 +170,7 @@ static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overlo option = &options[offset]; switch (code) { - case DHCP_OPTION_MESSAGE_TYPE: + case SD_DHCP_OPTION_MESSAGE_TYPE: if (len != 1) return -EINVAL; @@ -179,7 +179,7 @@ static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overlo break; - case DHCP_OPTION_ERROR_MESSAGE: + case SD_DHCP_OPTION_ERROR_MESSAGE: if (len == 0) return -EINVAL; @@ -203,7 +203,7 @@ static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overlo } break; - case DHCP_OPTION_OVERLOAD: + case SD_DHCP_OPTION_OVERLOAD: if (len != 1) return -EINVAL; diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c index 9ff42b155e..8d75d49691 100644 --- a/src/libsystemd-network/dhcp-packet.c +++ b/src/libsystemd-network/dhcp-packet.c @@ -44,7 +44,7 @@ int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid, message->magic = htobe32(DHCP_MAGIC_COOKIE); r = dhcp_option_append(message, optlen, &offset, 0, - DHCP_OPTION_MESSAGE_TYPE, 1, &type); + SD_DHCP_OPTION_MESSAGE_TYPE, 1, &type); if (r < 0) return r; diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h index f65529a00e..18490def06 100644 --- a/src/libsystemd-network/dhcp-protocol.h +++ b/src/libsystemd-network/dhcp-protocol.h @@ -105,48 +105,6 @@ enum { DHCP_OVERLOAD_SNAME = 2, }; -enum { - DHCP_OPTION_PAD = 0, - DHCP_OPTION_SUBNET_MASK = 1, - DHCP_OPTION_TIME_OFFSET = 2, - DHCP_OPTION_ROUTER = 3, - DHCP_OPTION_DOMAIN_NAME_SERVER = 6, - DHCP_OPTION_HOST_NAME = 12, - DHCP_OPTION_BOOT_FILE_SIZE = 13, - DHCP_OPTION_DOMAIN_NAME = 15, - DHCP_OPTION_ROOT_PATH = 17, - DHCP_OPTION_ENABLE_IP_FORWARDING = 19, - DHCP_OPTION_ENABLE_IP_FORWARDING_NL = 20, - DHCP_OPTION_POLICY_FILTER = 21, - DHCP_OPTION_INTERFACE_MDR = 22, - DHCP_OPTION_INTERFACE_TTL = 23, - DHCP_OPTION_INTERFACE_MTU_AGING_TIMEOUT = 24, - DHCP_OPTION_INTERFACE_MTU = 26, - DHCP_OPTION_BROADCAST = 28, - DHCP_OPTION_STATIC_ROUTE = 33, - DHCP_OPTION_NTP_SERVER = 42, - DHCP_OPTION_VENDOR_SPECIFIC = 43, - DHCP_OPTION_REQUESTED_IP_ADDRESS = 50, - DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51, - DHCP_OPTION_OVERLOAD = 52, - DHCP_OPTION_MESSAGE_TYPE = 53, - DHCP_OPTION_SERVER_IDENTIFIER = 54, - DHCP_OPTION_PARAMETER_REQUEST_LIST = 55, - DHCP_OPTION_ERROR_MESSAGE = 56, - DHCP_OPTION_MAXIMUM_MESSAGE_SIZE = 57, - DHCP_OPTION_RENEWAL_T1_TIME = 58, - DHCP_OPTION_REBINDING_T2_TIME = 59, - DHCP_OPTION_VENDOR_CLASS_IDENTIFIER = 60, - DHCP_OPTION_CLIENT_IDENTIFIER = 61, - DHCP_OPTION_FQDN = 81, - DHCP_OPTION_NEW_POSIX_TIMEZONE = 100, - DHCP_OPTION_NEW_TZDB_TIMEZONE = 101, - DHCP_OPTION_CLASSLESS_STATIC_ROUTE = 121, - DHCP_OPTION_PRIVATE_BASE = 224, - DHCP_OPTION_PRIVATE_LAST = 254, - DHCP_OPTION_END = 255, -}; - #define DHCP_MAX_FQDN_LENGTH 255 enum { diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index 850212aea1..6050851858 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -23,6 +23,8 @@ #include <netinet/in.h> #include <string.h> +#include "sd-dhcp6-client.h" + #include "alloc-util.h" #include "dhcp6-internal.h" #include "dhcp6-protocol.h" @@ -90,11 +92,11 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) { assert_return(buf && *buf && buflen && ia, -EINVAL); switch (ia->type) { - case DHCP6_OPTION_IA_NA: + case SD_DHCP6_OPTION_IA_NA: len = DHCP6_OPTION_IA_NA_LEN; break; - case DHCP6_OPTION_IA_TA: + case SD_DHCP6_OPTION_IA_TA: len = DHCP6_OPTION_IA_TA_LEN; break; @@ -117,7 +119,7 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) { *buflen -= len; LIST_FOREACH(addresses, addr, ia->addresses) { - r = option_append_hdr(buf, buflen, DHCP6_OPTION_IAADDR, + r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR, sizeof(addr->iaaddr)); if (r < 0) return r; @@ -196,7 +198,7 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, assert_return(!ia->addresses, -EINVAL); switch (iatype) { - case DHCP6_OPTION_IA_NA: + case SD_DHCP6_OPTION_IA_NA: if (*buflen < DHCP6_OPTION_IA_NA_LEN + sizeof(DHCP6Option) + sizeof(addr->iaaddr)) { @@ -219,7 +221,7 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, break; - case DHCP6_OPTION_IA_TA: + case SD_DHCP6_OPTION_IA_TA: if (*buflen < DHCP6_OPTION_IA_TA_LEN + sizeof(DHCP6Option) + sizeof(addr->iaaddr)) { r = -ENOBUFS; @@ -247,7 +249,7 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, while ((r = option_parse_hdr(buf, buflen, &opt, &optlen)) >= 0) { switch (opt) { - case DHCP6_OPTION_IAADDR: + case SD_DHCP6_OPTION_IAADDR: addr = new0(DHCP6Address, 1); if (!addr) { @@ -274,7 +276,7 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, break; - case DHCP6_OPTION_STATUS_CODE: + case SD_DHCP6_OPTION_STATUS_CODE: if (optlen < sizeof(status)) break; diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h index b3a28f88b4..246cc94cd8 100644 --- a/src/libsystemd-network/dhcp6-protocol.h +++ b/src/libsystemd-network/dhcp6-protocol.h @@ -99,41 +99,6 @@ enum { }; enum { - DHCP6_OPTION_CLIENTID = 1, - DHCP6_OPTION_SERVERID = 2, - DHCP6_OPTION_IA_NA = 3, - DHCP6_OPTION_IA_TA = 4, - DHCP6_OPTION_IAADDR = 5, - DHCP6_OPTION_ORO = 6, - DHCP6_OPTION_PREFERENCE = 7, - DHCP6_OPTION_ELAPSED_TIME = 8, - DHCP6_OPTION_RELAY_MSG = 9, - /* option code 10 is unassigned */ - DHCP6_OPTION_AUTH = 11, - DHCP6_OPTION_UNICAST = 12, - DHCP6_OPTION_STATUS_CODE = 13, - DHCP6_OPTION_RAPID_COMMIT = 14, - DHCP6_OPTION_USER_CLASS = 15, - DHCP6_OPTION_VENDOR_CLASS = 16, - DHCP6_OPTION_VENDOR_OPTS = 17, - DHCP6_OPTION_INTERFACE_ID = 18, - DHCP6_OPTION_RECONF_MSG = 19, - DHCP6_OPTION_RECONF_ACCEPT = 20, - - DHCP6_OPTION_DNS_SERVERS = 23, /* RFC 3646 */ - DHCP6_OPTION_DOMAIN_LIST = 24, /* RFC 3646 */ - - DHCP6_OPTION_SNTP_SERVERS = 31, /* RFC 4075, deprecated */ - - /* option code 35 is unassigned */ - - DHCP6_OPTION_NTP_SERVER = 56, /* RFC 5908 */ - - /* option codes 89-142 are unassigned */ - /* option codes 144-65535 are unassigned */ -}; - -enum { DHCP6_NTP_SUBOPTION_SRV_ADDR = 1, DHCP6_NTP_SUBOPTION_MC_ADDR = 2, DHCP6_NTP_SUBOPTION_SRV_FQDN = 3, diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c index a4d4f1ae2f..5da06435ed 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -437,7 +437,7 @@ int deserialize_in6_addrs(struct in6_addr **ret, const char *string) { return size; } -void serialize_dhcp_routes(FILE *f, const char *key, struct sd_dhcp_route *routes, size_t size) { +void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size) { unsigned i; assert(f); @@ -448,10 +448,15 @@ void serialize_dhcp_routes(FILE *f, const char *key, struct sd_dhcp_route *route fprintf(f, "%s=", key); for (i = 0; i < size; i++) { - fprintf(f, "%s/%" PRIu8, inet_ntoa(routes[i].dst_addr), - routes[i].dst_prefixlen); - fprintf(f, ",%s%s", inet_ntoa(routes[i].gw_addr), - (i < (size - 1)) ? " ": ""); + struct in_addr dest, gw; + uint8_t length; + + assert_se(sd_dhcp_route_get_destination(routes[i], &dest) >= 0); + assert_se(sd_dhcp_route_get_gateway(routes[i], &gw) >= 0); + assert_se(sd_dhcp_route_get_destination_prefix_length(routes[i], &length) >= 0); + + fprintf(f, "%s/%" PRIu8, inet_ntoa(dest), length); + fprintf(f, ",%s%s", inet_ntoa(gw), (i < (size - 1)) ? " ": ""); } fputs("\n", f); diff --git a/src/libsystemd-network/network-internal.h b/src/libsystemd-network/network-internal.h index 8a30921966..c43c01accf 100644 --- a/src/libsystemd-network/network-internal.h +++ b/src/libsystemd-network/network-internal.h @@ -23,6 +23,8 @@ #include <stdbool.h> +#include "sd-dhcp-lease.h" + #include "condition.h" #include "udev.h" @@ -74,7 +76,7 @@ int deserialize_in6_addrs(struct in6_addr **addresses, const char *string); /* don't include "dhcp-lease-internal.h" as it causes conflicts between netinet/ip.h and linux/ip.h */ struct sd_dhcp_route; -void serialize_dhcp_routes(FILE *f, const char *key, struct sd_dhcp_route *routes, size_t size); +void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size); int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t *ret_allocated, const char *string); int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size); diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 4521f8f0b1..cad1a52c09 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -43,6 +43,9 @@ #define MAX_CLIENT_ID_LEN (sizeof(uint32_t) + MAX_DUID_LEN) /* Arbitrary limit */ #define MAX_MAC_ADDR_LEN CONST_MAX(INFINIBAND_ALEN, ETH_ALEN) +#define RESTART_AFTER_NAK_MIN_USEC (1 * USEC_PER_SEC) +#define RESTART_AFTER_NAK_MAX_USEC (30 * USEC_PER_MINUTE) + struct sd_dhcp_client { unsigned n_ref; @@ -101,14 +104,15 @@ struct sd_dhcp_client { sd_dhcp_client_cb_t cb; void *userdata; sd_dhcp_lease *lease; + usec_t start_delay; }; static const uint8_t default_req_opts[] = { - DHCP_OPTION_SUBNET_MASK, - DHCP_OPTION_ROUTER, - DHCP_OPTION_HOST_NAME, - DHCP_OPTION_DOMAIN_NAME, - DHCP_OPTION_DOMAIN_NAME_SERVER, + SD_DHCP_OPTION_SUBNET_MASK, + SD_DHCP_OPTION_ROUTER, + SD_DHCP_OPTION_HOST_NAME, + SD_DHCP_OPTION_DOMAIN_NAME, + SD_DHCP_OPTION_DOMAIN_NAME_SERVER, }; static int client_receive_message_raw(sd_event_source *s, int fd, @@ -143,11 +147,11 @@ int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) { DHCP_STATE_STOPPED), -EBUSY); switch(option) { - case DHCP_OPTION_PAD: - case DHCP_OPTION_OVERLOAD: - case DHCP_OPTION_MESSAGE_TYPE: - case DHCP_OPTION_PARAMETER_REQUEST_LIST: - case DHCP_OPTION_END: + case SD_DHCP_OPTION_PAD: + case SD_DHCP_OPTION_OVERLOAD: + case SD_DHCP_OPTION_MESSAGE_TYPE: + case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST: + case SD_DHCP_OPTION_END: return -EINVAL; default: @@ -486,7 +490,7 @@ static int client_message_init(sd_dhcp_client *client, DHCPPacket **ret, Identifier option is not set */ if (client->client_id_len) { r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_CLIENT_IDENTIFIER, + SD_DHCP_OPTION_CLIENT_IDENTIFIER, client->client_id_len, &client->client_id); if (r < 0) @@ -502,7 +506,7 @@ static int client_message_init(sd_dhcp_client *client, DHCPPacket **ret, messages. */ r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_PARAMETER_REQUEST_LIST, + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, client->req_opts_size, client->req_opts); if (r < 0) return r; @@ -531,7 +535,7 @@ static int client_message_init(sd_dhcp_client *client, DHCPPacket **ret, */ max_size = htobe16(size); r = dhcp_option_append(&packet->dhcp, client->mtu, &optoffset, 0, - DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, 2, &max_size); if (r < 0) return r; @@ -557,7 +561,7 @@ static int client_append_fqdn_option(DHCPMessage *message, size_t optlen, size_t r = dns_name_to_wire_format(fqdn, buffer + 3, sizeof(buffer) - 3, false); if (r > 0) r = dhcp_option_append(message, optlen, optoffset, 0, - DHCP_OPTION_FQDN, 3 + r, buffer); + SD_DHCP_OPTION_FQDN, 3 + r, buffer); return r; } @@ -593,7 +597,7 @@ static int client_send_discover(sd_dhcp_client *client) { */ if (client->last_addr != INADDR_ANY) { r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_REQUESTED_IP_ADDRESS, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, 4, &client->last_addr); if (r < 0) return r; @@ -609,7 +613,7 @@ static int client_send_discover(sd_dhcp_client *client) { DHCPDISCOVER but dhclient does and so we do as well */ r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_HOST_NAME, + SD_DHCP_OPTION_HOST_NAME, strlen(client->hostname), client->hostname); } else r = client_append_fqdn_option(&discover->dhcp, optlen, &optoffset, @@ -620,7 +624,7 @@ static int client_send_discover(sd_dhcp_client *client) { if (client->vendor_class_identifier) { r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, strlen(client->vendor_class_identifier), client->vendor_class_identifier); if (r < 0) @@ -628,7 +632,7 @@ static int client_send_discover(sd_dhcp_client *client) { } r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_END, 0, NULL); + SD_DHCP_OPTION_END, 0, NULL); if (r < 0) return r; @@ -667,13 +671,13 @@ static int client_send_request(sd_dhcp_client *client) { */ r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_SERVER_IDENTIFIER, + SD_DHCP_OPTION_SERVER_IDENTIFIER, 4, &client->lease->server_address); if (r < 0) return r; r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_REQUESTED_IP_ADDRESS, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, 4, &client->lease->address); if (r < 0) return r; @@ -686,7 +690,7 @@ static int client_send_request(sd_dhcp_client *client) { assigned address. ’ciaddr’ MUST be zero. */ r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_REQUESTED_IP_ADDRESS, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, 4, &client->last_addr); if (r < 0) return r; @@ -721,7 +725,7 @@ static int client_send_request(sd_dhcp_client *client) { if (client->hostname) { if (dns_name_is_single_label(client->hostname)) r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_HOST_NAME, + SD_DHCP_OPTION_HOST_NAME, strlen(client->hostname), client->hostname); else r = client_append_fqdn_option(&request->dhcp, optlen, &optoffset, @@ -731,7 +735,7 @@ static int client_send_request(sd_dhcp_client *client) { } r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_END, 0, NULL); + SD_DHCP_OPTION_END, 0, NULL); if (r < 0) return r; @@ -945,6 +949,7 @@ error: } static int client_initialize_time_events(sd_dhcp_client *client) { + uint64_t usec = 0; int r; assert(client); @@ -952,10 +957,15 @@ static int client_initialize_time_events(sd_dhcp_client *client) { client->timeout_resend = sd_event_source_unref(client->timeout_resend); + if (client->start_delay) { + sd_event_now(client->event, clock_boottime_or_monotonic(), &usec); + usec += client->start_delay; + } + r = sd_event_add_time(client->event, &client->timeout_resend, clock_boottime_or_monotonic(), - 0, 0, + usec, 0, client_timeout_resend, client); if (r < 0) goto error; @@ -985,7 +995,7 @@ static int client_initialize_events(sd_dhcp_client *client, return 0; } -static int client_start(sd_dhcp_client *client) { +static int client_start_delayed(sd_dhcp_client *client) { int r; assert_return(client, -EINVAL); @@ -1013,6 +1023,11 @@ static int client_start(sd_dhcp_client *client) { return client_initialize_events(client, client_receive_message_raw); } +static int client_start(sd_dhcp_client *client) { + client->start_delay = 0; + return client_start_delayed(client); +} + static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userdata) { sd_dhcp_client *client = userdata; @@ -1362,6 +1377,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, int len) { DHCP_CLIENT_DONT_DESTROY(client); + char time_string[FORMAT_TIMESPAN_MAX]; int r = 0, notify_event = 0; assert(client); @@ -1409,6 +1425,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, r = client_handle_ack(client, message, len); if (r >= 0) { + client->start_delay = 0; client->timeout_resend = sd_event_source_unref(client->timeout_resend); client->receive_message = @@ -1458,11 +1475,15 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, if (r < 0) goto error; - r = client_start(client); + r = client_start_delayed(client); if (r < 0) goto error; - log_dhcp_client(client, "REBOOTED"); + log_dhcp_client(client, "REBOOT in %s", format_timespan(time_string, FORMAT_TIMESPAN_MAX, + client->start_delay, USEC_PER_SEC)); + + client->start_delay = CLAMP(client->start_delay * 2, + RESTART_AFTER_NAK_MIN_USEC, RESTART_AFTER_NAK_MAX_USEC); return 0; } else if (r == -ENOMSG) diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index e875ba4986..7a119fd488 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -37,6 +37,7 @@ #include "in-addr-util.h" #include "network-internal.h" #include "parse-util.h" +#include "stdio-util.h" #include "string-util.h" #include "unaligned.h" @@ -205,14 +206,28 @@ int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr) { return 0; } -int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, struct sd_dhcp_route **routes) { +/* + * The returned routes array must be freed by the caller. + * Route objects have the same lifetime of the lease and must not be freed. + */ +int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes) { + sd_dhcp_route **ret; + unsigned i; + assert_return(lease, -EINVAL); assert_return(routes, -EINVAL); if (lease->static_route_size <= 0) return -ENODATA; - *routes = lease->static_route; + ret = new(sd_dhcp_route *, lease->static_route_size); + if (!ret) + return -ENOMEM; + + for (i = 0; i < lease->static_route_size; i++) + ret[i] = &lease->static_route[i]; + + *routes = ret; return (int) lease->static_route_size; } @@ -338,6 +353,38 @@ static int lease_parse_string(const uint8_t *option, size_t len, char **ret) { return 0; } +static int lease_parse_domain(const uint8_t *option, size_t len, char **ret) { + _cleanup_free_ char *name = NULL, *normalized = NULL; + int r; + + assert(option); + assert(ret); + + r = lease_parse_string(option, len, &name); + if (r < 0) + return r; + if (!name) { + *ret = mfree(*ret); + return 0; + } + + r = dns_name_normalize(name, &normalized); + if (r < 0) + return r; + + if (is_localhost(normalized)) + return -EINVAL; + + if (dns_name_is_root(normalized)) + return -EINVAL; + + free(*ret); + *ret = normalized; + normalized = NULL; + + return 0; +} + static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_addr **ret, size_t *n_ret) { assert(option); assert(ret); @@ -452,7 +499,7 @@ static int lease_parse_classless_routes( if (len < 4) return -EINVAL; - lease_parse_be32(option, 4, &route->gw_addr.s_addr); + assert_se(lease_parse_be32(option, 4, &route->gw_addr.s_addr) >= 0); option += 4; len -= 4; @@ -470,21 +517,21 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void switch(code) { - case DHCP_OPTION_IP_ADDRESS_LEASE_TIME: + case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME: r = lease_parse_u32(option, len, &lease->lifetime, 1); if (r < 0) log_debug_errno(r, "Failed to parse lease time, ignoring: %m"); break; - case DHCP_OPTION_SERVER_IDENTIFIER: + case SD_DHCP_OPTION_SERVER_IDENTIFIER: r = lease_parse_be32(option, len, &lease->server_address); if (r < 0) log_debug_errno(r, "Failed to parse server identifier, ignoring: %m"); break; - case DHCP_OPTION_SUBNET_MASK: + case SD_DHCP_OPTION_SUBNET_MASK: r = lease_parse_be32(option, len, &lease->subnet_mask); if (r < 0) log_debug_errno(r, "Failed to parse subnet mask, ignoring: %m"); @@ -492,7 +539,7 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void lease->have_subnet_mask = true; break; - case DHCP_OPTION_BROADCAST: + case SD_DHCP_OPTION_BROADCAST: r = lease_parse_be32(option, len, &lease->broadcast); if (r < 0) log_debug_errno(r, "Failed to parse broadcast address, ignoring: %m"); @@ -500,7 +547,7 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void lease->have_broadcast = true; break; - case DHCP_OPTION_ROUTER: + case SD_DHCP_OPTION_ROUTER: if (len >= 4) { r = lease_parse_be32(option, 4, &lease->router); if (r < 0) @@ -508,103 +555,67 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void } break; - case DHCP_OPTION_DOMAIN_NAME_SERVER: + case SD_DHCP_OPTION_DOMAIN_NAME_SERVER: r = lease_parse_in_addrs(option, len, &lease->dns, &lease->dns_size); if (r < 0) log_debug_errno(r, "Failed to parse DNS server, ignoring: %m"); break; - case DHCP_OPTION_NTP_SERVER: + case SD_DHCP_OPTION_NTP_SERVER: r = lease_parse_in_addrs(option, len, &lease->ntp, &lease->ntp_size); if (r < 0) log_debug_errno(r, "Failed to parse NTP server, ignoring: %m"); break; - case DHCP_OPTION_STATIC_ROUTE: + case SD_DHCP_OPTION_STATIC_ROUTE: r = lease_parse_routes(option, len, &lease->static_route, &lease->static_route_size, &lease->static_route_allocated); if (r < 0) log_debug_errno(r, "Failed to parse static routes, ignoring: %m"); break; - case DHCP_OPTION_INTERFACE_MTU: + case SD_DHCP_OPTION_INTERFACE_MTU: r = lease_parse_u16(option, len, &lease->mtu, 68); if (r < 0) log_debug_errno(r, "Failed to parse MTU, ignoring: %m"); break; - case DHCP_OPTION_DOMAIN_NAME: { - _cleanup_free_ char *domainname = NULL, *normalized = NULL; - - r = lease_parse_string(option, len, &domainname); + case SD_DHCP_OPTION_DOMAIN_NAME: + r = lease_parse_domain(option, len, &lease->domainname); if (r < 0) { log_debug_errno(r, "Failed to parse domain name, ignoring: %m"); return 0; } - r = dns_name_normalize(domainname, &normalized); - if (r < 0) { - log_debug_errno(r, "Failed to normalize domain name '%s': %m", domainname); - return 0; - } - - if (is_localhost(normalized)) { - log_debug_errno(r, "Detected 'localhost' as suggested domain name, ignoring."); - break; - } - - free(lease->domainname); - lease->domainname = normalized; - normalized = NULL; - break; - } - - case DHCP_OPTION_HOST_NAME: { - _cleanup_free_ char *hostname = NULL, *normalized = NULL; - r = lease_parse_string(option, len, &hostname); + case SD_DHCP_OPTION_HOST_NAME: + r = lease_parse_domain(option, len, &lease->hostname); if (r < 0) { log_debug_errno(r, "Failed to parse host name, ignoring: %m"); return 0; } - r = dns_name_normalize(hostname, &normalized); - if (r < 0) { - log_debug_errno(r, "Failed to normalize host name '%s', ignoring: %m", hostname); - return 0; - } - - if (is_localhost(normalized)) { - log_debug_errno(r, "Detected 'localhost' as suggested host name, ignoring."); - return 0; - } - - free(lease->hostname); - lease->hostname = normalized; - normalized = NULL; - break; - } - case DHCP_OPTION_ROOT_PATH: + case SD_DHCP_OPTION_ROOT_PATH: r = lease_parse_string(option, len, &lease->root_path); if (r < 0) log_debug_errno(r, "Failed to parse root path, ignoring: %m"); break; - case DHCP_OPTION_RENEWAL_T1_TIME: + case SD_DHCP_OPTION_RENEWAL_T1_TIME: r = lease_parse_u32(option, len, &lease->t1, 1); if (r < 0) log_debug_errno(r, "Failed to parse T1 time, ignoring: %m"); break; - case DHCP_OPTION_REBINDING_T2_TIME: + case SD_DHCP_OPTION_REBINDING_T2_TIME: r = lease_parse_u32(option, len, &lease->t2, 1); if (r < 0) log_debug_errno(r, "Failed to parse T2 time, ignoring: %m"); break; - case DHCP_OPTION_CLASSLESS_STATIC_ROUTE: + case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE: r = lease_parse_classless_routes( option, len, &lease->static_route, @@ -614,7 +625,7 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void log_debug_errno(r, "Failed to parse classless routes, ignoring: %m"); break; - case DHCP_OPTION_NEW_TZDB_TIMEZONE: { + case SD_DHCP_OPTION_NEW_TZDB_TIMEZONE: { _cleanup_free_ char *tz = NULL; r = lease_parse_string(option, len, &tz); @@ -635,7 +646,7 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void break; } - case DHCP_OPTION_VENDOR_SPECIFIC: + case SD_DHCP_OPTION_VENDOR_SPECIFIC: if (len <= 0) lease->vendor_specific = mfree(lease->vendor_specific); @@ -653,7 +664,7 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void lease->vendor_specific_len = len; break; - case DHCP_OPTION_PRIVATE_BASE ... DHCP_OPTION_PRIVATE_LAST: + case SD_DHCP_OPTION_PRIVATE_BASE ... SD_DHCP_OPTION_PRIVATE_LAST: r = dhcp_lease_insert_private_option(lease, code, option, len); if (r < 0) return r; @@ -722,7 +733,7 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { size_t client_id_len, data_len; const char *string; uint16_t mtu; - struct sd_dhcp_route *routes; + _cleanup_free_ sd_dhcp_route **routes = NULL; uint32_t t1, t2, lifetime; int r; @@ -839,7 +850,7 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { LIST_FOREACH(options, option, lease->private_options) { char key[strlen("OPTION_000")+1]; - snprintf(key, sizeof(key), "OPTION_%"PRIu8, option->tag); + xsprintf(key, "OPTION_%" PRIu8, option->tag); r = serialize_dhcp_option(f, key, option->data, option->length); if (r < 0) goto fail; @@ -882,7 +893,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { *lifetime = NULL, *t1 = NULL, *t2 = NULL, - *options[DHCP_OPTION_PRIVATE_LAST - DHCP_OPTION_PRIVATE_BASE + 1] = {}; + *options[SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE + 1] = {}; int r, i; @@ -1050,7 +1061,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { log_debug_errno(r, "Failed to parse vendor specific data %s, ignoring: %m", vendor_specific_hex); } - for (i = 0; i <= DHCP_OPTION_PRIVATE_LAST - DHCP_OPTION_PRIVATE_BASE; i++) { + for (i = 0; i <= SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE; i++) { _cleanup_free_ void *data = NULL; size_t len; @@ -1063,7 +1074,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { continue; } - r = dhcp_lease_insert_private_option(lease, DHCP_OPTION_PRIVATE_BASE + i, data, len); + r = dhcp_lease_insert_private_option(lease, SD_DHCP_OPTION_PRIVATE_BASE + i, data, len); if (r < 0) return r; } @@ -1141,3 +1152,27 @@ int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **tz) { *tz = lease->timezone; return 0; } + +int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination) { + assert_return(route, -EINVAL); + assert_return(destination, -EINVAL); + + *destination = route->dst_addr; + return 0; +} + +int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length) { + assert_return(route, -EINVAL); + assert_return(length, -EINVAL); + + *length = route->dst_prefixlen; + return 0; +} + +int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway) { + assert_return(route, -EINVAL); + assert_return(gateway, -EINVAL); + + *gateway = route->gw_addr; + return 0; +} diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index 87ad595a1a..0b8d4bb843 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -354,13 +354,13 @@ int dhcp_server_send_packet(sd_dhcp_server *server, assert(packet); r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, - DHCP_OPTION_SERVER_IDENTIFIER, + SD_DHCP_OPTION_SERVER_IDENTIFIER, 4, &server->address); if (r < 0) return r; r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, - DHCP_OPTION_END, 0, NULL); + SD_DHCP_OPTION_END, 0, NULL); if (r < 0) return r; @@ -457,18 +457,18 @@ static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req, lease_time = htobe32(req->lifetime); r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, &lease_time); if (r < 0) return r; r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - DHCP_OPTION_SUBNET_MASK, 4, &server->netmask); + SD_DHCP_OPTION_SUBNET_MASK, 4, &server->netmask); if (r < 0) return r; r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - DHCP_OPTION_ROUTER, 4, &server->address); + SD_DHCP_OPTION_ROUTER, 4, &server->address); if (r < 0) return r; @@ -494,25 +494,25 @@ static int server_send_ack(sd_dhcp_server *server, DHCPRequest *req, lease_time = htobe32(req->lifetime); r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, &lease_time); if (r < 0) return r; r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - DHCP_OPTION_SUBNET_MASK, 4, &server->netmask); + SD_DHCP_OPTION_SUBNET_MASK, 4, &server->netmask); if (r < 0) return r; r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - DHCP_OPTION_ROUTER, 4, &server->address); + SD_DHCP_OPTION_ROUTER, 4, &server->address); if (r < 0) return r; if (server->n_dns > 0) { r = dhcp_option_append( &packet->dhcp, req->max_optlen, &offset, 0, - DHCP_OPTION_DOMAIN_NAME_SERVER, + SD_DHCP_OPTION_DOMAIN_NAME_SERVER, sizeof(struct in_addr) * server->n_dns, server->dns); if (r < 0) return r; @@ -521,7 +521,7 @@ static int server_send_ack(sd_dhcp_server *server, DHCPRequest *req, if (server->n_ntp > 0) { r = dhcp_option_append( &packet->dhcp, req->max_optlen, &offset, 0, - DHCP_OPTION_NTP_SERVER, + SD_DHCP_OPTION_NTP_SERVER, sizeof(struct in_addr) * server->n_ntp, server->ntp); if (r < 0) return r; @@ -530,7 +530,7 @@ static int server_send_ack(sd_dhcp_server *server, DHCPRequest *req, if (server->timezone) { r = dhcp_option_append( &packet->dhcp, req->max_optlen, &offset, 0, - DHCP_OPTION_NEW_TZDB_TIMEZONE, + SD_DHCP_OPTION_NEW_TZDB_TIMEZONE, strlen(server->timezone), server->timezone); if (r < 0) return r; @@ -576,7 +576,7 @@ static int server_send_forcerenew(sd_dhcp_server *server, be32_t address, return r; r = dhcp_option_append(&packet->dhcp, DHCP_MIN_OPTIONS_SIZE, - &optoffset, 0, DHCP_OPTION_END, 0, NULL); + &optoffset, 0, SD_DHCP_OPTION_END, 0, NULL); if (r < 0) return r; @@ -596,22 +596,22 @@ static int parse_request(uint8_t code, uint8_t len, const void *option, void *us assert(req); switch(code) { - case DHCP_OPTION_IP_ADDRESS_LEASE_TIME: + case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME: if (len == 4) req->lifetime = be32toh(*(be32_t*)option); break; - case DHCP_OPTION_REQUESTED_IP_ADDRESS: + case SD_DHCP_OPTION_REQUESTED_IP_ADDRESS: if (len == 4) req->requested_ip = *(be32_t*)option; break; - case DHCP_OPTION_SERVER_IDENTIFIER: + case SD_DHCP_OPTION_SERVER_IDENTIFIER: if (len == 4) req->server_id = *(be32_t*)option; break; - case DHCP_OPTION_CLIENT_IDENTIFIER: + case SD_DHCP_OPTION_CLIENT_IDENTIFIER: if (len >= 2) { uint8_t *data; @@ -625,7 +625,7 @@ static int parse_request(uint8_t code, uint8_t len, const void *option, void *us } break; - case DHCP_OPTION_MAXIMUM_MESSAGE_SIZE: + case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE: if (len == 2) req->max_optlen = be16toh(*(be16_t*)option) - - sizeof(DHCPPacket); diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index b8fae1e805..0e7327b895 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -72,10 +72,10 @@ struct sd_dhcp6_client { }; static const uint16_t default_req_opts[] = { - DHCP6_OPTION_DNS_SERVERS, - DHCP6_OPTION_DOMAIN_LIST, - DHCP6_OPTION_NTP_SERVER, - DHCP6_OPTION_SNTP_SERVERS, + SD_DHCP6_OPTION_DNS_SERVERS, + SD_DHCP6_OPTION_DOMAIN_LIST, + SD_DHCP6_OPTION_NTP_SERVER, + SD_DHCP6_OPTION_SNTP_SERVERS, }; const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = { @@ -245,10 +245,10 @@ int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option) assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); switch(option) { - case DHCP6_OPTION_DNS_SERVERS: - case DHCP6_OPTION_DOMAIN_LIST: - case DHCP6_OPTION_SNTP_SERVERS: - case DHCP6_OPTION_NTP_SERVER: + case SD_DHCP6_OPTION_DNS_SERVERS: + case SD_DHCP6_OPTION_DOMAIN_LIST: + case SD_DHCP6_OPTION_SNTP_SERVERS: + case SD_DHCP6_OPTION_NTP_SERVER: break; default: @@ -362,7 +362,7 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { message->type = DHCP6_SOLICIT; r = dhcp6_option_append(&opt, &optlen, - DHCP6_OPTION_RAPID_COMMIT, 0, NULL); + SD_DHCP6_OPTION_RAPID_COMMIT, 0, NULL); if (r < 0) return r; @@ -380,7 +380,7 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { else message->type = DHCP6_RENEW; - r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_SERVERID, + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_SERVERID, client->lease->serverid_len, client->lease->serverid); if (r < 0) @@ -406,14 +406,14 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { return -EINVAL; } - r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_ORO, + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_ORO, client->req_opts_len * sizeof(be16_t), client->req_opts); if (r < 0) return r; assert (client->duid_len); - r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_CLIENTID, + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_CLIENTID, client->duid_len, &client->duid); if (r < 0) return r; @@ -424,7 +424,7 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { else elapsed_time = 0xffff; - r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_ELAPSED_TIME, + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_ELAPSED_TIME, sizeof(elapsed_time), &elapsed_time); if (r < 0) return r; @@ -687,7 +687,7 @@ static int client_parse_message(sd_dhcp6_client *client, while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen, &optval)) >= 0) { switch (optcode) { - case DHCP6_OPTION_CLIENTID: + case SD_DHCP6_OPTION_CLIENTID: if (clientid) { log_dhcp6_client(client, "%s contains multiple clientids", dhcp6_message_type_to_string(message->type)); @@ -705,7 +705,7 @@ static int client_parse_message(sd_dhcp6_client *client, break; - case DHCP6_OPTION_SERVERID: + case SD_DHCP6_OPTION_SERVERID: r = dhcp6_lease_get_serverid(lease, &id, &id_len); if (r >= 0 && id) { log_dhcp6_client(client, "%s contains multiple serverids", @@ -719,7 +719,7 @@ static int client_parse_message(sd_dhcp6_client *client, break; - case DHCP6_OPTION_PREFERENCE: + case SD_DHCP6_OPTION_PREFERENCE: if (optlen != 1) return -EINVAL; @@ -729,7 +729,7 @@ static int client_parse_message(sd_dhcp6_client *client, break; - case DHCP6_OPTION_STATUS_CODE: + case SD_DHCP6_OPTION_STATUS_CODE: if (optlen < 2) return -EINVAL; @@ -743,7 +743,7 @@ static int client_parse_message(sd_dhcp6_client *client, break; - case DHCP6_OPTION_IA_NA: + case SD_DHCP6_OPTION_IA_NA: if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { log_dhcp6_client(client, "Information request ignoring IA NA option"); @@ -767,35 +767,35 @@ static int client_parse_message(sd_dhcp6_client *client, break; - case DHCP6_OPTION_RAPID_COMMIT: + case SD_DHCP6_OPTION_RAPID_COMMIT: r = dhcp6_lease_set_rapid_commit(lease); if (r < 0) return r; break; - case DHCP6_OPTION_DNS_SERVERS: + case SD_DHCP6_OPTION_DNS_SERVERS: r = dhcp6_lease_set_dns(lease, optval, optlen); if (r < 0) return r; break; - case DHCP6_OPTION_DOMAIN_LIST: + case SD_DHCP6_OPTION_DOMAIN_LIST: r = dhcp6_lease_set_domains(lease, optval, optlen); if (r < 0) return r; break; - case DHCP6_OPTION_NTP_SERVER: + case SD_DHCP6_OPTION_NTP_SERVER: r = dhcp6_lease_set_ntp(lease, optval, optlen); if (r < 0) return r; break; - case DHCP6_OPTION_SNTP_SERVERS: + case SD_DHCP6_OPTION_SNTP_SERVERS: r = dhcp6_lease_set_sntp(lease, optval, optlen); if (r < 0) return r; @@ -1285,7 +1285,7 @@ int sd_dhcp6_client_new(sd_dhcp6_client **ret) { client->n_ref = 1; - client->ia_na.type = DHCP6_OPTION_IA_NA; + client->ia_na.type = SD_DHCP6_OPTION_IA_NA; client->index = -1; diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c index 3f32ba35e7..327759e180 100644 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -256,7 +256,7 @@ int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) { assert_return(lease, -EINVAL); assert_return(optval, -EINVAL); - free(lease->ntp); + lease->ntp = mfree(lease->ntp); lease->ntp_count = 0; lease->ntp_allocated = 0; diff --git a/src/libsystemd-network/sd-lldp.c b/src/libsystemd-network/sd-lldp.c index d3ea74404b..1c696f9ef0 100644 --- a/src/libsystemd-network/sd-lldp.c +++ b/src/libsystemd-network/sd-lldp.c @@ -145,12 +145,9 @@ static int lldp_receive_frame(sd_lldp *lldp, tlv_packet *tlv) { /* 10.3.2 LLDPDU validation: rxProcessFrame() */ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { + bool system_description = false, system_name = false, chassis_id = false; + bool malformed = false, port_id = false, ttl = false, end = false; uint16_t type, len, i, l, t; - bool chassis_id = false; - bool malformed = false; - bool port_id = false; - bool ttl = false; - bool end = false; lldp_port *port; uint8_t *p, *q; sd_lldp *lldp; @@ -163,8 +160,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { lldp = (sd_lldp *) port->userdata; if (lldp->port->status == LLDP_PORT_STATUS_DISABLED) { - log_lldp("Port is disabled : %s . Dropping ...", - lldp->port->ifname); + log_lldp("Port: %s is disabled. Dropping.", lldp->port->ifname); goto out; } @@ -182,8 +178,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { if (type == LLDP_TYPE_END) { if (len != 0) { - log_lldp("TLV type end is not length 0. Length:%d received . Dropping ...", - len); + log_lldp("TLV type end must be length 0 (not %d). Dropping.", len); malformed = true; goto out; @@ -193,8 +188,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { break; } else if (type >=_LLDP_TYPE_MAX) { - log_lldp("TLV type not recognized %d . Dropping ...", - type); + log_lldp("TLV type: %d not recognized. Dropping.", type); malformed = true; goto out; @@ -209,7 +203,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { if (i <= 3) { if (i != type) { - log_lldp("TLV missing or out of order. Dropping ..."); + log_lldp("TLV missing or out of order. Dropping."); malformed = true; goto out; @@ -220,25 +214,22 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { case LLDP_TYPE_CHASSIS_ID: if (len < 2) { - log_lldp("Received malformed Chassis ID TLV len = %d. Dropping", - len); + log_lldp("Received malformed Chassis ID TLV length: %d. Dropping.", len); malformed = true; goto out; } if (chassis_id) { - log_lldp("Duplicate Chassis ID TLV found. Dropping ..."); + log_lldp("Duplicate Chassis ID TLV found. Dropping."); malformed = true; goto out; } /* Look what subtype it has */ - if (*q == LLDP_CHASSIS_SUBTYPE_RESERVED || - *q > LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED) { - log_lldp("Unknown subtype: %d found in Chassis ID TLV . Dropping ...", - *q); + if (*q == LLDP_CHASSIS_SUBTYPE_RESERVED || *q > LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED) { + log_lldp("Unknown subtype: %d found in Chassis ID TLV. Dropping.", *q); malformed = true; goto out; @@ -251,25 +242,22 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { case LLDP_TYPE_PORT_ID: if (len < 2) { - log_lldp("Received malformed Port ID TLV len = %d. Dropping", - len); + log_lldp("Received malformed Port ID TLV length: %d. Dropping.", len); malformed = true; goto out; } if (port_id) { - log_lldp("Duplicate Port ID TLV found. Dropping ..."); + log_lldp("Duplicate Port ID TLV found. Dropping."); malformed = true; goto out; } /* Look what subtype it has */ - if (*q == LLDP_PORT_SUBTYPE_RESERVED || - *q > LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED) { - log_lldp("Unknown subtype: %d found in Port ID TLV . Dropping ...", - *q); + if (*q == LLDP_PORT_SUBTYPE_RESERVED || *q > LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED) { + log_lldp("Unknown subtype: %d found in Port ID TLV. Dropping.", *q); malformed = true; goto out; @@ -282,16 +270,14 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { case LLDP_TYPE_TTL: if(len != 2) { - log_lldp( - "Received invalid lenth: %d TTL TLV. Dropping ...", - len); + log_lldp("Received invalid TTL TLV lenth: %d. Dropping.", len); malformed = true; goto out; } if (ttl) { - log_lldp("Duplicate TTL TLV found. Dropping ..."); + log_lldp("Duplicate TTL TLV found. Dropping."); malformed = true; goto out; @@ -300,11 +286,45 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { ttl = true; break; + case LLDP_TYPE_SYSTEM_NAME: + + /* According to RFC 1035 the length of a FQDN is limited to 255 characters */ + if (len > 255) { + log_lldp("Received invalid system name length: %d. Dropping.", len); + malformed = true; + goto out; + } + + if (system_name) { + log_lldp("Duplicate system name found. Dropping."); + malformed = true; + goto out; + } + + system_name = true; + + break; + case LLDP_TYPE_SYSTEM_DESCRIPTION: + + /* 0 <= n <= 255 octets */ + if (len > 255) { + log_lldp("Received invalid system description length: %d. Dropping.", len); + malformed = true; + goto out; + } + + if (system_description) { + log_lldp("Duplicate system description found. Dropping."); + malformed = true; + goto out; + } + + system_description = true; + break; default: if (len == 0) { - log_lldp("TLV type = %d's, length 0 received . Dropping ...", - type); + log_lldp("TLV type: %d length 0 received. Dropping.", type); malformed = true; goto out; @@ -314,7 +334,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { } if(!chassis_id || !port_id || !ttl || !end) { - log_lldp( "One or more mandotory TLV missing . Dropping ..."); + log_lldp("One or more mandatory TLV missing. Dropping."); malformed = true; goto out; @@ -323,7 +343,7 @@ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { r = tlv_packet_parse_pdu(tlv, length); if (r < 0) { - log_lldp( "Failed to parse the TLV. Dropping ..."); + log_lldp("Failed to parse the TLV. Dropping."); malformed = true; goto out; 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-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 45817d8c36..31832d2d1e 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -77,26 +77,26 @@ static void test_request_basic(sd_event *e) { assert_se(sd_dhcp_client_set_index(client, 1) == 0); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_SUBNET_MASK) == -EEXIST); + SD_DHCP_OPTION_SUBNET_MASK) == -EEXIST); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_ROUTER) == -EEXIST); + SD_DHCP_OPTION_ROUTER) == -EEXIST); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_HOST_NAME) == -EEXIST); + SD_DHCP_OPTION_HOST_NAME) == -EEXIST); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_DOMAIN_NAME) == -EEXIST); + SD_DHCP_OPTION_DOMAIN_NAME) == -EEXIST); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_DOMAIN_NAME_SERVER) == -EEXIST); + SD_DHCP_OPTION_DOMAIN_NAME_SERVER) == -EEXIST); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_PAD) == -EINVAL); + SD_DHCP_OPTION_PAD) == -EINVAL); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_END) == -EINVAL); + SD_DHCP_OPTION_END) == -EINVAL); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_MESSAGE_TYPE) == -EINVAL); + SD_DHCP_OPTION_MESSAGE_TYPE) == -EINVAL); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_OVERLOAD) == -EINVAL); + SD_DHCP_OPTION_OVERLOAD) == -EINVAL); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_PARAMETER_REQUEST_LIST) + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST) == -EINVAL); assert_se(sd_dhcp_client_set_request_option(client, 33) == 0); @@ -122,7 +122,7 @@ static void test_checksum(void) { static int check_options(uint8_t code, uint8_t len, const void *option, void *userdata) { switch(code) { - case DHCP_OPTION_CLIENT_IDENTIFIER: + case SD_DHCP_OPTION_CLIENT_IDENTIFIER: { uint32_t iaid; struct duid duid; @@ -393,7 +393,7 @@ static int test_addr_acq_recv_request(size_t size, DHCPMessage *request) { assert_se(res == DHCP_REQUEST); assert_se(xid == request->xid); - assert_se(msg_bytes[size - 1] == DHCP_OPTION_END); + assert_se(msg_bytes[size - 1] == SD_DHCP_OPTION_END); if (verbose) printf(" recv DHCP Request 0x%08x\n", be32toh(xid)); @@ -422,7 +422,7 @@ static int test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) { res = dhcp_option_parse(discover, size, check_options, NULL, NULL); assert_se(res == DHCP_DISCOVER); - assert_se(msg_bytes[size - 1] == DHCP_OPTION_END); + assert_se(msg_bytes[size - 1] == SD_DHCP_OPTION_END); xid = discover->xid; diff --git a/src/libsystemd-network/test-dhcp-option.c b/src/libsystemd-network/test-dhcp-option.c index 75d22c4df3..7b80a5bd90 100644 --- a/src/libsystemd-network/test-dhcp-option.c +++ b/src/libsystemd-network/test-dhcp-option.c @@ -29,7 +29,7 @@ static bool verbose = false; static struct option_desc option_tests[] = { { {}, 0, {}, 0, { 42, 5, 65, 66, 67, 68, 69 }, 7, false, }, { {}, 0, {}, 0, { 42, 5, 65, 66, 67, 68, 69, 0, 0, - DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_ACK }, 12, true, }, + SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_ACK }, 12, true, }, { {}, 0, {}, 0, { 8, 255, 70, 71, 72 }, 5, false, }, { {}, 0, {}, 0, { 0x35, 0x01, 0x05, 0x36, 0x04, 0x01, 0x00, 0xa8, 0xc0, 0x33, 0x04, 0x00, 0x01, 0x51, 0x80, 0x01, @@ -37,17 +37,17 @@ static struct option_desc option_tests[] = { 0xa8, 0x00, 0x01, 0x06, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, 40, true, }, - { {}, 0, {}, 0, { DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_OFFER, + { {}, 0, {}, 0, { SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_OFFER, 42, 3, 0, 0, 0 }, 8, true, }, { {}, 0, {}, 0, { 42, 2, 1, 2, 44 }, 5, false, }, { {}, 0, - { 222, 3, 1, 2, 3, DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_NAK }, 8, - { DHCP_OPTION_OVERLOAD, 1, DHCP_OVERLOAD_FILE }, 3, true, }, + { 222, 3, 1, 2, 3, SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_NAK }, 8, + { SD_DHCP_OPTION_OVERLOAD, 1, DHCP_OVERLOAD_FILE }, 3, true, }, - { { 1, 4, 1, 2, 3, 4, DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_ACK }, 9, + { { 1, 4, 1, 2, 3, 4, SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_ACK }, 9, { 222, 3, 1, 2, 3 }, 5, - { DHCP_OPTION_OVERLOAD, 1, + { SD_DHCP_OPTION_OVERLOAD, 1, DHCP_OVERLOAD_FILE|DHCP_OVERLOAD_SNAME }, 3, true, }, }; @@ -129,12 +129,12 @@ static void test_ignore_opts(uint8_t *descoption, int *descpos, int *desclen) { while (*descpos < *desclen) { switch(descoption[*descpos]) { - case DHCP_OPTION_PAD: + case SD_DHCP_OPTION_PAD: *descpos += 1; break; - case DHCP_OPTION_MESSAGE_TYPE: - case DHCP_OPTION_OVERLOAD: + case SD_DHCP_OPTION_MESSAGE_TYPE: + case SD_DHCP_OPTION_OVERLOAD: *descpos += 3; break; @@ -157,10 +157,10 @@ static int test_options_cb(uint8_t code, uint8_t len, const void *option, void * if (!desc) return -EINVAL; - assert_se(code != DHCP_OPTION_PAD); - assert_se(code != DHCP_OPTION_END); - assert_se(code != DHCP_OPTION_MESSAGE_TYPE); - assert_se(code != DHCP_OPTION_OVERLOAD); + assert_se(code != SD_DHCP_OPTION_PAD); + assert_se(code != SD_DHCP_OPTION_END); + assert_se(code != SD_DHCP_OPTION_MESSAGE_TYPE); + assert_se(code != SD_DHCP_OPTION_OVERLOAD); while (desc->pos >= 0 || desc->filepos >= 0 || desc->snamepos >= 0) { @@ -298,27 +298,27 @@ static void test_option_set(void) { result->options[2] = 'C'; result->options[3] = 'D'; - assert_se(dhcp_option_append(result, 0, &offset, 0, DHCP_OPTION_PAD, + assert_se(dhcp_option_append(result, 0, &offset, 0, SD_DHCP_OPTION_PAD, 0, NULL) == -ENOBUFS); assert_se(offset == 0); offset = 4; - assert_se(dhcp_option_append(result, 5, &offset, 0, DHCP_OPTION_PAD, + assert_se(dhcp_option_append(result, 5, &offset, 0, SD_DHCP_OPTION_PAD, 0, NULL) == -ENOBUFS); assert_se(offset == 4); - assert_se(dhcp_option_append(result, 6, &offset, 0, DHCP_OPTION_PAD, + assert_se(dhcp_option_append(result, 6, &offset, 0, SD_DHCP_OPTION_PAD, 0, NULL) >= 0); assert_se(offset == 5); offset = pos = 4; len = 11; - while (pos < len && options[pos] != DHCP_OPTION_END) { + while (pos < len && options[pos] != SD_DHCP_OPTION_END) { assert_se(dhcp_option_append(result, len, &offset, DHCP_OVERLOAD_SNAME, options[pos], options[pos + 1], &options[pos + 2]) >= 0); - if (options[pos] == DHCP_OPTION_PAD) + if (options[pos] == SD_DHCP_OPTION_PAD) pos++; else pos += 2 + options[pos + 1]; @@ -336,15 +336,15 @@ static void test_option_set(void) { if (verbose) printf("%2d: 0x%02x(0x%02x) (options)\n", 9, result->options[9], - DHCP_OPTION_END); + SD_DHCP_OPTION_END); - assert_se(result->options[9] == DHCP_OPTION_END); + assert_se(result->options[9] == SD_DHCP_OPTION_END); if (verbose) printf("%2d: 0x%02x(0x%02x) (options)\n", 10, result->options[10], - DHCP_OPTION_PAD); + SD_DHCP_OPTION_PAD); - assert_se(result->options[10] == DHCP_OPTION_PAD); + assert_se(result->options[10] == SD_DHCP_OPTION_PAD); for (i = 0; i < pos - 8; i++) { if (verbose) diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index 0ba9adff0a..4ad2e42b86 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -115,10 +115,10 @@ static void test_message_handler(void) { .message.hlen = ETHER_ADDR_LEN, .message.xid = htobe32(0x12345678), .message.chaddr = { 'A', 'B', 'C', 'D', 'E', 'F' }, - .option_type.code = DHCP_OPTION_MESSAGE_TYPE, + .option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE, .option_type.length = 1, .option_type.type = DHCP_DISCOVER, - .end = DHCP_OPTION_END, + .end = SD_DHCP_OPTION_END, }; struct in_addr address_lo = { .s_addr = htonl(INADDR_LOOPBACK), @@ -134,14 +134,14 @@ static void test_message_handler(void) { test.end = 0; /* TODO, shouldn't this fail? */ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER); - test.end = DHCP_OPTION_END; + test.end = SD_DHCP_OPTION_END; assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER); test.option_type.code = 0; test.option_type.length = 0; test.option_type.type = 0; assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0); - test.option_type.code = DHCP_OPTION_MESSAGE_TYPE; + test.option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE; test.option_type.length = 1; test.option_type.type = DHCP_DISCOVER; assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER); @@ -163,11 +163,11 @@ static void test_message_handler(void) { test.option_type.type = DHCP_REQUEST; assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0); - test.option_requested_ip.code = DHCP_OPTION_REQUESTED_IP_ADDRESS; + test.option_requested_ip.code = SD_DHCP_OPTION_REQUESTED_IP_ADDRESS; test.option_requested_ip.length = 4; test.option_requested_ip.address = htobe32(0x12345678); assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_NAK); - test.option_server_id.code = DHCP_OPTION_SERVER_IDENTIFIER; + test.option_server_id.code = SD_DHCP_OPTION_SERVER_IDENTIFIER; test.option_server_id.length = 4; test.option_server_id.address = htobe32(INADDR_LOOPBACK); test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3); @@ -182,7 +182,7 @@ static void test_message_handler(void) { test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3); assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_ACK); - test.option_client_id.code = DHCP_OPTION_CLIENT_IDENTIFIER; + test.option_client_id.code = SD_DHCP_OPTION_CLIENT_IDENTIFIER; test.option_client_id.length = 7; test.option_client_id.id[0] = 0x01; test.option_client_id.id[1] = 'A'; diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index 974d9ef6ac..93f585b8d4 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -70,11 +70,11 @@ static int test_client_basic(sd_event *e) { sizeof (mac_addr), ARPHRD_ETHER) >= 0); - assert_se(sd_dhcp6_client_set_request_option(client, DHCP6_OPTION_CLIENTID) == -EINVAL); - assert_se(sd_dhcp6_client_set_request_option(client, DHCP6_OPTION_DNS_SERVERS) == -EEXIST); - assert_se(sd_dhcp6_client_set_request_option(client, DHCP6_OPTION_NTP_SERVER) == -EEXIST); - assert_se(sd_dhcp6_client_set_request_option(client, DHCP6_OPTION_SNTP_SERVERS) == -EEXIST); - assert_se(sd_dhcp6_client_set_request_option(client, DHCP6_OPTION_DOMAIN_LIST) == -EEXIST); + assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_CLIENTID) == -EINVAL); + assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVERS) == -EEXIST); + assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NTP_SERVER) == -EEXIST); + assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_SNTP_SERVERS) == -EEXIST); + assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DOMAIN_LIST) == -EEXIST); assert_se(sd_dhcp6_client_set_request_option(client, 10) == -EINVAL); assert_se(sd_dhcp6_client_set_callback(client, NULL, NULL) >= 0); @@ -88,9 +88,9 @@ static int test_client_basic(sd_event *e) { static int test_option(sd_event *e) { uint8_t packet[] = { 'F', 'O', 'O', - 0x00, DHCP6_OPTION_ORO, 0x00, 0x07, + 0x00, SD_DHCP6_OPTION_ORO, 0x00, 0x07, 'A', 'B', 'C', 'D', 'E', 'F', 'G', - 0x00, DHCP6_OPTION_VENDOR_CLASS, 0x00, 0x09, + 0x00, SD_DHCP6_OPTION_VENDOR_CLASS, 0x00, 0x09, '1', '2', '3', '4', '5', '6', '7', '8', '9', 'B', 'A', 'R', }; @@ -124,7 +124,7 @@ static int test_option(sd_event *e) { &optval) >= 0); pos += 4 + optlen; assert_se(buf == &packet[pos]); - assert_se(optcode == DHCP6_OPTION_ORO); + assert_se(optcode == SD_DHCP6_OPTION_ORO); assert_se(optlen == 7); assert_se(buflen + pos == sizeof(packet)); @@ -137,7 +137,7 @@ static int test_option(sd_event *e) { &optval) >= 0); pos += 4 + optlen; assert_se(buf == &packet[pos]); - assert_se(optcode == DHCP6_OPTION_VENDOR_CLASS); + assert_se(optcode == SD_DHCP6_OPTION_VENDOR_CLASS); assert_se(optlen == 9); assert_se(buflen + pos == sizeof(packet)); @@ -232,13 +232,13 @@ static int test_advertise_option(sd_event *e) { &optval)) >= 0) { switch(optcode) { - case DHCP6_OPTION_CLIENTID: + case SD_DHCP6_OPTION_CLIENTID: assert_se(optlen == 14); opt_clientid = true; break; - case DHCP6_OPTION_IA_NA: + case SD_DHCP6_OPTION_IA_NA: assert_se(optlen == 94); assert_se(!memcmp(optval, &msg_advertise[26], optlen)); @@ -257,7 +257,7 @@ static int test_advertise_option(sd_event *e) { break; - case DHCP6_OPTION_SERVERID: + case SD_DHCP6_OPTION_SERVERID: assert_se(optlen == 14); assert_se(!memcmp(optval, &msg_advertise[179], optlen)); @@ -265,7 +265,7 @@ static int test_advertise_option(sd_event *e) { optlen) >= 0); break; - case DHCP6_OPTION_PREFERENCE: + case SD_DHCP6_OPTION_PREFERENCE: assert_se(optlen == 1); assert_se(!*optval); @@ -273,24 +273,24 @@ static int test_advertise_option(sd_event *e) { *optval) >= 0); break; - case DHCP6_OPTION_ELAPSED_TIME: + case SD_DHCP6_OPTION_ELAPSED_TIME: assert_se(optlen == 2); break; - case DHCP6_OPTION_DNS_SERVERS: + case SD_DHCP6_OPTION_DNS_SERVERS: assert_se(optlen == 16); assert_se(dhcp6_lease_set_dns(lease, optval, optlen) >= 0); break; - case DHCP6_OPTION_DOMAIN_LIST: + case SD_DHCP6_OPTION_DOMAIN_LIST: assert_se(optlen == 11); assert_se(dhcp6_lease_set_domains(lease, optval, optlen) >= 0); break; - case DHCP6_OPTION_SNTP_SERVERS: + case SD_DHCP6_OPTION_SNTP_SERVERS: assert_se(optlen == 16); assert_se(dhcp6_lease_set_sntp(lease, optval, optlen) >= 0); @@ -379,7 +379,7 @@ static void test_client_solicit_cb(sd_dhcp6_client *client, int event, assert_se(sd_dhcp6_lease_get_ntp_addrs(lease, &addrs) == 1); assert_se(!memcmp(addrs, &msg_advertise[159], 16)); - assert_se(sd_dhcp6_client_set_request_option(client, DHCP6_OPTION_DNS_SERVERS) == -EBUSY); + assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVERS) == -EBUSY); if (verbose) printf(" got DHCPv6 event %d\n", event); @@ -425,7 +425,7 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option, while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen, &optval)) >= 0) { switch(optcode) { - case DHCP6_OPTION_CLIENTID: + case SD_DHCP6_OPTION_CLIENTID: assert_se(!found_clientid); found_clientid = true; @@ -434,7 +434,7 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option, break; - case DHCP6_OPTION_IA_NA: + case SD_DHCP6_OPTION_IA_NA: assert_se(!found_iana); found_iana = true; @@ -453,7 +453,7 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option, break; - case DHCP6_OPTION_SERVERID: + case SD_DHCP6_OPTION_SERVERID: assert_se(!found_serverid); found_serverid = true; @@ -462,7 +462,7 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option, break; - case DHCP6_OPTION_ELAPSED_TIME: + case SD_DHCP6_OPTION_ELAPSED_TIME: assert_se(!found_elapsed_time); found_elapsed_time = true; @@ -521,7 +521,7 @@ static int test_client_verify_solicit(DHCP6Message *solicit, uint8_t *option, while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen, &optval)) >= 0) { switch(optcode) { - case DHCP6_OPTION_CLIENTID: + case SD_DHCP6_OPTION_CLIENTID: assert_se(!found_clientid); found_clientid = true; @@ -530,7 +530,7 @@ static int test_client_verify_solicit(DHCP6Message *solicit, uint8_t *option, break; - case DHCP6_OPTION_IA_NA: + case SD_DHCP6_OPTION_IA_NA: assert_se(!found_iana); found_iana = true; @@ -540,7 +540,7 @@ static int test_client_verify_solicit(DHCP6Message *solicit, uint8_t *option, break; - case DHCP6_OPTION_ELAPSED_TIME: + case SD_DHCP6_OPTION_ELAPSED_TIME: assert_se(!found_elapsed_time); found_elapsed_time = true; @@ -614,7 +614,7 @@ static int test_client_verify_information_request(DHCP6Message *information_requ while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen, &optval)) >= 0) { switch(optcode) { - case DHCP6_OPTION_CLIENTID: + case SD_DHCP6_OPTION_CLIENTID: assert_se(!found_clientid); found_clientid = true; @@ -623,17 +623,17 @@ static int test_client_verify_information_request(DHCP6Message *information_requ break; - case DHCP6_OPTION_IA_NA: + case SD_DHCP6_OPTION_IA_NA: assert_not_reached("IA TA option must not be present"); break; - case DHCP6_OPTION_SERVERID: + case SD_DHCP6_OPTION_SERVERID: assert_not_reached("Server ID option must not be present"); break; - case DHCP6_OPTION_ELAPSED_TIME: + case SD_DHCP6_OPTION_ELAPSED_TIME: assert_se(!found_elapsed_time); found_elapsed_time = true; diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index 043ff13e6f..4ab637b686 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -481,3 +481,11 @@ global: sd_bus_path_encode_many; sd_listen_fds_with_names; } LIBSYSTEMD_226; + +LIBSYSTEMD_229 { +global: + sd_journal_has_runtime_files; + sd_journal_has_persistent_files; + sd_journal_enumerate_fields; + sd_journal_restart_fields; +} LIBSYSTEMD_227; diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index 8d486fcbbd..e344b3b77b 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -67,12 +67,20 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_PROCESS, ESRCH), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_NAME_SERVERS, EIO), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_NAME_SERVERS, ESRCH), SD_BUS_ERROR_MAP(BUS_ERROR_INVALID_REPLY, EINVAL), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_RR, ENOENT), SD_BUS_ERROR_MAP(BUS_ERROR_NO_RESOURCES, ENOMEM), SD_BUS_ERROR_MAP(BUS_ERROR_CNAME_LOOP, EDEADLK), 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_NETWORK_DOWN, ENETDOWN), 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 f2092795f4..130779e8e3 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -72,6 +72,14 @@ #define BUS_ERROR_NO_RESOURCES "org.freedesktop.resolve1.NoResources" #define BUS_ERROR_CNAME_LOOP "org.freedesktop.resolve1.CNameLoop" #define BUS_ERROR_ABORTED "org.freedesktop.resolve1.Aborted" +#define BUS_ERROR_CONNECTION_FAILURE "org.freedesktop.resolve1.ConnectionFailure" +#define BUS_ERROR_NO_SUCH_SERVICE "org.freedesktop.resolve1.NoSuchService" +#define BUS_ERROR_DNSSEC_FAILED "org.freedesktop.resolve1.DnssecFailed" +#define BUS_ERROR_NO_TRUST_ANCHOR "org.freedesktop.resolve1.NoTrustAnchor" +#define BUS_ERROR_RR_TYPE_UNSUPPORTED "org.freedesktop.resolve1.ResourceRecordTypeUnsupported" +#define BUS_ERROR_NO_SUCH_LINK "org.freedesktop.resolve1.NoSuchLink" +#define BUS_ERROR_LINK_BUSY "org.freedesktop.resolve1.LinkBusy" +#define BUS_ERROR_NETWORK_DOWN "org.freedesktop.resolve1.NetworkDown" #define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError." #define BUS_ERROR_NO_SUCH_TRANSFER "org.freedesktop.import1.NoSuchTransfer" diff --git a/src/libsystemd/sd-bus/bus-error.c b/src/libsystemd/sd-bus/bus-error.c index 404eaa3c89..c77eb5fd03 100644 --- a/src/libsystemd/sd-bus/bus-error.c +++ b/src/libsystemd/sd-bus/bus-error.c @@ -93,14 +93,14 @@ static int bus_error_name_to_errno(const char *name) { p = startswith(name, "System.Error."); if (p) { r = errno_from_name(p); - if (r <= 0) + if (r < 0) return EIO; return r; } - if (additional_error_maps) { - for (map = additional_error_maps; *map; map++) { + if (additional_error_maps) + for (map = additional_error_maps; *map; map++) for (m = *map;; m++) { /* For additional error maps the end marker is actually the end marker */ if (m->code == BUS_ERROR_MAP_END_MARKER) @@ -109,15 +109,13 @@ static int bus_error_name_to_errno(const char *name) { if (streq(m->name, name)) return m->code; } - } - } m = __start_BUS_ERROR_MAP; while (m < __stop_BUS_ERROR_MAP) { /* For magic ELF error maps, the end marker might * appear in the middle of things, since multiple maps * might appear in the same section. Hence, let's skip - * over it, but realign the pointer to the netx 8byte + * over it, but realign the pointer to the next 8 byte * boundary, which is the selected alignment for the * arrays. */ if (m->code == BUS_ERROR_MAP_END_MARKER) { @@ -258,25 +256,24 @@ int bus_error_setfv(sd_bus_error *e, const char *name, const char *format, va_li if (!name) return 0; - if (!e) - goto finish; - assert_return(!bus_error_is_dirty(e), -EINVAL); + if (e) { + assert_return(!bus_error_is_dirty(e), -EINVAL); - e->name = strdup(name); - if (!e->name) { - *e = BUS_ERROR_OOM; - return -ENOMEM; - } + e->name = strdup(name); + if (!e->name) { + *e = BUS_ERROR_OOM; + return -ENOMEM; + } - /* If we hit OOM on formatting the pretty message, we ignore - * this, since we at least managed to write the error name */ - if (format) - (void) vasprintf((char**) &e->message, format, ap); + /* If we hit OOM on formatting the pretty message, we ignore + * this, since we at least managed to write the error name */ + if (format) + (void) vasprintf((char**) &e->message, format, ap); - e->_need_free = 1; + e->_need_free = 1; + } -finish: return -bus_error_name_to_errno(name); } @@ -582,27 +579,29 @@ const char *bus_error_message(const sd_bus_error *e, int error) { return strerror(error); } +static bool map_ok(const sd_bus_error_map *map) { + for (; map->code != BUS_ERROR_MAP_END_MARKER; map++) + if (!map->name || map->code <=0) + return false; + return true; +} + _public_ int sd_bus_error_add_map(const sd_bus_error_map *map) { const sd_bus_error_map **maps = NULL; unsigned n = 0; assert_return(map, -EINVAL); + assert_return(map_ok(map), -EINVAL); - if (additional_error_maps) { - for (;; n++) { - if (additional_error_maps[n] == NULL) - break; - + if (additional_error_maps) + for (; additional_error_maps[n] != NULL; n++) if (additional_error_maps[n] == map) return 0; - } - } maps = realloc_multiply(additional_error_maps, sizeof(struct sd_bus_error_map*), n + 2); if (!maps) return -ENOMEM; - maps[n] = map; maps[n+1] = NULL; diff --git a/src/libsystemd/sd-bus/bus-kernel.c b/src/libsystemd/sd-bus/bus-kernel.c index 6c05444e9a..e7d6170eec 100644 --- a/src/libsystemd/sd-bus/bus-kernel.c +++ b/src/libsystemd/sd-bus/bus-kernel.c @@ -47,6 +47,7 @@ #include "formats-util.h" #include "memfd-util.h" #include "parse-util.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "user-util.h" @@ -269,8 +270,8 @@ static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) { struct bus_body_part *part; struct kdbus_item *d; const char *destination; - bool well_known; - uint64_t unique; + bool well_known = false; + uint64_t dst_id; size_t sz, dl; unsigned i; int r; @@ -287,13 +288,21 @@ static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) { destination = m->destination ?: m->destination_ptr; if (destination) { - r = bus_kernel_parse_unique_name(destination, &unique); + r = bus_kernel_parse_unique_name(destination, &dst_id); if (r < 0) return r; - - well_known = r == 0; + if (r == 0) { + well_known = true; + + /* verify_destination_id will usually be 0, which makes the kernel + * driver only look at the provided well-known name. Otherwise, + * the kernel will make sure the provided destination id matches + * the owner of the provided well-known-name, and fail if they + * differ. Currently, this is only needed for bus-proxyd. */ + dst_id = m->verify_destination_id; + } } else - well_known = false; + dst_id = KDBUS_DST_ID_BROADCAST; sz = offsetof(struct kdbus_msg, items); @@ -331,15 +340,7 @@ static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) { ((m->header->flags & BUS_MESSAGE_NO_AUTO_START) ? KDBUS_MSG_NO_AUTO_START : 0) | ((m->header->type == SD_BUS_MESSAGE_SIGNAL) ? KDBUS_MSG_SIGNAL : 0); - if (well_known) - /* verify_destination_id will usually be 0, which makes the kernel driver only look - * at the provided well-known name. Otherwise, the kernel will make sure the provided - * destination id matches the owner of the provided weel-known-name, and fail if they - * differ. Currently, this is only needed for bus-proxyd. */ - m->kdbus->dst_id = m->verify_destination_id; - else - m->kdbus->dst_id = destination ? unique : KDBUS_DST_ID_BROADCAST; - + m->kdbus->dst_id = dst_id; m->kdbus->payload_type = KDBUS_PAYLOAD_DBUS; m->kdbus->cookie = m->header->dbus2.cookie; m->kdbus->priority = m->priority; @@ -849,7 +850,8 @@ static int bus_kernel_make_message(sd_bus *bus, struct kdbus_msg *k) { if (k->src_id == KDBUS_SRC_ID_KERNEL) bus_message_set_sender_driver(bus, m); else { - snprintf(m->sender_buffer, sizeof(m->sender_buffer), ":1.%llu", (unsigned long long) k->src_id); + xsprintf(m->sender_buffer, ":1.%llu", + (unsigned long long)k->src_id); m->sender = m->creds.unique_name = m->sender_buffer; } @@ -860,7 +862,8 @@ static int bus_kernel_make_message(sd_bus *bus, struct kdbus_msg *k) { else if (k->dst_id == KDBUS_DST_ID_NAME) m->destination = bus->unique_name; /* fill in unique name if the well-known name is missing */ else { - snprintf(m->destination_buffer, sizeof(m->destination_buffer), ":1.%llu", (unsigned long long) k->dst_id); + xsprintf(m->destination_buffer, ":1.%llu", + (unsigned long long)k->dst_id); m->destination = m->destination_buffer; } diff --git a/src/libsystemd/sd-bus/test-bus-cleanup.c b/src/libsystemd/sd-bus/test-bus-cleanup.c index 1c3ccda364..cbc450fdb2 100644 --- a/src/libsystemd/sd-bus/test-bus-cleanup.c +++ b/src/libsystemd/sd-bus/test-bus-cleanup.c @@ -36,7 +36,7 @@ static void test_bus_new(void) { } static int test_bus_open(void) { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; r = sd_bus_open_system(&bus); @@ -59,8 +59,8 @@ static void test_bus_new_method_call(void) { printf("after message_new_method_call: refcount %u\n", REFCNT_GET(bus->n_ref)); - sd_bus_unref(bus); - printf("after bus_unref: refcount %u\n", m->n_ref); + sd_bus_flush_close_unref(bus); + printf("after bus_flush_close_unref: refcount %u\n", m->n_ref); } static void test_bus_new_signal(void) { @@ -73,8 +73,8 @@ static void test_bus_new_signal(void) { printf("after message_new_signal: refcount %u\n", REFCNT_GET(bus->n_ref)); - sd_bus_unref(bus); - printf("after bus_unref: refcount %u\n", m->n_ref); + sd_bus_flush_close_unref(bus); + printf("after bus_flush_close_unref: refcount %u\n", m->n_ref); } int main(int argc, char **argv) { diff --git a/src/libsystemd/sd-bus/test-bus-error.c b/src/libsystemd/sd-bus/test-bus-error.c index c52405463e..407fd14555 100644 --- a/src/libsystemd/sd-bus/test-bus-error.c +++ b/src/libsystemd/sd-bus/test-bus-error.c @@ -44,7 +44,15 @@ static void test_error(void) { assert_se(sd_bus_error_is_set(&error)); sd_bus_error_free(&error); + /* Check with no error */ assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_setf(&error, NULL, "yyy %i", -1) == 0); + assert_se(error.name == NULL); + assert_se(error.message == NULL); + assert_se(!sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND)); + assert_se(sd_bus_error_get_errno(&error) == 0); + assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_setf(&error, SD_BUS_ERROR_FILE_NOT_FOUND, "yyy %i", -1) == -ENOENT); assert_se(streq(error.name, SD_BUS_ERROR_FILE_NOT_FOUND)); assert_se(streq(error.message, "yyy -1")); @@ -112,6 +120,16 @@ static void test_error(void) { assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_IO_ERROR)); assert_se(sd_bus_error_get_errno(&error) == EIO); assert_se(sd_bus_error_is_set(&error)); + sd_bus_error_free(&error); + + /* Check with no error */ + assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_set_errnof(&error, 0, "Waldi %c", 'X') == 0); + assert_se(error.name == NULL); + assert_se(error.message == NULL); + assert_se(!sd_bus_error_has_name(&error, SD_BUS_ERROR_IO_ERROR)); + assert_se(sd_bus_error_get_errno(&error) == 0); + assert_se(!sd_bus_error_is_set(&error)); } extern const sd_bus_error_map __start_BUS_ERROR_MAP[]; @@ -167,6 +185,16 @@ static const sd_bus_error_map test_errors4[] = { SD_BUS_ERROR_MAP_END }; +static const sd_bus_error_map test_errors_bad1[] = { + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-1", 0), + SD_BUS_ERROR_MAP_END +}; + +static const sd_bus_error_map test_errors_bad2[] = { + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-1", -1), + SD_BUS_ERROR_MAP_END +}; + static void test_errno_mapping_custom(void) { assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error", NULL) == -5); assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-2", NULL) == -52); @@ -190,6 +218,9 @@ static void test_errno_mapping_custom(void) { assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-y", NULL) == -EIO); assert_se(sd_bus_error_set(NULL, BUS_ERROR_NO_SUCH_UNIT, NULL) == -ENOENT); + + assert_se(sd_bus_error_add_map(test_errors_bad1) == -EINVAL); + assert_se(sd_bus_error_add_map(test_errors_bad2) == -EINVAL); } int main(int argc, char *argv[]) { diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c index 077cc6ddac..b9d1ea5217 100644 --- a/src/libsystemd/sd-bus/test-bus-marshal.c +++ b/src/libsystemd/sd-bus/test-bus-marshal.c @@ -246,6 +246,8 @@ int main(int argc, char *argv[]) { log_error("%s", error.message); else dbus_message_unref(w); + + dbus_error_free(&error); } #endif 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 3191b458d1..eeb3453919 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -37,6 +37,7 @@ #include "process-util.h" #include "set.h" #include "signal-util.h" +#include "string-table.h" #include "string-util.h" #include "time-util.h" #include "util.h" @@ -60,6 +61,23 @@ typedef enum EventSourceType { _SOURCE_EVENT_SOURCE_TYPE_INVALID = -1 } EventSourceType; +static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] = { + [SOURCE_IO] = "io", + [SOURCE_TIME_REALTIME] = "realtime", + [SOURCE_TIME_BOOTTIME] = "bootime", + [SOURCE_TIME_MONOTONIC] = "monotonic", + [SOURCE_TIME_REALTIME_ALARM] = "realtime-alarm", + [SOURCE_TIME_BOOTTIME_ALARM] = "boottime-alarm", + [SOURCE_SIGNAL] = "signal", + [SOURCE_CHILD] = "child", + [SOURCE_DEFER] = "defer", + [SOURCE_POST] = "post", + [SOURCE_EXIT] = "exit", + [SOURCE_WATCHDOG] = "watchdog", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int); + /* All objects we use in epoll events start with this value, so that * we know how to dispatch it */ typedef enum WakeupType { @@ -207,6 +225,7 @@ struct sd_event { bool exit_requested:1; bool need_process_child:1; bool watchdog:1; + bool profile_delays:1; int exit_code; @@ -218,6 +237,9 @@ struct sd_event { unsigned n_sources; LIST_HEAD(sd_event_source, sources); + + usec_t last_run, last_log; + unsigned delays[sizeof(usec_t) * 8]; }; static void source_disconnect(sd_event_source *s); @@ -305,6 +327,10 @@ static int earliest_time_prioq_compare(const void *a, const void *b) { return 0; } +static usec_t time_event_source_latest(const sd_event_source *s) { + return usec_add(s->time.next, s->time.accuracy); +} + static int latest_time_prioq_compare(const void *a, const void *b) { const sd_event_source *x = a, *y = b; @@ -324,9 +350,9 @@ static int latest_time_prioq_compare(const void *a, const void *b) { return 1; /* Order by time */ - if (x->time.next + x->time.accuracy < y->time.next + y->time.accuracy) + if (time_event_source_latest(x) < time_event_source_latest(y)) return -1; - if (x->time.next + x->time.accuracy > y->time.next + y->time.accuracy) + if (time_event_source_latest(x) > time_event_source_latest(y)) return 1; return 0; @@ -426,6 +452,11 @@ _public_ int sd_event_new(sd_event** ret) { goto fail; } + if (secure_getenv("SD_EVENT_PROFILE_DELAYS")) { + log_debug("Event loop profiling enabled. Logarithmic histogram of event loop iterations in the range 2^0 ... 2^63 us will be logged every 5s."); + e->profile_delays = true; + } + *ret = e; return 0; @@ -482,7 +513,8 @@ static void source_io_unregister(sd_event_source *s) { r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->io.fd, NULL); if (r < 0) - log_debug_errno(errno, "Failed to remove source %s from epoll: %m", strna(s->description)); + log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll: %m", + strna(s->description), event_source_type_to_string(s->type)); s->io.registered = false; } @@ -633,8 +665,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; } @@ -1036,7 +1070,6 @@ _public_ int sd_event_add_time( int r; assert_return(e, -EINVAL); - assert_return(usec != (uint64_t) -1, -EINVAL); assert_return(accuracy != (uint64_t) -1, -EINVAL); assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); assert_return(!event_pid_changed(e), -ECHILD); @@ -1726,7 +1759,6 @@ _public_ int sd_event_source_set_time(sd_event_source *s, uint64_t usec) { struct clock_data *d; assert_return(s, -EINVAL); - assert_return(usec != (uint64_t) -1, -EINVAL); assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); assert_return(!event_pid_changed(s->event), -ECHILD); @@ -1856,6 +1888,8 @@ static usec_t sleep_between(sd_event *e, usec_t a, usec_t b) { if (a <= 0) return 0; + if (a >= USEC_INFINITY) + return USEC_INFINITY; if (b <= a + 1) return a; @@ -1945,7 +1979,7 @@ static int event_arm_timer( d->needs_rearm = false; a = prioq_peek(d->earliest); - if (!a || a->enabled == SD_EVENT_OFF) { + if (!a || a->enabled == SD_EVENT_OFF || a->time.next == USEC_INFINITY) { if (d->fd < 0) return 0; @@ -1965,7 +1999,7 @@ static int event_arm_timer( b = prioq_peek(d->latest); assert_se(b && b->enabled != SD_EVENT_OFF); - t = sleep_between(e, a->time.next, b->time.next + b->time.accuracy); + t = sleep_between(e, a->time.next, time_event_source_latest(b)); if (d->next == t) return 0; @@ -2281,12 +2315,9 @@ static int source_dispatch(sd_event_source *s) { s->dispatching = false; - if (r < 0) { - if (s->description) - log_debug_errno(r, "Event source '%s' returned error, disabling: %m", s->description); - else - log_debug_errno(r, "Event source %p returned error, disabling: %m", s); - } + if (r < 0) + log_debug_errno(r, "Event source %s (type %s) returned error, disabling: %m", + strna(s->description), event_source_type_to_string(s->type)); if (s->n_ref == 0) source_free(s); @@ -2319,12 +2350,9 @@ static int event_prepare(sd_event *e) { r = s->prepare(s, s->userdata); s->dispatching = false; - if (r < 0) { - if (s->description) - log_debug_errno(r, "Prepare callback of event source '%s' returned error, disabling: %m", s->description); - else - log_debug_errno(r, "Prepare callback of event source %p returned error, disabling: %m", s); - } + if (r < 0) + log_debug_errno(r, "Prepare callback of event source %s (type %s) returned error, disabling: %m", + strna(s->description), event_source_type_to_string(s->type)); if (s->n_ref == 0) source_free(s); @@ -2609,6 +2637,18 @@ _public_ int sd_event_dispatch(sd_event *e) { return 1; } +static void event_log_delays(sd_event *e) { + char b[ELEMENTSOF(e->delays) * DECIMAL_STR_MAX(unsigned) + 1]; + unsigned i; + int o; + + for (i = o = 0; i < ELEMENTSOF(e->delays); i++) { + o += snprintf(&b[o], sizeof(b) - o, "%u ", e->delays[i]); + e->delays[i] = 0; + } + log_debug("Event loop iterations: %.*s", o, b); +} + _public_ int sd_event_run(sd_event *e, uint64_t timeout) { int r; @@ -2617,11 +2657,30 @@ _public_ int sd_event_run(sd_event *e, uint64_t timeout) { assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); + if (e->profile_delays && e->last_run) { + usec_t this_run; + unsigned l; + + this_run = now(CLOCK_MONOTONIC); + + l = u64log2(this_run - e->last_run); + assert(l < sizeof(e->delays)); + e->delays[l]++; + + if (this_run - e->last_log >= 5*USEC_PER_SEC) { + event_log_delays(e); + e->last_log = this_run; + } + } + r = sd_event_prepare(e); if (r == 0) /* There was nothing? Then wait... */ r = sd_event_wait(e, timeout); + if (e->profile_delays) + e->last_run = now(CLOCK_MONOTONIC); + if (r > 0) { /* There's something now, then let's dispatch it */ r = sd_event_dispatch(e); @@ -2698,6 +2757,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 @@ -2717,8 +2782,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-message.c b/src/libsystemd/sd-netlink/netlink-message.c index 50792bc4a3..b1b3bccc44 100644 --- a/src/libsystemd/sd-netlink/netlink-message.c +++ b/src/libsystemd/sd-netlink/netlink-message.c @@ -342,6 +342,19 @@ int sd_netlink_message_append_u32(sd_netlink_message *m, unsigned short type, ui return 0; } +int sd_netlink_message_append_data(sd_netlink_message *m, unsigned short type, const void *data, size_t len) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = add_rtattr(m, type, &data, len); + if (r < 0) + return r; + + return 0; +} + int sd_netlink_message_append_in_addr(sd_netlink_message *m, unsigned short type, const struct in_addr *data) { int r; diff --git a/src/libsystemd/sd-netlink/netlink-socket.c b/src/libsystemd/sd-netlink/netlink-socket.c index 2181201017..e95c99af0d 100644 --- a/src/libsystemd/sd-netlink/netlink-socket.c +++ b/src/libsystemd/sd-netlink/netlink-socket.c @@ -52,7 +52,7 @@ static int broadcast_groups_get(sd_netlink *nl) { int r; assert(nl); - assert(nl->fd > 0); + assert(nl->fd >= 0); r = getsockopt(nl->fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, NULL, &len); if (r < 0) { diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c index 135354e5f3..be4ab1373d 100644 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -96,15 +96,6 @@ static const NLType rtnl_link_info_data_macvlan_types[] = { [IFLA_MACVLAN_FLAGS] = { .type = NETLINK_TYPE_U16 }, }; -static const NLType rtnl_link_bridge_management_types[] = { - [IFLA_BRIDGE_FLAGS] = { .type = NETLINK_TYPE_U16 }, - [IFLA_BRIDGE_MODE] = { .type = NETLINK_TYPE_U16 }, -/* - [IFLA_BRIDGE_VLAN_INFO] = { .type = NETLINK_TYPE_BINARY, - .len = sizeof(struct bridge_vlan_info), }, -*/ -}; - static const NLType rtnl_link_info_data_bridge_types[] = { [IFLA_BR_FORWARD_DELAY] = { .type = NETLINK_TYPE_U32 }, [IFLA_BR_HELLO_TIME] = { .type = NETLINK_TYPE_U32 }, diff --git a/src/libsystemd/sd-netlink/sd-netlink.c b/src/libsystemd/sd-netlink/sd-netlink.c index 15d387df2c..d3eb379c9a 100644 --- a/src/libsystemd/sd-netlink/sd-netlink.c +++ b/src/libsystemd/sd-netlink/sd-netlink.c @@ -44,11 +44,8 @@ static int sd_netlink_new(sd_netlink **ret) { return -ENOMEM; rtnl->n_ref = REFCNT_INIT; - rtnl->fd = -1; - rtnl->sockaddr.nl.nl_family = AF_NETLINK; - rtnl->original_pid = getpid(); LIST_HEAD_INIT(rtnl->match_callbacks); @@ -87,6 +84,9 @@ int sd_netlink_new_from_netlink(sd_netlink **ret, int fd) { if (r < 0) return -errno; + if (rtnl->sockaddr.nl.nl_family != AF_NETLINK) + return -EINVAL; + rtnl->fd = fd; *ret = rtnl; @@ -118,8 +118,10 @@ int sd_netlink_open_fd(sd_netlink **ret, int fd) { rtnl->fd = fd; r = socket_bind(rtnl); - if (r < 0) + if (r < 0) { + rtnl->fd = -1; /* on failure, the caller remains owner of the fd, hence don't close it here */ return r; + } *ret = rtnl; rtnl = NULL; diff --git a/src/libsystemd/sd-network/sd-network.c b/src/libsystemd/sd-network/sd-network.c index efbceba83d..4b7fad9c81 100644 --- a/src/libsystemd/sd-network/sd-network.c +++ b/src/libsystemd/sd-network/sd-network.c @@ -95,21 +95,25 @@ _public_ int sd_network_get_ntp(char ***ret) { return network_get_strv("NTP", ret); } -_public_ int sd_network_get_domains(char ***ret) { +_public_ int sd_network_get_search_domains(char ***ret) { return network_get_strv("DOMAINS", ret); } -_public_ int sd_network_link_get_setup_state(int ifindex, char **state) { +_public_ int sd_network_get_route_domains(char ***ret) { + return network_get_strv("ROUTE_DOMAINS", ret); +} + +static int network_link_get_string(int ifindex, const char *field, char **ret) { _cleanup_free_ char *s = NULL, *p = NULL; int r; assert_return(ifindex > 0, -EINVAL); - assert_return(state, -EINVAL); + assert_return(ret, -EINVAL); - if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) + if (asprintf(&p, "/run/systemd/netif/links/%i", ifindex) < 0) return -ENOMEM; - r = parse_env_file(p, NEWLINE, "ADMIN_STATE", &s, NULL); + r = parse_env_file(p, NEWLINE, field, &s, NULL); if (r == -ENOENT) return -ENODATA; if (r < 0) @@ -117,82 +121,72 @@ _public_ int sd_network_link_get_setup_state(int ifindex, char **state) { if (isempty(s)) return -ENODATA; - *state = s; + *ret = s; s = NULL; return 0; } -_public_ int sd_network_link_get_network_file(int ifindex, char **filename) { - _cleanup_free_ char *s = NULL, *p = NULL; +static int network_link_get_strv(int ifindex, const char *key, char ***ret) { + _cleanup_free_ char *p = NULL, *s = NULL; + _cleanup_strv_free_ char **a = NULL; int r; assert_return(ifindex > 0, -EINVAL); - assert_return(filename, -EINVAL); + assert_return(ret, -EINVAL); if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) return -ENOMEM; - r = parse_env_file(p, NEWLINE, "NETWORK_FILE", &s, NULL); + r = parse_env_file(p, NEWLINE, key, &s, NULL); if (r == -ENOENT) return -ENODATA; if (r < 0) return r; - if (isempty(s)) - return -ENODATA; - - *filename = s; - s = NULL; + if (isempty(s)) { + *ret = NULL; + return 0; + } - return 0; -} + a = strv_split(s, " "); + if (!a) + return -ENOMEM; -_public_ int sd_network_link_get_operational_state(int ifindex, char **state) { - _cleanup_free_ char *s = NULL, *p = NULL; - int r; + strv_uniq(a); + r = strv_length(a); - assert_return(ifindex > 0, -EINVAL); - assert_return(state, -EINVAL); + *ret = a; + a = NULL; - if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) - return -ENOMEM; + return r; +} - r = parse_env_file(p, NEWLINE, "OPER_STATE", &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; +_public_ int sd_network_link_get_setup_state(int ifindex, char **state) { + return network_link_get_string(ifindex, "ADMIN_STATE", state); +} - *state = s; - s = NULL; +_public_ int sd_network_link_get_network_file(int ifindex, char **filename) { + return network_link_get_string(ifindex, "NETWORK_FILE", filename); +} - return 0; +_public_ int sd_network_link_get_operational_state(int ifindex, char **state) { + return network_link_get_string(ifindex, "OPER_STATE", state); } _public_ int sd_network_link_get_llmnr(int ifindex, char **llmnr) { - _cleanup_free_ char *s = NULL, *p = NULL; - int r; - - assert_return(ifindex > 0, -EINVAL); - assert_return(llmnr, -EINVAL); - - if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) - return -ENOMEM; + return network_link_get_string(ifindex, "LLMNR", llmnr); +} - r = parse_env_file(p, NEWLINE, "LLMNR", &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; +_public_ int sd_network_link_get_mdns(int ifindex, char **mdns) { + return network_link_get_string(ifindex, "MDNS", mdns); +} - *llmnr = s; - s = NULL; +_public_ int sd_network_link_get_dnssec(int ifindex, char **dnssec) { + return network_link_get_string(ifindex, "DNSSEC", dnssec); +} - return 0; +_public_ int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char ***nta) { + return network_link_get_strv(ifindex, "DNSSEC_NTA", nta); } _public_ int sd_network_link_get_lldp(int ifindex, char **lldp) { @@ -221,100 +215,31 @@ _public_ int sd_network_link_get_lldp(int ifindex, char **lldp) { } int sd_network_link_get_timezone(int ifindex, char **ret) { - _cleanup_free_ char *s = NULL, *p = NULL; - int r; - - assert_return(ifindex > 0, -EINVAL); - assert_return(ret, -EINVAL); - - if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) - return -ENOMEM; - - r = parse_env_file(p, NEWLINE, "TIMEZONE", &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; - - *ret = s; - s = NULL; - return 0; -} - -static int network_get_link_strv(const char *key, int ifindex, char ***ret) { - _cleanup_free_ char *p = NULL, *s = NULL; - _cleanup_strv_free_ char **a = NULL; - int r; - - assert_return(ifindex > 0, -EINVAL); - assert_return(ret, -EINVAL); - - if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) - return -ENOMEM; - - r = parse_env_file(p, NEWLINE, key, &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) { - *ret = NULL; - return 0; - } - - a = strv_split(s, " "); - if (!a) - return -ENOMEM; - - strv_uniq(a); - r = strv_length(a); - - *ret = a; - a = NULL; - - return r; + return network_link_get_string(ifindex, "TIMEZONE", ret); } _public_ int sd_network_link_get_dns(int ifindex, char ***ret) { - return network_get_link_strv("DNS", ifindex, ret); + return network_link_get_strv(ifindex, "DNS", ret); } _public_ int sd_network_link_get_ntp(int ifindex, char ***ret) { - return network_get_link_strv("NTP", ifindex, ret); + return network_link_get_strv(ifindex, "NTP", ret); } -_public_ int sd_network_link_get_domains(int ifindex, char ***ret) { - return network_get_link_strv("DOMAINS", ifindex, ret); +_public_ int sd_network_link_get_search_domains(int ifindex, char ***ret) { + return network_link_get_strv(ifindex, "DOMAINS", ret); } -_public_ int sd_network_link_get_carrier_bound_to(int ifindex, char ***ret) { - return network_get_link_strv("CARRIER_BOUND_TO", ifindex, ret); +_public_ int sd_network_link_get_route_domains(int ifindex, char ***ret) { + return network_link_get_strv(ifindex, "ROUTE_DOMAINS", ret); } -_public_ int sd_network_link_get_carrier_bound_by(int ifindex, char ***ret) { - return network_get_link_strv("CARRIER_BOUND_BY", ifindex, ret); +_public_ int sd_network_link_get_carrier_bound_to(int ifindex, char ***ret) { + return network_link_get_strv(ifindex, "CARRIER_BOUND_TO", ret); } -_public_ int sd_network_link_get_wildcard_domain(int ifindex) { - int r; - _cleanup_free_ char *p = NULL, *s = NULL; - - assert_return(ifindex > 0, -EINVAL); - - if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) - return -ENOMEM; - - r = parse_env_file(p, NEWLINE, "WILDCARD_DOMAIN", &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; - - return parse_boolean(s); +_public_ int sd_network_link_get_carrier_bound_by(int ifindex, char ***ret) { + return network_link_get_strv(ifindex, "CARRIER_BOUND_BY", ret); } static inline int MONITOR_TO_FD(sd_network_monitor *m) { diff --git a/src/libsystemd/sd-resolve/test-resolve.c b/src/libsystemd/sd-resolve/test-resolve.c index e78a75c9ea..ce97e81ed6 100644 --- a/src/libsystemd/sd-resolve/test-resolve.c +++ b/src/libsystemd/sd-resolve/test-resolve.c @@ -101,11 +101,11 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "sd_resolve_getnameinfo(): %m"); - /* Wait until the two queries are completed */ - while (sd_resolve_query_is_done(q1) == 0 || - sd_resolve_query_is_done(q2) == 0) { - + /* Wait until all queries are completed */ + for (;;) { r = sd_resolve_wait(resolve, (uint64_t) -1); + if (r == 0) + break; if (r < 0) { log_error_errno(r, "sd_resolve_wait(): %m"); assert_not_reached("sd_resolve_wait() failed"); diff --git a/src/locale/localed.c b/src/locale/localed.c index 5ca41331bd..8ab845eb80 100644 --- a/src/locale/localed.c +++ b/src/locale/localed.c @@ -539,7 +539,7 @@ static int read_next_mapping(const char* filename, if (!fgets(line, sizeof(line), f)) { if (ferror(f)) - return errno ? -errno : -EIO; + return errno > 0 ? -errno : -EIO; return 0; } diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 816349c559..931b96fe51 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -88,7 +88,7 @@ static OutputFlags get_output_flags(void) { arg_all * OUTPUT_SHOW_ALL | arg_full * OUTPUT_FULL_WIDTH | (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | - on_tty() * OUTPUT_COLOR; + colors_enabled() * OUTPUT_COLOR; } static int list_sessions(int argc, char *argv[], void *userdata) { diff --git a/src/login/logind-core.c b/src/login/logind-core.c index d51330fb85..2e14aa2d95 100644 --- a/src/login/logind-core.c +++ b/src/login/logind-core.c @@ -139,7 +139,7 @@ int manager_add_user_by_uid(Manager *m, uid_t uid, User **_user) { errno = 0; p = getpwuid(uid); if (!p) - return errno ? -errno : -ENOENT; + return errno > 0 ? -errno : -ENOENT; return manager_add_user(m, uid, p->pw_gid, p->pw_name, _user); } diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index c1643cf41a..ddfeaa1d87 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, @@ -1229,7 +1228,6 @@ static int attach_device(Manager *m, const char *seat, const char *sysfs) { return -ENOMEM; mkdir_p_label("/etc/udev/rules.d", 0755); - mac_selinux_init("/etc"); r = write_string_file_atomic_label(file, rule); if (r < 0) return r; @@ -1944,9 +1942,9 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ action_multiple_sessions = "org.freedesktop.login1.halt-multiple-sessions"; action_ignore_inhibit = "org.freedesktop.login1.halt-ignore-inhibit"; } else if (streq(type, "poweroff")) { - action = "org.freedesktop.login1.poweroff"; - action_multiple_sessions = "org.freedesktop.login1.poweroff-multiple-sessions"; - action_ignore_inhibit = "org.freedesktop.login1.poweroff-ignore-inhibit"; + action = "org.freedesktop.login1.power-off"; + action_multiple_sessions = "org.freedesktop.login1.power-off-multiple-sessions"; + action_ignore_inhibit = "org.freedesktop.login1.power-off-ignore-inhibit"; } else return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unsupported shutdown type"); @@ -1995,7 +1993,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 +2750,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 +2782,7 @@ int manager_start_slice( assert(manager); assert(slice); + assert(job); r = sd_bus_message_new_method_call( manager->bus, @@ -2820,22 +2836,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 +2857,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 +2932,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 +2941,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 +2955,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 +2964,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 +2979,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 +2987,7 @@ int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, c return r; } - if (job) { - const char *j; - char *copy; - - r = sd_bus_message_read(reply, "o", &j); - if (r < 0) - return r; - - copy = strdup(j); - if (!copy) - return -ENOMEM; - - *job = copy; - } - - return 1; + return strdup_job(reply, job); } int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *error) { diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c index 1f4936cebe..9d111f737c 100644 --- a/src/login/logind-seat.c +++ b/src/login/logind-seat.c @@ -34,6 +34,7 @@ #include "logind-seat.h" #include "mkdir.h" #include "parse-util.h" +#include "stdio-util.h" #include "string-util.h" #include "terminal-util.h" #include "util.h" @@ -181,7 +182,7 @@ static int vt_allocate(unsigned int vtnr) { assert(vtnr >= 1); - snprintf(p, sizeof(p), "/dev/tty%u", vtnr); + xsprintf(p, "/dev/tty%u", vtnr); fd = open_terminal(p, O_RDWR|O_NOCTTY|O_CLOEXEC); if (fd < 0) return -errno; diff --git a/src/login/logind-user.c b/src/login/logind-user.c index 4ad9740e5e..98f8ea3c78 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -412,13 +412,12 @@ static int user_start_slice(User *u) { u->manager->user_tasks_max, &error, &job); - if (r < 0) { - /* we don't fail due to this, let's try to continue */ - if (!sd_bus_error_has_name(&error, BUS_ERROR_UNIT_EXISTS)) - log_error_errno(r, "Failed to start user slice %s, ignoring: %s (%s)", u->slice, bus_error_message(&error, r), error.name); - } else { + if (r >= 0) u->slice_job = job; - } + else if (!sd_bus_error_has_name(&error, BUS_ERROR_UNIT_EXISTS)) + /* we don't fail due to this, let's try to continue */ + log_error_errno(r, "Failed to start user slice %s, ignoring: %s (%s)", + u->slice, bus_error_message(&error, r), error.name); return 0; } @@ -868,7 +867,7 @@ int config_parse_tmpfs_size( errno = 0; ul = strtoul(rvalue, &f, 10); - if (errno != 0 || f != e) { + if (errno > 0 || f != e) { log_syntax(unit, LOG_ERR, filename, line, errno, "Failed to parse percentage value, ignoring: %s", rvalue); return 0; } diff --git a/src/login/logind.c b/src/login/logind.c index 79ea5ddfcb..7e2d114194 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -36,6 +36,7 @@ #include "fd-util.h" #include "formats-util.h" #include "logind.h" +#include "selinux-util.h" #include "signal-util.h" #include "strv.h" #include "udev-util.h" @@ -70,7 +71,7 @@ static Manager *manager_new(void) { m->idle_action_not_before_usec = now(CLOCK_MONOTONIC); m->runtime_dir_size = PAGE_ALIGN((size_t) (physical_memory() / 10)); /* 10% */ - m->user_tasks_max = UINT64_C(4096); + m->user_tasks_max = UINT64_C(12288); m->devices = hashmap_new(&string_hash_ops); m->seats = hashmap_new(&string_hash_ops); @@ -1127,6 +1128,12 @@ int main(int argc, char *argv[]) { goto finish; } + r = mac_selinux_init("/run"); + if (r < 0) { + log_error_errno(r, "Could not initialize labelling: %m"); + goto finish; + } + /* Always create the directories people can create inotify * watches in. Note that some applications might check for the * existence of /run/systemd/seats/ to determine whether diff --git a/src/login/logind.conf b/src/login/logind.conf index 81f6695434..6095e482ac 100644 --- a/src/login/logind.conf +++ b/src/login/logind.conf @@ -32,4 +32,4 @@ #IdleActionSec=30min #RuntimeDirectorySize=10% #RemoveIPC=yes -#UserTasksMax=4096 +#UserTasksMax=12288 diff --git a/src/machine-id-setup/machine-id-setup-main.c b/src/machine-id-setup/machine-id-setup-main.c index d805bcfdca..9d19307236 100644 --- a/src/machine-id-setup/machine-id-setup-main.c +++ b/src/machine-id-setup/machine-id-setup-main.c @@ -112,7 +112,7 @@ int main(int argc, char *argv[]) { if (arg_commit) r = machine_id_commit(arg_root); else - r = machine_id_setup(arg_root); + r = machine_id_setup(arg_root, SD_ID128_NULL); finish: free(arg_root); diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c index 4ec1766033..19388b016a 100644 --- a/src/machine/image-dbus.c +++ b/src/machine/image-dbus.c @@ -23,6 +23,7 @@ #include "bus-label.h" #include "bus-util.h" #include "image-dbus.h" +#include "io-util.h" #include "machine-image.h" #include "strv.h" #include "user-util.h" @@ -195,6 +196,8 @@ int bus_image_method_set_limit( r = sd_bus_message_read(message, "t", &limit); if (r < 0) return r; + if (!FILE_SIZE_VALID_OR_INFINITY(limit)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range"); r = bus_verify_polkit_async( message, diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index 9f7c9952d3..03d32c6ed7 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -922,7 +922,7 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu First, we start by creating a private playground in /tmp, that we can mount MS_SLAVE. (Which is necessary, since - MS_MOUNT cannot be applied to mounts with MS_SHARED parent + MS_MOVE cannot be applied to mounts with MS_SHARED parent mounts.) */ if (!mkdtemp(mount_slave)) diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 685bbafdf1..fd454310ae 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -108,7 +108,7 @@ static OutputFlags get_output_flags(void) { arg_all * OUTPUT_SHOW_ALL | arg_full * OUTPUT_FULL_WIDTH | (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | - on_tty() * OUTPUT_COLOR | + colors_enabled() * OUTPUT_COLOR | !arg_quiet * OUTPUT_WARN_CUTOFF; } @@ -1571,7 +1571,7 @@ static int start_machine(int argc, char *argv[], void *userdata) { return log_oom(); } - r = bus_wait_for_jobs(w, arg_quiet); + r = bus_wait_for_jobs(w, arg_quiet, NULL); if (r < 0) return r; diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index c6b5b1ec44..6cb70af3aa 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -34,6 +34,7 @@ #include "formats-util.h" #include "hostname-util.h" #include "image-dbus.h" +#include "io-util.h" #include "machine-dbus.h" #include "machine-image.h" #include "machine-pool.h" @@ -813,6 +814,8 @@ static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus r = sd_bus_message_read(message, "t", &limit); if (r < 0) return r; + if (!FILE_SIZE_VALID_OR_INFINITY(limit)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range"); r = bus_verify_polkit_async( message, @@ -833,11 +836,14 @@ static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus if (r < 0) return r; - r = btrfs_resize_loopback("/var/lib/machines", limit, false); - if (r == -ENOTTY) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs."); - if (r < 0 && r != -ENODEV) /* ignore ENODEV, as that's what is returned if the file system is not on loopback */ - return sd_bus_error_set_errnof(error, r, "Failed to adjust loopback limit: %m"); + /* Resize the backing loopback device, if there is one, except if we asked to drop any limit */ + if (limit != (uint64_t) -1) { + r = btrfs_resize_loopback("/var/lib/machines", limit, false); + if (r == -ENOTTY) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs."); + if (r < 0 && r != -ENODEV) /* ignore ENODEV, as that's what is returned if the file system is not on loopback */ + return sd_bus_error_set_errnof(error, r, "Failed to adjust loopback limit: %m"); + } (void) btrfs_qgroup_set_limit("/var/lib/machines", 0, limit); @@ -910,7 +916,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 +974,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 +1034,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 +1092,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; @@ -1325,7 +1331,7 @@ int manager_start_scope( if (r < 0) return r; - r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", 8192); + r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", UINT64_C(16384)); if (r < 0) return bus_log_create_error(r); diff --git a/src/network/networkctl.c b/src/network/networkctl.c index 0234825adb..253692aa64 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -40,6 +40,7 @@ #include "pager.h" #include "parse-util.h" #include "socket-util.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -275,7 +276,8 @@ static int ieee_oui(sd_hwdb *hwdb, struct ether_addr *mac, char **ret) { if (memcmp(mac, "\0\0\0", 3) == 0) return -EINVAL; - snprintf(modalias, sizeof(modalias), "OUI:" ETHER_ADDR_FORMAT_STR, ETHER_ADDR_FORMAT_VAL(*mac)); + xsprintf(modalias, "OUI:" ETHER_ADDR_FORMAT_STR, + ETHER_ADDR_FORMAT_VAL(*mac)); r = sd_hwdb_get(hwdb, modalias, "ID_OUI_FROM_DATABASE", &description); if (r < 0) @@ -488,6 +490,9 @@ static int dump_addresses( static void dump_list(const char *prefix, char **l) { char **i; + if (strv_isempty(l)) + return; + STRV_FOREACH(i, l) { printf("%*s%s\n", (int) strlen(prefix), @@ -500,7 +505,7 @@ static int link_status_one( sd_netlink *rtnl, sd_hwdb *hwdb, const char *name) { - _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **domains = NULL; + _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **search_domains = NULL, **route_domains = NULL; _cleanup_free_ char *setup_state = NULL, *operational_state = NULL, *tz = NULL; _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; _cleanup_(sd_device_unrefp) sd_device *d = NULL; @@ -550,7 +555,6 @@ static int link_status_one( return rtnl_log_parse_error(r); have_mac = sd_netlink_message_read_ether_addr(reply, IFLA_ADDRESS, &e) >= 0; - if (have_mac) { const uint8_t *p; bool all_zeroes = true; @@ -565,44 +569,35 @@ static int link_status_one( have_mac = false; } - sd_netlink_message_read_u32(reply, IFLA_MTU, &mtu); + (void) sd_netlink_message_read_u32(reply, IFLA_MTU, &mtu); - sd_network_link_get_operational_state(ifindex, &operational_state); + (void) sd_network_link_get_operational_state(ifindex, &operational_state); operational_state_to_color(operational_state, &on_color_operational, &off_color_operational); - sd_network_link_get_setup_state(ifindex, &setup_state); + (void) sd_network_link_get_setup_state(ifindex, &setup_state); setup_state_to_color(setup_state, &on_color_setup, &off_color_setup); - sd_network_link_get_dns(ifindex, &dns); - sd_network_link_get_domains(ifindex, &domains); - r = sd_network_link_get_wildcard_domain(ifindex); - if (r > 0) { - char *wildcard; - - wildcard = strdup("*"); - if (!wildcard) - return log_oom(); - - if (strv_consume(&domains, wildcard) < 0) - return log_oom(); - } + (void) sd_network_link_get_dns(ifindex, &dns); + (void) sd_network_link_get_search_domains(ifindex, &search_domains); + (void) sd_network_link_get_route_domains(ifindex, &route_domains); + (void) sd_network_link_get_ntp(ifindex, &ntp); sprintf(devid, "n%i", ifindex); - (void)sd_device_new_from_device_id(&d, devid); + (void) sd_device_new_from_device_id(&d, devid); if (d) { - (void)sd_device_get_property_value(d, "ID_NET_LINK_FILE", &link); - (void)sd_device_get_property_value(d, "ID_NET_DRIVER", &driver); - (void)sd_device_get_property_value(d, "ID_PATH", &path); + (void) sd_device_get_property_value(d, "ID_NET_LINK_FILE", &link); + (void) sd_device_get_property_value(d, "ID_NET_DRIVER", &driver); + (void) sd_device_get_property_value(d, "ID_PATH", &path); r = sd_device_get_property_value(d, "ID_VENDOR_FROM_DATABASE", &vendor); if (r < 0) - (void)sd_device_get_property_value(d, "ID_VENDOR", &vendor); + (void) sd_device_get_property_value(d, "ID_VENDOR", &vendor); r = sd_device_get_property_value(d, "ID_MODEL_FROM_DATABASE", &model); if (r < 0) - (void)sd_device_get_property_value(d, "ID_MODEL", &model); + (void) sd_device_get_property_value(d, "ID_MODEL", &model); } link_get_type_string(iftype, d, &t); @@ -651,20 +646,14 @@ static int link_status_one( dump_addresses(rtnl, " Address: ", ifindex); dump_gateways(rtnl, hwdb, " Gateway: ", ifindex); - if (!strv_isempty(dns)) - dump_list(" DNS: ", dns); - if (!strv_isempty(domains)) - dump_list(" Domain: ", domains); + dump_list(" DNS: ", dns); + dump_list(" Search Domains: ", search_domains); + dump_list(" Route Domains: ", route_domains); - (void) sd_network_link_get_ntp(ifindex, &ntp); - if (!strv_isempty(ntp)) - dump_list(" NTP: ", ntp); + dump_list(" NTP: ", ntp); - if (!strv_isempty(carrier_bound_to)) - dump_list("Carrier Bound To: ", carrier_bound_to); - - if (!strv_isempty(carrier_bound_by)) - dump_list("Carrier Bound By: ", carrier_bound_by); + dump_list("Carrier Bound To: ", carrier_bound_to); + dump_list("Carrier Bound By: ", carrier_bound_by); (void) sd_network_link_get_timezone(ifindex, &tz); if (tz) @@ -689,30 +678,30 @@ static int link_status(int argc, char *argv[], void *userdata) { if (argc <= 1 && !arg_all) { _cleanup_free_ char *operational_state = NULL; - _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **domains = NULL; + _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **search_domains = NULL, **route_domains; const char *on_color_operational, *off_color_operational; sd_network_get_operational_state(&operational_state); operational_state_to_color(operational_state, &on_color_operational, &off_color_operational); - printf("%s%s%s State: %s%s%s\n", + printf("%s%s%s State: %s%s%s\n", on_color_operational, draw_special_char(DRAW_BLACK_CIRCLE), off_color_operational, on_color_operational, strna(operational_state), off_color_operational); - dump_addresses(rtnl, " Address: ", 0); - dump_gateways(rtnl, hwdb, " Gateway: ", 0); + dump_addresses(rtnl, " Address: ", 0); + dump_gateways(rtnl, hwdb, " Gateway: ", 0); sd_network_get_dns(&dns); - if (!strv_isempty(dns)) - dump_list(" DNS: ", dns); + dump_list(" DNS: ", dns); + + sd_network_get_search_domains(&search_domains); + dump_list("Search Domains: ", search_domains); - sd_network_get_domains(&domains); - if (!strv_isempty(domains)) - dump_list(" Domain: ", domains); + sd_network_get_route_domains(&route_domains); + dump_list(" Route Domains: ", route_domains); sd_network_get_ntp(&ntp); - if (!strv_isempty(ntp)) - dump_list(" NTP: ", ntp); + dump_list(" NTP: ", ntp); return 0; } diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 48e3d84055..59eccb392f 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -54,7 +54,7 @@ static int dhcp4_route_handler(sd_netlink *rtnl, sd_netlink_message *m, static int link_set_dhcp_routes(Link *link) { struct in_addr gateway; - struct sd_dhcp_route *static_routes; + _cleanup_free_ sd_dhcp_route **static_routes = NULL; int r, n, i; assert(link); @@ -130,9 +130,9 @@ static int link_set_dhcp_routes(Link *link) { route->family = AF_INET; route->protocol = RTPROT_DHCP; - route->gw.in = static_routes[i].gw_addr; - route->dst.in = static_routes[i].dst_addr; - route->dst_prefixlen = static_routes[i].dst_prefixlen; + assert_se(sd_dhcp_route_get_gateway(static_routes[i], &route->gw.in) >= 0); + assert_se(sd_dhcp_route_get_destination(static_routes[i], &route->dst.in) >= 0); + assert_se(sd_dhcp_route_get_destination_prefix_length(static_routes[i], &route->dst_prefixlen) >= 0); route->priority = link->network->dhcp_route_metric; r = route_configure(route, link, &dhcp4_route_handler); @@ -158,8 +158,8 @@ static int dhcp_lease_lost(Link *link) { log_link_warning(link, "DHCP lease lost"); - if (link->network->dhcp_routes) { - struct sd_dhcp_route *routes; + if (link->network->dhcp_use_routes) { + _cleanup_free_ sd_dhcp_route **routes = NULL; int n, i; n = sd_dhcp_lease_get_routes(link->dhcp_lease, &routes); @@ -170,9 +170,9 @@ static int dhcp_lease_lost(Link *link) { r = route_new(&route); if (r >= 0) { route->family = AF_INET; - route->gw.in = routes[i].gw_addr; - route->dst.in = routes[i].dst_addr; - route->dst_prefixlen = routes[i].dst_prefixlen; + assert_se(sd_dhcp_route_get_gateway(routes[i], &route->gw.in) >= 0); + assert_se(sd_dhcp_route_get_destination(routes[i], &route->dst.in) >= 0); + assert_se(sd_dhcp_route_get_destination_prefix_length(routes[i], &route->dst_prefixlen) >= 0); route_remove(route, link, &link_route_remove_handler); @@ -223,7 +223,7 @@ static int dhcp_lease_lost(Link *link) { } } - if (link->network->dhcp_mtu) { + if (link->network->dhcp_use_mtu) { uint16_t mtu; r = sd_dhcp_lease_get_mtu(link->dhcp_lease, &mtu); @@ -238,11 +238,11 @@ static int dhcp_lease_lost(Link *link) { } } - if (link->network->dhcp_hostname) { + if (link->network->dhcp_use_hostname) { const char *hostname = NULL; - if (link->network->hostname) - hostname = link->network->hostname; + if (link->network->dhcp_hostname) + hostname = link->network->dhcp_hostname; else (void) sd_dhcp_lease_get_hostname(link->dhcp_lease, &hostname); @@ -412,7 +412,7 @@ static int dhcp_lease_acquired(sd_dhcp_client *client, Link *link) { link->dhcp_lease = sd_dhcp_lease_ref(lease); link_dirty(link); - if (link->network->dhcp_mtu) { + if (link->network->dhcp_use_mtu) { uint16_t mtu; r = sd_dhcp_lease_get_mtu(lease, &mtu); @@ -423,11 +423,11 @@ static int dhcp_lease_acquired(sd_dhcp_client *client, Link *link) { } } - if (link->network->dhcp_hostname) { + if (link->network->dhcp_use_hostname) { const char *hostname = NULL; - if (link->network->hostname) - hostname = link->network->hostname; + if (link->network->dhcp_hostname) + hostname = link->network->dhcp_hostname; else (void) sd_dhcp_lease_get_hostname(lease, &hostname); @@ -438,7 +438,7 @@ static int dhcp_lease_acquired(sd_dhcp_client *client, Link *link) { } } - if (link->network->dhcp_timezone) { + if (link->network->dhcp_use_timezone) { const char *tz = NULL; (void) sd_dhcp_lease_get_timezone(link->dhcp_lease, &tz); @@ -571,45 +571,45 @@ int dhcp4_configure(Link *link) { return r; } - if (link->network->dhcp_mtu) { + if (link->network->dhcp_use_mtu) { r = sd_dhcp_client_set_request_option(link->dhcp_client, - DHCP_OPTION_INTERFACE_MTU); + SD_DHCP_OPTION_INTERFACE_MTU); if (r < 0) return r; } - if (link->network->dhcp_routes) { + if (link->network->dhcp_use_routes) { r = sd_dhcp_client_set_request_option(link->dhcp_client, - DHCP_OPTION_STATIC_ROUTE); + SD_DHCP_OPTION_STATIC_ROUTE); if (r < 0) return r; r = sd_dhcp_client_set_request_option(link->dhcp_client, - DHCP_OPTION_CLASSLESS_STATIC_ROUTE); + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE); if (r < 0) return r; } - /* Always acquire the timezone and NTP*/ - r = sd_dhcp_client_set_request_option(link->dhcp_client, DHCP_OPTION_NTP_SERVER); + /* Always acquire the timezone and NTP */ + r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_NTP_SERVER); if (r < 0) return r; - r = sd_dhcp_client_set_request_option(link->dhcp_client, DHCP_OPTION_NEW_TZDB_TIMEZONE); + r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_NEW_TZDB_TIMEZONE); if (r < 0) return r; - if (link->network->dhcp_sendhost) { + if (link->network->dhcp_send_hostname) { _cleanup_free_ char *hostname = NULL; const char *hn = NULL; - if (!link->network->hostname) { + if (!link->network->dhcp_hostname) { hostname = gethostname_malloc(); if (!hostname) return -ENOMEM; hn = hostname; } else - hn = link->network->hostname; + hn = link->network->dhcp_hostname; if (!is_localhost(hn)) { r = sd_dhcp_client_set_hostname(link->dhcp_client, hn); 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 9811526c6d..a2f0eceb6d 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -767,7 +767,7 @@ static int link_push_dns_to_dhcp_server(Link *link, sd_dhcp_server *s) { addresses[n_addresses++] = ia; } - if (link->network->dhcp_dns && + if (link->network->dhcp_use_dns && link->dhcp_lease) { const struct in_addr *da = NULL; int n; @@ -812,7 +812,7 @@ static int link_push_ntp_to_dhcp_server(Link *link, sd_dhcp_server *s) { addresses[n_addresses++] = ia; } - if (link->network->dhcp_ntp && + if (link->network->dhcp_use_ntp && link->dhcp_lease) { const struct in_addr *da = NULL; int n; @@ -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; @@ -2495,7 +2495,7 @@ int link_ipv6ll_gained(Link *link, const struct in6_addr *address) { link->ipv6ll_address = *address; link_check_ready(link); - if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_PENDING, LINK_STATE_UNMANAGED, LINK_STATE_FAILED)) { + if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_UNMANAGED, LINK_STATE_FAILED)) { r = link_acquire_ipv6_conf(link); if (r < 0) { link_enter_failed(link); @@ -2511,7 +2511,7 @@ static int link_carrier_gained(Link *link) { assert(link); - if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_PENDING, LINK_STATE_UNMANAGED, LINK_STATE_FAILED)) { + if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_UNMANAGED, LINK_STATE_FAILED)) { r = link_acquire_conf(link); if (r < 0) { link_enter_failed(link); @@ -2729,9 +2729,10 @@ int link_save(Link *link) { admin_state, oper_state); if (link->network) { - char **address, **domain; bool space; sd_dhcp6_lease *dhcp6_lease = NULL; + const char *dhcp_domainname = NULL; + char **dhcp6_domains = NULL; if (link->dhcp6_client) { r = sd_dhcp6_client_get_lease(link->dhcp6_client, &dhcp6_lease); @@ -2743,14 +2744,9 @@ int link_save(Link *link) { fputs("DNS=", f); space = false; - STRV_FOREACH(address, link->network->dns) { - if (space) - fputc(' ', f); - fputs(*address, f); - space = true; - } + fputstrv(f, link->network->dns, NULL, &space); - if (link->network->dhcp_dns && + if (link->network->dhcp_use_dns && link->dhcp_lease) { const struct in_addr *addresses; @@ -2763,7 +2759,7 @@ int link_save(Link *link) { } } - if (link->network->dhcp_dns && dhcp6_lease) { + if (link->network->dhcp_use_dns && dhcp6_lease) { struct in6_addr *in6_addrs; r = sd_dhcp6_lease_get_dns(dhcp6_lease, &in6_addrs); @@ -2778,14 +2774,9 @@ int link_save(Link *link) { fputs("NTP=", f); space = false; - STRV_FOREACH(address, link->network->ntp) { - if (space) - fputc(' ', f); - fputs(*address, f); - space = true; - } + fputstrv(f, link->network->ntp, NULL, &space); - if (link->network->dhcp_ntp && + if (link->network->dhcp_use_ntp && link->dhcp_lease) { const struct in_addr *addresses; @@ -2798,10 +2789,9 @@ int link_save(Link *link) { } } - if (link->network->dhcp_ntp && dhcp6_lease) { + if (link->network->dhcp_use_ntp && dhcp6_lease) { struct in6_addr *in6_addrs; char **hosts; - char **hostname; r = sd_dhcp6_lease_get_ntp_addrs(dhcp6_lease, &in6_addrs); @@ -2813,61 +2803,60 @@ int link_save(Link *link) { } r = sd_dhcp6_lease_get_ntp_fqdn(dhcp6_lease, &hosts); - if (r > 0) { - STRV_FOREACH(hostname, hosts) { - if (space) - fputc(' ', f); - fputs(*hostname, f); - space = true; - } - } + if (r > 0) + fputstrv(f, hosts, NULL, &space); } fputc('\n', f); - fputs("DOMAINS=", f); - space = false; - STRV_FOREACH(domain, link->network->domains) { - if (space) - fputc(' ', f); - fputs(*domain, f); - space = true; + if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) { + if (link->dhcp_lease) + (void) sd_dhcp_lease_get_domainname(link->dhcp_lease, &dhcp_domainname); + + if (dhcp6_lease) + (void) sd_dhcp6_lease_get_domains(dhcp6_lease, &dhcp6_domains); } - if (link->network->dhcp_domains && - link->dhcp_lease) { - const char *domainname; + fputs("DOMAINS=", f); + fputstrv(f, link->network->search_domains, NULL, &space); - r = sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname); - if (r >= 0) { - if (space) - fputc(' ', f); - fputs(domainname, f); - space = true; - } - } + if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES && dhcp_domainname) + fputs_with_space(f, dhcp_domainname, NULL, &space); - if (link->network->dhcp_domains && dhcp6_lease) { - char **domains; - - r = sd_dhcp6_lease_get_domains(dhcp6_lease, &domains); - if (r >= 0) { - STRV_FOREACH(domain, domains) { - if (space) - fputc(' ', f); - fputs(*domain, f); - space = true; - } - } - } + if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES && dhcp6_domains) + fputstrv(f, dhcp6_domains, NULL, &space); fputc('\n', f); - fprintf(f, "WILDCARD_DOMAIN=%s\n", - yes_no(link->network->wildcard_domain)); + fputs("ROUTE_DOMAINS=", f); + fputstrv(f, link->network->route_domains, NULL, NULL); + + if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE && dhcp_domainname) + fputs_with_space(f, dhcp_domainname, NULL, &space); + + if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE && dhcp6_domains) + fputstrv(f, dhcp6_domains, NULL, &space); + + fputc('\n', f); fprintf(f, "LLMNR=%s\n", resolve_support_to_string(link->network->llmnr)); + fprintf(f, "MDNS=%s\n", + resolve_support_to_string(link->network->mdns)); + + if (link->network->dnssec_mode != _DNSSEC_MODE_INVALID) + fprintf(f, "DNSSEC=%s\n", + dnssec_mode_to_string(link->network->dnssec_mode)); + + if (!set_isempty(link->network->dnssec_negative_trust_anchors)) { + const char *n; + + fputs("DNSSEC_NTA=", f); + space = false; + SET_FOREACH(n, link->network->dnssec_negative_trust_anchors, i) + fputs_with_space(f, n, NULL, &space); + fputc('\n', f); + } fputs("ADDRESSES=", f); space = false; @@ -2881,7 +2870,6 @@ int link_save(Link *link) { fprintf(f, "%s%s/%u", space ? " " : "", address_str, a->prefixlen); space = true; } - fputc('\n', f); fputs("ROUTES=", f); @@ -2906,12 +2894,8 @@ int link_save(Link *link) { bool space = false; fputs("CARRIER_BOUND_TO=", f); - HASHMAP_FOREACH(carrier, link->bound_to_links, i) { - if (space) - fputc(' ', f); - fputs(carrier->ifname, f); - space = true; - } + HASHMAP_FOREACH(carrier, link->bound_to_links, i) + fputs_with_space(f, carrier->ifname, NULL, &space); fputc('\n', f); } @@ -2921,12 +2905,8 @@ int link_save(Link *link) { bool space = false; fputs("CARRIER_BOUND_BY=", f); - HASHMAP_FOREACH(carrier, link->bound_by_links, i) { - if (space) - fputc(' ', f); - fputs(carrier->ifname, f); - space = true; - } + HASHMAP_FOREACH(carrier, link->bound_by_links, i) + fputs_with_space(f, carrier->ifname, NULL, &space); fputc('\n', f); } diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index aeb6e34c52..c10635d202 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -29,12 +29,14 @@ #include "bus-util.h" #include "conf-parser.h" #include "def.h" +#include "dns-domain.h" #include "fd-util.h" #include "fileio.h" #include "libudev-private.h" #include "local-addresses.h" #include "netlink-util.h" #include "networkd.h" +#include "ordered-set.h" #include "path-util.h" #include "set.h" #include "udev-util.h" @@ -659,15 +661,16 @@ static int manager_rtnl_process_link(sd_netlink *rtnl, sd_netlink_message *messa } else if (ifindex <= 0) { log_warning("rtnl: received link message with invalid ifindex: %d", ifindex); return 0; - } else - link_get(m, ifindex, &link); + } r = sd_netlink_message_read_string(message, IFLA_IFNAME, &name); if (r < 0) { log_warning_errno(r, "rtnl: Received link message without ifname: %m"); return 0; - } else - netdev_get(m, name, &netdev); + } + + (void) link_get(m, ifindex, &link); + (void) netdev_get(m, name, &netdev); switch (type) { case RTM_NEWLINK: @@ -775,7 +778,7 @@ static int manager_connect_rtnl(Manager *m) { return 0; } -static int set_put_in_addr(Set *s, const struct in_addr *address) { +static int ordered_set_put_in_addr(OrderedSet *s, const struct in_addr *address) { char *p; int r; @@ -785,21 +788,21 @@ static int set_put_in_addr(Set *s, const struct in_addr *address) { if (r < 0) return r; - r = set_consume(s, p); + r = ordered_set_consume(s, p); if (r == -EEXIST) return 0; return r; } -static int set_put_in_addrv(Set *s, const struct in_addr *addresses, int n) { +static int ordered_set_put_in_addrv(OrderedSet *s, const struct in_addr *addresses, int n) { int r, i, c = 0; assert(s); assert(n <= 0 || addresses); for (i = 0; i < n; i++) { - r = set_put_in_addr(s, addresses+i); + r = ordered_set_put_in_addr(s, addresses+i); if (r < 0) return r; @@ -809,27 +812,24 @@ static int set_put_in_addrv(Set *s, const struct in_addr *addresses, int n) { return c; } -static void print_string_set(FILE *f, const char *field, Set *s) { +static void print_string_set(FILE *f, const char *field, OrderedSet *s) { bool space = false; Iterator i; char *p; - if (set_isempty(s)) + if (ordered_set_isempty(s)) return; fputs(field, f); - SET_FOREACH(p, s, i) { - if (space) - fputc(' ', f); - fputs(p, f); - space = true; - } + ORDERED_SET_FOREACH(p, s, i) + fputs_with_space(f, p, NULL, &space); + fputc('\n', f); } static int manager_save(Manager *m) { - _cleanup_set_free_free_ Set *dns = NULL, *ntp = NULL, *domains = NULL; + _cleanup_ordered_set_free_free_ OrderedSet *dns = NULL, *ntp = NULL, *search_domains = NULL, *route_domains = NULL; Link *link; Iterator i; _cleanup_free_ char *temp_path = NULL; @@ -842,16 +842,20 @@ static int manager_save(Manager *m) { assert(m->state_file); /* We add all NTP and DNS server to a set, to filter out duplicates */ - dns = set_new(&string_hash_ops); + dns = ordered_set_new(&string_hash_ops); if (!dns) return -ENOMEM; - ntp = set_new(&string_hash_ops); + ntp = ordered_set_new(&string_hash_ops); if (!ntp) return -ENOMEM; - domains = set_new(&string_hash_ops); - if (!domains) + search_domains = ordered_set_new(&dns_name_hash_ops); + if (!search_domains) + return -ENOMEM; + + route_domains = ordered_set_new(&dns_name_hash_ops); + if (!route_domains) return -ENOMEM; HASHMAP_FOREACH(link, m->links, i) { @@ -865,15 +869,19 @@ static int manager_save(Manager *m) { continue; /* First add the static configured entries */ - r = set_put_strdupv(dns, link->network->dns); + r = ordered_set_put_strdupv(dns, link->network->dns); + if (r < 0) + return r; + + r = ordered_set_put_strdupv(ntp, link->network->ntp); if (r < 0) return r; - r = set_put_strdupv(ntp, link->network->ntp); + r = ordered_set_put_strdupv(search_domains, link->network->search_domains); if (r < 0) return r; - r = set_put_strdupv(domains, link->network->domains); + r = ordered_set_put_strdupv(route_domains, link->network->route_domains); if (r < 0) return r; @@ -881,36 +889,41 @@ static int manager_save(Manager *m) { continue; /* Secondly, add the entries acquired via DHCP */ - if (link->network->dhcp_dns) { + if (link->network->dhcp_use_dns) { const struct in_addr *addresses; r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses); if (r > 0) { - r = set_put_in_addrv(dns, addresses, r); + r = ordered_set_put_in_addrv(dns, addresses, r); if (r < 0) return r; } else if (r < 0 && r != -ENODATA) return r; } - if (link->network->dhcp_ntp) { + if (link->network->dhcp_use_ntp) { const struct in_addr *addresses; r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses); if (r > 0) { - r = set_put_in_addrv(ntp, addresses, r); + r = ordered_set_put_in_addrv(ntp, addresses, r); if (r < 0) return r; } else if (r < 0 && r != -ENODATA) return r; } - if (link->network->dhcp_domains) { + if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) { const char *domainname; r = sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname); if (r >= 0) { - r = set_put_strdup(domains, domainname); + + if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES) + r = ordered_set_put_strdup(search_domains, domainname); + else + r = ordered_set_put_strdup(route_domains, domainname); + if (r < 0) return r; } else if (r != -ENODATA) @@ -933,7 +946,8 @@ static int manager_save(Manager *m) { print_string_set(f, "DNS=", dns); print_string_set(f, "NTP=", ntp); - print_string_set(f, "DOMAINS=", domains); + print_string_set(f, "DOMAINS=", search_domains); + print_string_set(f, "ROUTE_DOMAINS=", route_domains); r = fflush_and_check(f); if (r < 0) diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index ce9e513ceb..483efd17c3 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -89,7 +89,7 @@ static void ndisc_prefix_autonomous_handler(sd_ndisc *nd, const struct in6_addr address->in_addr.in6.__in6_u.__u6_addr8[15] = link->mac.ether_addr_octet[5]; } address->prefixlen = prefixlen; - address->flags = IFA_F_NOPREFIXROUTE; + address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR; address->cinfo.ifa_prefered = lifetime_preferred; address->cinfo.ifa_valid = lifetime_valid; diff --git a/src/network/networkd-netdev-gperf.gperf b/src/network/networkd-netdev-gperf.gperf index 4a4b400e41..8f506af092 100644 --- a/src/network/networkd-netdev-gperf.gperf +++ b/src/network/networkd-netdev-gperf.gperf @@ -57,6 +57,8 @@ VXLAN.UDP6ZeroCheckSumTx, config_parse_bool, 0, VXLAN.FDBAgeingSec, config_parse_sec, 0, offsetof(VxLan, fdb_ageing) VXLAN.GroupPolicyExtension, config_parse_bool, 0, offsetof(VxLan, group_policy) VXLAN.MaximumFDBEntries, config_parse_unsigned, 0, offsetof(VxLan, max_fdb) +VXLAN.PortRange, config_parse_port_range, 0, 0 +VXLAN.DestinationPort, config_parse_destination_port, 0, offsetof(VxLan, dest_port) Tun.OneQueue, config_parse_bool, 0, offsetof(TunTap, one_queue) Tun.MultiQueue, config_parse_bool, 0, offsetof(TunTap, multi_queue) Tun.PacketInfo, config_parse_bool, 0, offsetof(TunTap, packet_info) diff --git a/src/network/networkd-netdev-tunnel.c b/src/network/networkd-netdev-tunnel.c index 385338849f..a2c00161d3 100644 --- a/src/network/networkd-netdev-tunnel.c +++ b/src/network/networkd-netdev-tunnel.c @@ -56,7 +56,7 @@ static int netdev_ipip_fill_message_create(NetDev *netdev, Link *link, sd_netlin assert(link); assert(m); assert(t); - assert(t->family == AF_INET); + assert(t->family == AF_INET || t->family != -1); r = sd_netlink_message_append_u32(m, IFLA_IPTUN_LINK, link->ifindex); if (r < 0) @@ -89,7 +89,7 @@ static int netdev_sit_fill_message_create(NetDev *netdev, Link *link, sd_netlink assert(link); assert(m); assert(t); - assert(t->family == AF_INET); + assert(t->family == AF_INET || t->family != -1); r = sd_netlink_message_append_u32(m, IFLA_IPTUN_LINK, link->ifindex); if (r < 0) @@ -126,7 +126,7 @@ static int netdev_gre_fill_message_create(NetDev *netdev, Link *link, sd_netlink t = GRETAP(netdev); assert(t); - assert(t->family == AF_INET); + assert(t->family == AF_INET || t->family != -1); assert(link); assert(m); @@ -358,12 +358,7 @@ static int netdev_tunnel_verify(NetDev *netdev, const char *filename) { assert(t); - if (t->remote.in.s_addr == INADDR_ANY) { - log_warning("Tunnel without remote address configured in %s. Ignoring", filename); - return -EINVAL; - } - - if (t->family != AF_INET && t->family != AF_INET6) { + if (t->family != AF_INET && t->family != AF_INET6 && t->family != 0) { log_warning("Tunnel with invalid address family configured in %s. Ignoring", filename); return -EINVAL; } @@ -397,15 +392,21 @@ int config_parse_tunnel_address(const char *unit, assert(rvalue); assert(data); - r = in_addr_from_string_auto(rvalue, &f, &buffer); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Tunnel address is invalid, ignoring assignment: %s", rvalue); + if (streq(rvalue, "any")) { + t->family = 0; return 0; - } + } else { - if (t->family != AF_UNSPEC && t->family != f) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Tunnel addresses incompatible, ignoring assignment: %s", rvalue); - return 0; + r = in_addr_from_string_auto(rvalue, &f, &buffer); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Tunnel address is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + if (t->family != AF_UNSPEC && t->family != f) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Tunnel addresses incompatible, ignoring assignment: %s", rvalue); + return 0; + } } t->family = f; @@ -498,6 +499,7 @@ static void ipip_init(NetDev *n) { assert(t); t->pmtudisc = true; + t->family = -1; } static void sit_init(NetDev *n) { @@ -507,6 +509,7 @@ static void sit_init(NetDev *n) { assert(t); t->pmtudisc = true; + t->family = -1; } static void vti_init(NetDev *n) { @@ -537,6 +540,7 @@ static void gre_init(NetDev *n) { assert(t); t->pmtudisc = true; + t->family = -1; } static void ip6gre_init(NetDev *n) { diff --git a/src/network/networkd-netdev-vxlan.c b/src/network/networkd-netdev-vxlan.c index 7932b93335..531f2c300e 100644 --- a/src/network/networkd-netdev-vxlan.c +++ b/src/network/networkd-netdev-vxlan.c @@ -24,6 +24,8 @@ #include "sd-netlink.h" #include "conf-parser.h" +#include "alloc-util.h" +#include "parse-util.h" #include "missing.h" #include "networkd-link.h" #include "networkd-netdev-vxlan.h" @@ -110,6 +112,21 @@ static int netdev_vxlan_fill_message_create(NetDev *netdev, Link *link, sd_netli if (r < 0) return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_UDP_ZERO_CSUM6_RX attribute: %m"); + r = sd_netlink_message_append_u16(m, IFLA_VXLAN_PORT, htobe16(v->dest_port)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_PORT attribute: %m"); + + if (v->port_range.low || v->port_range.high) { + struct ifla_vxlan_port_range port_range; + + port_range.low = htobe16(v->port_range.low); + port_range.high = htobe16(v->port_range.high); + + r = sd_netlink_message_append_data(m, IFLA_VXLAN_PORT_RANGE, &port_range, sizeof(port_range)); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_PORT_RANGE attribute: %m"); + } + if (v->group_policy) { r = sd_netlink_message_append_flag(m, IFLA_VXLAN_GBP); if (r < 0) @@ -155,6 +172,89 @@ int config_parse_vxlan_group_address(const char *unit, return 0; } +int config_parse_port_range(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + _cleanup_free_ char *word = NULL; + VxLan *v = userdata; + unsigned low, high; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = extract_first_word(&rvalue, &word, NULL, 0); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract VXLAN port range, ignoring: %s", rvalue); + return 0; + } + + if (r == 0) + return 0; + + r = parse_range(word, &low, &high); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse VXLAN port range '%s'", word); + return 0; + } + + if (low <= 0 || low > 65535 || high <= 0 || high > 65535) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to parse VXLAN port range '%s'. Port should be greater than 0 and less than 65535.", word); + return 0; + } + + if (high < low) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to parse VXLAN port range '%s'. Port range %u .. %u not valid", word, low, high); + return 0; + } + + v->port_range.low = low; + v->port_range.high = high; + + return 0; +} + +int config_parse_destination_port(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + VxLan *v = userdata; + uint16_t port; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atou16(rvalue, &port); + if (r < 0 || port <= 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse VXLAN destination port '%s'.", rvalue); + return 0; + } + + v->dest_port = port; + + return 0; +} + static int netdev_vxlan_verify(NetDev *netdev, const char *filename) { VxLan *v = VXLAN(netdev); diff --git a/src/network/networkd-netdev-vxlan.h b/src/network/networkd-netdev-vxlan.h index 16977ea6a9..00142968ae 100644 --- a/src/network/networkd-netdev-vxlan.h +++ b/src/network/networkd-netdev-vxlan.h @@ -40,6 +40,8 @@ struct VxLan { unsigned ttl; unsigned max_fdb; + uint16_t dest_port; + usec_t fdb_ageing; bool learning; @@ -51,6 +53,8 @@ struct VxLan { bool udp6zerocsumtx; bool udp6zerocsumrx; bool group_policy; + + struct ifla_vxlan_port_range port_range; }; extern const NetDevVTable vxlan_vtable; @@ -65,3 +69,24 @@ int config_parse_vxlan_group_address(const char *unit, const char *rvalue, void *data, void *userdata); +int config_parse_port_range(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata); + +int config_parse_destination_port(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index de2c66d153..409df1709f 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -43,9 +43,12 @@ Network.IPv6Token, config_parse_ipv6token, Network.LLDP, config_parse_bool, 0, offsetof(Network, lldp) Network.Address, config_parse_address, 0, 0 Network.Gateway, config_parse_gateway, 0, 0 -Network.Domains, config_parse_domains, 0, offsetof(Network, domains) +Network.Domains, config_parse_domains, 0, 0 Network.DNS, config_parse_strv, 0, offsetof(Network, dns) -Network.LLMNR, config_parse_resolve, 0, offsetof(Network, llmnr) +Network.LLMNR, config_parse_resolve_support, 0, offsetof(Network, llmnr) +Network.MulticastDNS, config_parse_resolve_support, 0, offsetof(Network, mdns) +Network.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Network, dnssec_mode) +Network.DNSSECNegativeTrustAnchors, config_parse_dnssec_negative_trust_anchors, 0, offsetof(Network, dnssec_negative_trust_anchors) Network.NTP, config_parse_strv, 0, offsetof(Network, ntp) Network.IPForward, config_parse_address_family_boolean_with_kernel,0, offsetof(Network, ip_forward) Network.IPMasquerade, config_parse_bool, 0, offsetof(Network, ip_masquerade) @@ -65,19 +68,19 @@ Route.Metric, config_parse_route_priority, Route.Scope, config_parse_route_scope, 0, 0 Route.PreferredSource, config_parse_preferred_src, 0, 0 DHCP.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier) -DHCP.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_dns) -DHCP.UseNTP, config_parse_bool, 0, offsetof(Network, dhcp_ntp) -DHCP.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_mtu) -DHCP.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_hostname) -DHCP.UseDomains, config_parse_bool, 0, offsetof(Network, dhcp_domains) -DHCP.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_routes) -DHCP.SendHostname, config_parse_bool, 0, offsetof(Network, dhcp_sendhost) -DHCP.Hostname, config_parse_hostname, 0, offsetof(Network, hostname) +DHCP.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns) +DHCP.UseNTP, config_parse_bool, 0, offsetof(Network, dhcp_use_ntp) +DHCP.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu) +DHCP.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname) +DHCP.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains) +DHCP.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_use_routes) +DHCP.SendHostname, config_parse_bool, 0, offsetof(Network, dhcp_send_hostname) +DHCP.Hostname, config_parse_hostname, 0, offsetof(Network, dhcp_hostname) DHCP.RequestBroadcast, config_parse_bool, 0, offsetof(Network, dhcp_broadcast) DHCP.CriticalConnection, config_parse_bool, 0, offsetof(Network, dhcp_critical) DHCP.VendorClassIdentifier, config_parse_string, 0, offsetof(Network, dhcp_vendor_class_identifier) DHCP.RouteMetric, config_parse_unsigned, 0, offsetof(Network, dhcp_route_metric) -DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_timezone) +DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone) DHCPServer.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec) DHCPServer.DefaultLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_default_lease_time_usec) DHCPServer.EmitDNS, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_dns) @@ -98,9 +101,9 @@ BridgeFDB.MACAddress, config_parse_fdb_hwaddr, BridgeFDB.VLANId, config_parse_fdb_vlan_id, 0, 0 /* backwards compatibility: do not add new entries to this section */ Network.IPv4LL, config_parse_ipv4ll, 0, offsetof(Network, link_local) -DHCPv4.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_dns) -DHCPv4.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_mtu) -DHCPv4.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_hostname) -DHCP.UseDomainName, config_parse_bool, 0, offsetof(Network, dhcp_domains) -DHCPv4.UseDomainName, config_parse_bool, 0, offsetof(Network, dhcp_domains) +DHCPv4.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns) +DHCPv4.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu) +DHCPv4.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname) +DHCP.UseDomainName, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains) +DHCPv4.UseDomainName, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains) DHCPv4.CriticalConnection, config_parse_bool, 0, offsetof(Network, dhcp_critical) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 29723a852f..e1a811129d 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -32,6 +32,7 @@ #include "networkd-network.h" #include "networkd.h" #include "parse-util.h" +#include "set.h" #include "stat-util.h" #include "string-table.h" #include "string-util.h" @@ -104,11 +105,11 @@ static int network_load_one(Manager *manager, const char *filename) { *d = '\0'; network->dhcp = ADDRESS_FAMILY_NO; - network->dhcp_ntp = true; - network->dhcp_dns = true; - network->dhcp_hostname = true; - network->dhcp_routes = true; - network->dhcp_sendhost = true; + network->dhcp_use_ntp = true; + network->dhcp_use_dns = true; + network->dhcp_use_hostname = true; + network->dhcp_use_routes = true; + network->dhcp_send_hostname = true; network->dhcp_route_metric = DHCP_ROUTE_METRIC; network->dhcp_client_identifier = DHCP_CLIENT_ID_DUID; @@ -121,6 +122,8 @@ static int network_load_one(Manager *manager, const char *filename) { network->unicast_flood = true; network->llmnr = RESOLVE_SUPPORT_YES; + network->mdns = RESOLVE_SUPPORT_NO; + network->dnssec_mode = _DNSSEC_MODE_INVALID; network->link_local = ADDRESS_FAMILY_IPV6; @@ -224,13 +227,14 @@ void network_free(Network *network) { free(network->description); free(network->dhcp_vendor_class_identifier); - free(network->hostname); + free(network->dhcp_hostname); free(network->mac); strv_free(network->ntp); strv_free(network->dns); - strv_free(network->domains); + strv_free(network->search_domains); + strv_free(network->route_domains); strv_free(network->bind_carrier); netdev_unref(network->bridge); @@ -275,6 +279,8 @@ void network_free(Network *network) { free(network->dhcp_server_dns); free(network->dhcp_server_ntp); + set_free_free(network->dnssec_negative_trust_anchors); + free(network); } @@ -379,7 +385,10 @@ int network_apply(Manager *manager, Network *network, Link *link) { route->protocol = RTPROT_STATIC; } - if (network->dns || network->ntp || network->domains) { + if (!strv_isempty(network->dns) || + !strv_isempty(network->ntp) || + !strv_isempty(network->search_domains) || + !strv_isempty(network->route_domains)) { manager_dirty(manager); link_dirty(link); } @@ -464,49 +473,85 @@ int config_parse_netdev(const char *unit, return 0; } -int config_parse_domains(const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - Network *network = userdata; - char ***domains = data; - char **domain; +int config_parse_domains( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + const char *p; + Network *n = data; int r; - r = config_parse_strv(unit, filename, line, section, section_line, - lvalue, ltype, rvalue, domains, userdata); - if (r < 0) - return r; + assert(n); + assert(lvalue); + assert(rvalue); - strv_uniq(*domains); - network->wildcard_domain = !!strv_find(*domains, "*"); + if (isempty(rvalue)) { + n->search_domains = strv_free(n->search_domains); + n->route_domains = strv_free(n->route_domains); + return 0; + } - STRV_FOREACH(domain, *domains) { - if (is_localhost(*domain)) - log_syntax(unit, LOG_ERR, filename, line, 0, "'localhost' domain names may not be configured, ignoring assignment: %s", *domain); - else { - r = dns_name_is_valid(*domain); - if (r <= 0 && !streq(*domain, "*")) { - if (r < 0) - log_error_errno(r, "Failed to validate domain name: %s: %m", *domain); - if (r == 0) - log_warning("Domain name is not valid, ignoring assignment: %s", *domain); - } else + p = rvalue; + for (;;) { + _cleanup_free_ char *w = NULL, *normalized = NULL; + const char *domain; + bool is_route; + + r = extract_first_word(&p, &w, NULL, 0); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract search or route domain, ignoring: %s", rvalue); + break; + } + if (r == 0) + break; + + is_route = w[0] == '~'; + domain = is_route ? w + 1 : w; + + if (dns_name_is_root(domain) || streq(domain, "*")) { + /* If the root domain appears as is, or the special token "*" is found, we'll consider this as + * routing domain, unconditionally. */ + is_route = true; + domain = "."; /* make sure we don't allow empty strings, thus write the root domain as "." */ + + } else { + r = dns_name_normalize(domain, &normalized); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "'%s' is not a valid domain name, ignoring.", domain); continue; + } + + domain = normalized; + + if (is_localhost(domain)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "'localhost' domain names may not be configure as search or route domains, ignoring assignment: %s", domain); + continue; + } } - strv_remove(*domains, *domain); + if (is_route) { + r = strv_extend(&n->route_domains, domain); + if (r < 0) + return log_oom(); - /* We removed one entry, make sure we don't skip the next one */ - domain--; + } else { + r = strv_extend(&n->search_domains, domain); + if (r < 0) + return log_oom(); + } } + strv_uniq(n->route_domains); + strv_uniq(n->search_domains); + return 0; } @@ -908,3 +953,65 @@ int config_parse_dhcp_server_ntp( n->dhcp_server_ntp = m; } } + +int config_parse_dnssec_negative_trust_anchors( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + const char *p = rvalue; + Network *n = data; + int r; + + assert(n); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + n->dnssec_negative_trust_anchors = set_free_free(n->dnssec_negative_trust_anchors); + return 0; + } + + for (;;) { + _cleanup_free_ char *w = NULL; + + r = extract_first_word(&p, &w, NULL, 0); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract negative trust anchor domain, ignoring: %s", rvalue); + break; + } + if (r == 0) + break; + + r = dns_name_is_valid(w); + if (r <= 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "%s is not a valid domain name, ignoring.", w); + continue; + } + + r = set_put(n->dnssec_negative_trust_anchors, w); + if (r < 0) + return log_oom(); + if (r > 0) + w = NULL; + } + + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_dhcp_use_domains, dhcp_use_domains, DHCPUseDomains, "Failed to parse DHCP use domains setting"); + +static const char* const dhcp_use_domains_table[_DHCP_USE_DOMAINS_MAX] = { + [DHCP_USE_DOMAINS_NO] = "no", + [DHCP_USE_DOMAINS_ROUTE] = "route", + [DHCP_USE_DOMAINS_YES] = "yes", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dhcp_use_domains, DHCPUseDomains, DHCP_USE_DOMAINS_YES); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index cb3a50d9ba..626dfbd40a 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -22,6 +22,7 @@ ***/ #include "condition.h" +#include "resolve-util.h" typedef struct Network Network; @@ -51,6 +52,14 @@ typedef enum IPv6PrivacyExtensions { _IPV6_PRIVACY_EXTENSIONS_INVALID = -1, } IPv6PrivacyExtensions; +typedef enum DHCPUseDomains { + DHCP_USE_DOMAINS_NO, + DHCP_USE_DOMAINS_YES, + DHCP_USE_DOMAINS_ROUTE, + _DHCP_USE_DOMAINS_MAX, + _DHCP_USE_DOMAINS_INVALID = -1, +} DHCPUseDomains; + struct Network { Manager *manager; @@ -78,17 +87,17 @@ struct Network { AddressFamilyBoolean dhcp; DCHPClientIdentifier dhcp_client_identifier; char *dhcp_vendor_class_identifier; - char *hostname; - bool dhcp_dns; - bool dhcp_ntp; - bool dhcp_mtu; - bool dhcp_hostname; - bool dhcp_domains; - bool dhcp_sendhost; + char *dhcp_hostname; + bool dhcp_use_dns; + bool dhcp_use_ntp; + bool dhcp_use_mtu; + bool dhcp_use_hostname; + DHCPUseDomains dhcp_use_domains; + bool dhcp_send_hostname; bool dhcp_broadcast; bool dhcp_critical; - bool dhcp_routes; - bool dhcp_timezone; + bool dhcp_use_routes; + bool dhcp_use_timezone; unsigned dhcp_route_metric; /* DHCP Server Support */ @@ -140,10 +149,12 @@ struct Network { Hashmap *routes_by_section; Hashmap *fdb_entries_by_section; - bool wildcard_domain; - char **domains, **dns, **ntp, **bind_carrier; + char **search_domains, **route_domains, **dns, **ntp, **bind_carrier; ResolveSupport llmnr; + ResolveSupport mdns; + DnssecMode dnssec_mode; + Set *dnssec_negative_trust_anchors; LIST_FIELDS(Network, networks); }; @@ -170,6 +181,8 @@ int config_parse_hostname(const char *unit, const char *filename, unsigned line, int config_parse_timezone(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_dhcp_server_dns(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_dhcp_server_ntp(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_dnssec_negative_trust_anchors(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_dhcp_use_domains(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); /* Legacy IPv4LL support */ int config_parse_ipv4ll(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); @@ -183,3 +196,6 @@ int network_object_find(sd_bus *bus, const char *path, const char *interface, vo const char* ipv6_privacy_extensions_to_string(IPv6PrivacyExtensions i) _const_; IPv6PrivacyExtensions ipv6_privacy_extensions_from_string(const char *s) _pure_; + +const char* dhcp_use_domains_to_string(DHCPUseDomains p) _const_; +DHCPUseDomains dhcp_use_domains_from_string(const char *s) _pure_; diff --git a/src/network/networkd-util.c b/src/network/networkd-util.c index 2545621a93..93135bb658 100644 --- a/src/network/networkd-util.c +++ b/src/network/networkd-util.c @@ -101,54 +101,3 @@ int config_parse_address_family_boolean_with_kernel( return 0; } - -static const char* const resolve_support_table[_RESOLVE_SUPPORT_MAX] = { - [RESOLVE_SUPPORT_NO] = "no", - [RESOLVE_SUPPORT_YES] = "yes", - [RESOLVE_SUPPORT_RESOLVE] = "resolve", -}; - -DEFINE_STRING_TABLE_LOOKUP(resolve_support, ResolveSupport); - -int config_parse_resolve( - const char* unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - ResolveSupport *resolve = data; - int k; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(resolve); - - /* Our enum shall be a superset of booleans, hence first try - * to parse as boolean, and then as enum */ - - k = parse_boolean(rvalue); - if (k > 0) - *resolve = RESOLVE_SUPPORT_YES; - else if (k == 0) - *resolve = RESOLVE_SUPPORT_NO; - else { - ResolveSupport s; - - s = resolve_support_from_string(rvalue); - if (s < 0){ - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse %s= option, ignoring: %s", lvalue, rvalue); - return 0; - } - - *resolve = s; - } - - return 0; -} diff --git a/src/network/networkd-util.h b/src/network/networkd-util.h index cc41aae85a..021ce4b128 100644 --- a/src/network/networkd-util.h +++ b/src/network/networkd-util.h @@ -33,20 +33,8 @@ typedef enum AddressFamilyBoolean { _ADDRESS_FAMILY_BOOLEAN_INVALID = -1, } AddressFamilyBoolean; -typedef enum ResolveSupport { - RESOLVE_SUPPORT_NO, - RESOLVE_SUPPORT_YES, - RESOLVE_SUPPORT_RESOLVE, - _RESOLVE_SUPPORT_MAX, - _RESOLVE_SUPPORT_INVALID = -1, -} ResolveSupport; - -int config_parse_resolve(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_address_family_boolean(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_address_family_boolean_with_kernel(const char* unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -const char* resolve_support_to_string(ResolveSupport i) _const_; -ResolveSupport resolve_support_from_string(const char *s) _pure_; - const char *address_family_boolean_to_string(AddressFamilyBoolean b) _const_; AddressFamilyBoolean address_family_boolean_from_string(const char *s) _const_; diff --git a/src/nspawn/nspawn-cgroup.c b/src/nspawn/nspawn-cgroup.c index 270bcf010f..3c0e26ea5a 100644 --- a/src/nspawn/nspawn-cgroup.c +++ b/src/nspawn/nspawn-cgroup.c @@ -54,6 +54,7 @@ int chown_cgroup(pid_t pid, uid_t uid_shift) { "tasks", "notify_on_release", "cgroup.procs", + "cgroup.events", "cgroup.clone_children", "cgroup.controllers", "cgroup.subtree_control", diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf index 58f9f4c635..116655cdd2 100644 --- a/src/nspawn/nspawn-gperf.gperf +++ b/src/nspawn/nspawn-gperf.gperf @@ -15,7 +15,8 @@ struct ConfigPerfItem; %struct-type %includes %% -Exec.Boot, config_parse_tristate, 0, offsetof(Settings, boot) +Exec.Boot, config_parse_boot, 0, 0 +Exec.ProcessTwo, config_parse_pid2, 0, 0, Exec.Parameters, config_parse_strv, 0, offsetof(Settings, parameters) Exec.Environment, config_parse_strv, 0, offsetof(Settings, environment) Exec.User, config_parse_string, 0, offsetof(Settings, user) @@ -24,6 +25,7 @@ Exec.DropCapability, config_parse_capability, 0, offsetof(Settings, Exec.KillSignal, config_parse_signal, 0, offsetof(Settings, kill_signal) Exec.Personality, config_parse_personality, 0, offsetof(Settings, personality) Exec.MachineID, config_parse_id128, 0, offsetof(Settings, machine_id) +Exec.WorkingDirectory, config_parse_path, 0, offsetof(Settings, working_directory) Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only) Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode) Files.Bind, config_parse_bind, 0, 0 diff --git a/src/nspawn/nspawn-register.c b/src/nspawn/nspawn-register.c index 65ca9c762b..a89de4b324 100644 --- a/src/nspawn/nspawn-register.c +++ b/src/nspawn/nspawn-register.c @@ -166,17 +166,9 @@ int register_machine( } STRV_FOREACH(i, properties) { - r = sd_bus_message_open_container(m, 'r', "sv"); - if (r < 0) - return bus_log_create_error(r); - r = bus_append_unit_property_assignment(m, *i); if (r < 0) return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); } r = sd_bus_message_close_container(m); diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index d6b64d8d5a..12524d3b89 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -24,6 +24,7 @@ #include "conf-parser.h" #include "nspawn-network.h" #include "nspawn-settings.h" +#include "parse-util.h" #include "process-util.h" #include "strv.h" #include "util.h" @@ -39,7 +40,7 @@ int settings_load(FILE *f, const char *path, Settings **ret) { if (!s) return -ENOMEM; - s->boot = -1; + s->start_mode = _START_MODE_INVALID; s->personality = PERSONALITY_INVALID; s->read_only = -1; @@ -74,6 +75,7 @@ Settings* settings_free(Settings *s) { strv_free(s->parameters); strv_free(s->environment); free(s->user); + free(s->working_directory); strv_free(s->network_interfaces); strv_free(s->network_macvlan); @@ -302,3 +304,93 @@ int config_parse_veth_extra( return 0; } + +int config_parse_boot( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Settings *settings = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse Boot= parameter %s, ignoring: %m", rvalue); + return 0; + } + + if (r > 0) { + if (settings->start_mode == START_PID2) + goto conflict; + + settings->start_mode = START_BOOT; + } else { + if (settings->start_mode == START_BOOT) + goto conflict; + + if (settings->start_mode < 0) + settings->start_mode = START_PID1; + } + + return 0; + +conflict: + log_syntax(unit, LOG_ERR, filename, line, r, "Conflicting Boot= or ProcessTwo= setting found. Ignoring."); + return 0; +} + +int config_parse_pid2( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Settings *settings = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse ProcessTwo= parameter %s, ignoring: %m", rvalue); + return 0; + } + + if (r > 0) { + if (settings->start_mode == START_BOOT) + goto conflict; + + settings->start_mode = START_PID2; + } else { + if (settings->start_mode == START_PID2) + goto conflict; + + if (settings->start_mode < 0) + settings->start_mode = START_PID1; + } + + return 0; + +conflict: + log_syntax(unit, LOG_ERR, filename, line, r, "Conflicting Boot= or ProcessTwo= setting found. Ignoring."); + return 0; +} diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index 10230a5b83..fdb07486da 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -27,25 +27,34 @@ #include "nspawn-expose-ports.h" #include "nspawn-mount.h" +typedef enum StartMode { + START_PID1, /* Run parameters as command line as process 1 */ + START_PID2, /* Use stub init process as PID 1, run parameters as command line as process 2 */ + START_BOOT, /* Search for init system, pass arguments as parameters */ + _START_MODE_MAX, + _START_MODE_INVALID = -1 +} StartMode; + typedef enum SettingsMask { - SETTING_BOOT = 1 << 0, - SETTING_ENVIRONMENT = 1 << 1, - SETTING_USER = 1 << 2, - SETTING_CAPABILITY = 1 << 3, - SETTING_KILL_SIGNAL = 1 << 4, - SETTING_PERSONALITY = 1 << 5, - SETTING_MACHINE_ID = 1 << 6, - SETTING_NETWORK = 1 << 7, - SETTING_EXPOSE_PORTS = 1 << 8, - SETTING_READ_ONLY = 1 << 9, - SETTING_VOLATILE_MODE = 1 << 10, - SETTING_CUSTOM_MOUNTS = 1 << 11, - _SETTINGS_MASK_ALL = (1 << 12) -1 + SETTING_START_MODE = 1 << 0, + SETTING_ENVIRONMENT = 1 << 1, + SETTING_USER = 1 << 2, + SETTING_CAPABILITY = 1 << 3, + SETTING_KILL_SIGNAL = 1 << 4, + SETTING_PERSONALITY = 1 << 5, + SETTING_MACHINE_ID = 1 << 6, + SETTING_NETWORK = 1 << 7, + SETTING_EXPOSE_PORTS = 1 << 8, + SETTING_READ_ONLY = 1 << 9, + SETTING_VOLATILE_MODE = 1 << 10, + SETTING_CUSTOM_MOUNTS = 1 << 11, + SETTING_WORKING_DIRECTORY = 1 << 12, + _SETTINGS_MASK_ALL = (1 << 13) -1 } SettingsMask; typedef struct Settings { /* [Run] */ - int boot; + StartMode start_mode; char **parameters; char **environment; char *user; @@ -54,6 +63,7 @@ typedef struct Settings { int kill_signal; unsigned long personality; sd_id128_t machine_id; + char *working_directory; /* [Image] */ int read_only; @@ -89,3 +99,5 @@ int config_parse_volatile_mode(const char *unit, const char *filename, unsigned int config_parse_bind(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_tmpfs(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_veth_extra(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_boot(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_pid2(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/nspawn/nspawn-setuid.c b/src/nspawn/nspawn-setuid.c index aa6a16309c..014a40b243 100644 --- a/src/nspawn/nspawn-setuid.c +++ b/src/nspawn/nspawn-setuid.c @@ -261,10 +261,10 @@ int change_uid_gid(const char *user, char **_home) { return log_error_errno(errno, "Failed to set auxiliary groups: %m"); if (setresgid(gid, gid, gid) < 0) - return log_error_errno(errno, "setregid() failed: %m"); + return log_error_errno(errno, "setresgid() failed: %m"); if (setresuid(uid, uid, uid) < 0) - return log_error_errno(errno, "setreuid() failed: %m"); + return log_error_errno(errno, "setresuid() failed: %m"); if (_home) { *_home = home; diff --git a/src/nspawn/nspawn-stub-pid1.c b/src/nspawn/nspawn-stub-pid1.c new file mode 100644 index 0000000000..2de87e3c63 --- /dev/null +++ b/src/nspawn/nspawn-stub-pid1.c @@ -0,0 +1,170 @@ +/*** + 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 <sys/reboot.h> +#include <sys/unistd.h> +#include <sys/wait.h> + +#include "fd-util.h" +#include "log.h" +#include "nspawn-stub-pid1.h" +#include "process-util.h" +#include "signal-util.h" +#include "time-util.h" +#include "def.h" + +int stub_pid1(void) { + enum { + STATE_RUNNING, + STATE_REBOOT, + STATE_POWEROFF, + } state = STATE_RUNNING; + + sigset_t fullmask, oldmask, waitmask; + usec_t quit_usec = USEC_INFINITY; + pid_t pid; + int r; + + /* Implements a stub PID 1, that reaps all processes and processes a couple of standard signals. This is useful + * for allowing arbitrary processes run in a container, and still have all zombies reaped. */ + + assert_se(sigfillset(&fullmask) >= 0); + assert_se(sigprocmask(SIG_BLOCK, &fullmask, &oldmask) >= 0); + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork child pid: %m"); + + if (pid == 0) { + /* Return in the child */ + assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) >= 0); + setsid(); + return 0; + } + + reset_all_signal_handlers(); + + log_close(); + close_all_fds(NULL, 0); + log_open(); + + rename_process("STUBINIT"); + + assert_se(sigemptyset(&waitmask) >= 0); + assert_se(sigset_add_many(&waitmask, + SIGCHLD, /* posix: process died */ + SIGINT, /* sysv: ctrl-alt-del */ + SIGRTMIN+3, /* systemd: halt */ + SIGRTMIN+4, /* systemd: poweroff */ + SIGRTMIN+5, /* systemd: reboot */ + SIGRTMIN+6, /* systemd: kexec */ + SIGRTMIN+13, /* systemd: halt */ + SIGRTMIN+14, /* systemd: poweroff */ + SIGRTMIN+15, /* systemd: reboot */ + SIGRTMIN+16, /* systemd: kexec */ + -1) >= 0); + + /* Note that we ignore SIGTERM (sysv's reexec), SIGHUP (reload), and all other signals here, since we don't + * support reexec/reloading in this stub process. */ + + for (;;) { + siginfo_t si; + usec_t current_usec; + + si.si_pid = 0; + r = waitid(P_ALL, 0, &si, WEXITED|WNOHANG); + if (r < 0) { + r = log_error_errno(errno, "Failed to reap children: %m"); + goto finish; + } + + current_usec = now(CLOCK_MONOTONIC); + + if (si.si_pid == pid || current_usec >= quit_usec) { + + /* The child we started ourselves died or we reached a timeout. */ + + if (state == STATE_REBOOT) { /* dispatch a queued reboot */ + (void) reboot(RB_AUTOBOOT); + r = log_error_errno(errno, "Failed to reboot: %m"); + goto finish; + + } else if (state == STATE_POWEROFF) + (void) reboot(RB_POWER_OFF); /* if this fails, fall back to normal exit. */ + + if (si.si_pid == pid && si.si_code == CLD_EXITED) + r = si.si_status; /* pass on exit code */ + else + r = 255; /* signal, coredump, timeout, … */ + + goto finish; + } + if (si.si_pid != 0) + /* We reaped something. Retry until there's nothing more to reap. */ + continue; + + if (quit_usec == USEC_INFINITY) + r = sigwaitinfo(&waitmask, &si); + else { + struct timespec ts; + r = sigtimedwait(&waitmask, &si, timespec_store(&ts, quit_usec - current_usec)); + } + if (r < 0) { + if (errno == EINTR) /* strace -p attach can result in EINTR, let's handle this nicely. */ + continue; + if (errno == EAGAIN) /* timeout reached */ + continue; + + r = log_error_errno(errno, "Failed to wait for signal: %m"); + goto finish; + } + + if (si.si_signo == SIGCHLD) + continue; /* Let's reap this */ + + if (state != STATE_RUNNING) + continue; + + /* Would love to use a switch() statement here, but SIGRTMIN is actually a function call, not a + * constant… */ + + if (si.si_signo == SIGRTMIN+3 || + si.si_signo == SIGRTMIN+4 || + si.si_signo == SIGRTMIN+13 || + si.si_signo == SIGRTMIN+14) + + state = STATE_POWEROFF; + + else if (si.si_signo == SIGINT || + si.si_signo == SIGRTMIN+5 || + si.si_signo == SIGRTMIN+6 || + si.si_signo == SIGRTMIN+15 || + si.si_signo == SIGRTMIN+16) + + state = STATE_REBOOT; + else + assert_not_reached("Got unexpected signal"); + + /* (void) kill_and_sigcont(pid, SIGTERM); */ + quit_usec = now(CLOCK_MONOTONIC) + DEFAULT_TIMEOUT_USEC; + } + +finish: + _exit(r < 0 ? EXIT_FAILURE : r); +} diff --git a/src/nspawn/nspawn-stub-pid1.h b/src/nspawn/nspawn-stub-pid1.h new file mode 100644 index 0000000000..36c1aaf5dd --- /dev/null +++ b/src/nspawn/nspawn-stub-pid1.h @@ -0,0 +1,22 @@ +#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/>. +***/ + +int stub_pid1(void); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index a4e13bd6aa..370161e9bf 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -79,6 +79,7 @@ #include "nspawn-register.h" #include "nspawn-settings.h" #include "nspawn-setuid.h" +#include "nspawn-stub-pid1.h" #include "parse-util.h" #include "path-util.h" #include "process-util.h" @@ -114,6 +115,7 @@ typedef enum LinkJournal { static char *arg_directory = NULL; static char *arg_template = NULL; +static char *arg_chdir = NULL; static char *arg_user = NULL; static sd_id128_t arg_uuid = {}; static char *arg_machine = NULL; @@ -122,7 +124,7 @@ static const char *arg_selinux_apifs_context = NULL; static const char *arg_slice = NULL; static bool arg_private_network = false; static bool arg_read_only = false; -static bool arg_boot = false; +static StartMode arg_start_mode = START_PID1; static bool arg_ephemeral = false; static LinkJournal arg_link_journal = LINK_AUTO; static bool arg_link_journal_try = false; @@ -192,7 +194,9 @@ static void help(void) { " -x --ephemeral Run container with snapshot of root directory, and\n" " remove it after exit\n" " -i --image=PATH File system device or disk image for the container\n" + " -a --as-pid2 Maintain a stub init as PID1, invoke binary as PID2\n" " -b --boot Boot up full system (i.e. invoke init)\n" + " --chdir=PATH Set working directory in the container\n" " -u --user=USER Run the command under specified user or uid\n" " -M --machine=NAME Set the machine name for the container\n" " --uuid=UUID Set a specific machine UUID for the container\n" @@ -231,8 +235,8 @@ static void help(void) { " capability\n" " --drop-capability=CAP Drop the specified capability from the default set\n" " --kill-signal=SIGNAL Select signal to use for shutting down PID 1\n" - " --link-journal=MODE Link up guest journal, one of no, auto, guest, host,\n" - " try-guest, try-host\n" + " --link-journal=MODE Link up guest journal, one of no, auto, guest, \n" + " host, try-guest, try-host\n" " -j Equivalent to --link-journal=try-guest\n" " --read-only Mount the root directory read-only\n" " --bind=PATH[:PATH[:OPTIONS]]\n" @@ -345,6 +349,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_PRIVATE_USERS, ARG_KILL_SIGNAL, ARG_SETTINGS, + ARG_CHDIR, }; static const struct option options[] = { @@ -355,6 +360,7 @@ static int parse_argv(int argc, char *argv[]) { { "ephemeral", no_argument, NULL, 'x' }, { "user", required_argument, NULL, 'u' }, { "private-network", no_argument, NULL, ARG_PRIVATE_NETWORK }, + { "as-pid2", no_argument, NULL, 'a' }, { "boot", no_argument, NULL, 'b' }, { "uuid", required_argument, NULL, ARG_UUID }, { "read-only", no_argument, NULL, ARG_READ_ONLY }, @@ -389,6 +395,7 @@ static int parse_argv(int argc, char *argv[]) { { "private-users", optional_argument, NULL, ARG_PRIVATE_USERS }, { "kill-signal", required_argument, NULL, ARG_KILL_SIGNAL }, { "settings", required_argument, NULL, ARG_SETTINGS }, + { "chdir", required_argument, NULL, ARG_CHDIR }, {} }; @@ -400,7 +407,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "+hD:u:bL:M:jS:Z:qi:xp:n", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "+hD:u:abL:M:jS:Z:qi:xp:n", options, NULL)) >= 0) switch (c) { @@ -491,8 +498,23 @@ static int parse_argv(int argc, char *argv[]) { break; case 'b': - arg_boot = true; - arg_settings_mask |= SETTING_BOOT; + if (arg_start_mode == START_PID2) { + log_error("--boot and --as-pid2 may not be combined."); + return -EINVAL; + } + + arg_start_mode = START_BOOT; + arg_settings_mask |= SETTING_START_MODE; + break; + + case 'a': + if (arg_start_mode == START_BOOT) { + log_error("--boot and --as-pid2 may not be combined."); + return -EINVAL; + } + + arg_start_mode = START_PID2; + arg_settings_mask |= SETTING_START_MODE; break; case ARG_UUID: @@ -849,6 +871,19 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_CHDIR: + if (!path_is_absolute(optarg)) { + log_error("Working directory %s is not an absolute path.", optarg); + return -EINVAL; + } + + r = free_and_strdup(&arg_chdir, optarg); + if (r < 0) + return log_oom(); + + arg_settings_mask |= SETTING_WORKING_DIRECTORY; + break; + case '?': return -EINVAL; @@ -859,7 +894,7 @@ static int parse_argv(int argc, char *argv[]) { if (arg_share_system) arg_register = false; - if (arg_boot && arg_share_system) { + if (arg_start_mode != START_PID1 && arg_share_system) { log_error("--boot and --share-system may not be combined."); return -EINVAL; } @@ -907,7 +942,7 @@ static int parse_argv(int argc, char *argv[]) { if (!arg_parameters) return log_oom(); - arg_settings_mask |= SETTING_BOOT; + arg_settings_mask |= SETTING_START_MODE; } /* Load all settings from .nspawn files */ @@ -943,7 +978,7 @@ static int verify_arguments(void) { return -EINVAL; } - if (arg_boot && arg_kill_signal <= 0) + if (arg_start_mode == START_BOOT && arg_kill_signal <= 0) arg_kill_signal = SIGRTMIN+3; return 0; @@ -1027,7 +1062,7 @@ static int setup_timezone(const char *dest) { } check = strjoina("/usr/share/zoneinfo/", z); - check = prefix_root(dest, check); + check = prefix_roota(dest, check); if (laccess(check, F_OK) < 0) { log_warning("Timezone %s does not exist in container, not updating container timezone.", z); return 0; @@ -1337,6 +1372,7 @@ static int setup_journal(const char *directory) { sd_id128_t machine_id, this_id; _cleanup_free_ char *b = NULL, *d = NULL; const char *etc_machine_id, *p, *q; + bool try; char *id; int r; @@ -1344,16 +1380,21 @@ static int setup_journal(const char *directory) { if (arg_ephemeral) return 0; + if (arg_link_journal == LINK_NO) + return 0; + + try = arg_link_journal_try || arg_link_journal == LINK_AUTO; + etc_machine_id = prefix_roota(directory, "/etc/machine-id"); r = read_one_line_file(etc_machine_id, &b); - if (r == -ENOENT && arg_link_journal == LINK_AUTO) + if (r == -ENOENT && try) return 0; else if (r < 0) return log_error_errno(r, "Failed to read machine ID from %s: %m", etc_machine_id); id = strstrip(b); - if (isempty(id) && arg_link_journal == LINK_AUTO) + if (isempty(id) && try) return 0; /* Verify validity */ @@ -1366,16 +1407,13 @@ static int setup_journal(const char *directory) { return log_error_errno(r, "Failed to retrieve machine ID: %m"); if (sd_id128_equal(machine_id, this_id)) { - log_full(arg_link_journal == LINK_AUTO ? LOG_WARNING : LOG_ERR, + log_full(try ? LOG_WARNING : LOG_ERR, "Host and machine ids are equal (%s): refusing to link journals", id); - if (arg_link_journal == LINK_AUTO) + if (try) return 0; return -EEXIST; } - if (arg_link_journal == LINK_NO) - return 0; - r = userns_mkdir(directory, "/var", 0755, 0, 0); if (r < 0) return log_error_errno(r, "Failed to create /var: %m"); @@ -1392,21 +1430,19 @@ static int setup_journal(const char *directory) { q = prefix_roota(directory, p); if (path_is_mount_point(p, 0) > 0) { - if (arg_link_journal != LINK_AUTO) { - log_error("%s: already a mount point, refusing to use for journal", p); - return -EEXIST; - } + if (try) + return 0; - return 0; + log_error("%s: already a mount point, refusing to use for journal", p); + return -EEXIST; } if (path_is_mount_point(q, 0) > 0) { - if (arg_link_journal != LINK_AUTO) { - log_error("%s: already a mount point, refusing to use for journal", q); - return -EEXIST; - } + if (try) + return 0; - return 0; + log_error("%s: already a mount point, refusing to use for journal", q); + return -EEXIST; } r = readlink_and_make_absolute(p, &d); @@ -1440,7 +1476,7 @@ static int setup_journal(const char *directory) { if (arg_link_journal == LINK_GUEST) { if (symlink(q, p) < 0) { - if (arg_link_journal_try) { + if (try) { log_debug_errno(errno, "Failed to symlink %s to %s, skipping journal setup: %m", q, p); return 0; } else @@ -1456,9 +1492,9 @@ static int setup_journal(const char *directory) { if (arg_link_journal == LINK_HOST) { /* don't create parents here -- if the host doesn't have * permanent journal set up, don't force it here */ - r = mkdir(p, 0755); - if (r < 0) { - if (arg_link_journal_try) { + + if (mkdir(p, 0755) < 0 && errno != EEXIST) { + if (try) { log_debug_errno(errno, "Failed to create %s, skipping journal setup: %m", p); return 0; } else @@ -1482,7 +1518,7 @@ static int setup_journal(const char *directory) { } static int drop_capabilities(void) { - return capability_bounding_set_drop(~arg_retain, false); + return capability_bounding_set_drop(arg_retain, false); } static int reset_audit_loginuid(void) { @@ -2562,6 +2598,16 @@ static int inner_child( return -ESRCH; } + if (arg_chdir) + if (chdir(arg_chdir) < 0) + return log_error_errno(errno, "Failed to change to specified working directory %s: %m", arg_chdir); + + if (arg_start_mode == START_PID2) { + r = stub_pid1(); + if (r < 0) + return r; + } + /* Now, explicitly close the log, so that we * then can close all remaining fds. Closing * the log explicitly first has the benefit @@ -2573,7 +2619,7 @@ static int inner_child( log_close(); (void) fdset_close_others(fds); - if (arg_boot) { + if (arg_start_mode == START_BOOT) { char **a; size_t m; @@ -2597,7 +2643,9 @@ static int inner_child( } else if (!strv_isempty(arg_parameters)) execvpe(arg_parameters[0], arg_parameters, env_use); else { - chdir(home ?: "/root"); + if (!arg_chdir) + chdir(home ?: "/root"); + execle("/bin/bash", "-bash", NULL, env_use); execle("/bin/sh", "-sh", NULL, env_use); } @@ -2893,15 +2941,22 @@ static int load_settings(void) { /* Copy over bits from the settings, unless they have been * explicitly masked by command line switches. */ - if ((arg_settings_mask & SETTING_BOOT) == 0 && - settings->boot >= 0) { - arg_boot = settings->boot; + if ((arg_settings_mask & SETTING_START_MODE) == 0 && + settings->start_mode >= 0) { + arg_start_mode = settings->start_mode; strv_free(arg_parameters); arg_parameters = settings->parameters; settings->parameters = NULL; } + if ((arg_settings_mask & SETTING_WORKING_DIRECTORY) == 0 && + settings->working_directory) { + free(arg_chdir); + arg_chdir = settings->working_directory; + settings->working_directory = NULL; + } + if ((arg_settings_mask & SETTING_ENVIRONMENT) == 0 && settings->environment) { strv_free(arg_setenv); @@ -3043,6 +3098,10 @@ int main(int argc, char *argv[]) { log_parse_environment(); log_open(); + /* Make sure rename_process() in the stub init process can work */ + saved_argv = argv; + saved_argc = argc; + r = parse_argv(argc, argv); if (r <= 0) goto finish; @@ -3149,7 +3208,7 @@ int main(int argc, char *argv[]) { } } - if (arg_boot) { + if (arg_start_mode == START_BOOT) { if (path_is_os_tree(arg_directory) <= 0) { log_error("Directory %s doesn't look like an OS root directory (os-release file is missing). Refusing.", arg_directory); r = -EINVAL; @@ -3628,6 +3687,7 @@ finish: free(arg_image); free(arg_machine); free(arg_user); + free(arg_chdir); strv_free(arg_setenv); free(arg_network_bridge); strv_free(arg_network_interfaces); diff --git a/src/nss-myhostname/nss-myhostname.c b/src/nss-myhostname/nss-myhostname.c index ee10b105ea..e438625814 100644 --- a/src/nss-myhostname/nss-myhostname.c +++ b/src/nss-myhostname/nss-myhostname.c @@ -31,6 +31,7 @@ #include "local-addresses.h" #include "macro.h" #include "nss-util.h" +#include "signal-util.h" #include "string-util.h" #include "util.h" @@ -63,6 +64,8 @@ enum nss_status _nss_myhostname_gethostbyname4_r( char *r_name; unsigned n; + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + assert(name); assert(pat); assert(buffer); @@ -327,6 +330,8 @@ enum nss_status _nss_myhostname_gethostbyname3_r( uint32_t local_address_ipv4 = 0; int n_addresses = 0; + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + assert(name); assert(host); assert(buffer); @@ -409,6 +414,8 @@ enum nss_status _nss_myhostname_gethostbyaddr2_r( bool additional_from_hostname = false; unsigned n; + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + assert(addr); assert(host); assert(buffer); diff --git a/src/nss-mymachines/nss-mymachines.c b/src/nss-mymachines/nss-mymachines.c index 40c8ad3a19..3cd29500d0 100644 --- a/src/nss-mymachines/nss-mymachines.c +++ b/src/nss-mymachines/nss-mymachines.c @@ -27,11 +27,11 @@ #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" #include "nss-util.h" +#include "signal-util.h" #include "string-util.h" #include "user-util.h" #include "util.h" @@ -95,6 +95,8 @@ enum nss_status _nss_mymachines_gethostbyname4_r( char *r_name; int n_ifindices, r; + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + assert(name); assert(pat); assert(buffer); @@ -243,6 +245,8 @@ enum nss_status _nss_mymachines_gethostbyname3_r( size_t l, idx, ms, alen; int r; + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + assert(name); assert(result); assert(buffer); @@ -405,6 +409,8 @@ enum nss_status _nss_mymachines_getpwnam_r( size_t l; int r; + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + assert(name); assert(pwd); @@ -492,6 +498,8 @@ enum nss_status _nss_mymachines_getpwuid_r( uint32_t mapped; int r; + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + if (!uid_is_valid(uid)) { r = -EINVAL; goto fail; @@ -565,6 +573,8 @@ enum nss_status _nss_mymachines_getgrnam_r( size_t l; int r; + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + assert(name); assert(gr); @@ -650,6 +660,8 @@ enum nss_status _nss_mymachines_getgrgid_r( uint32_t mapped; int r; + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + if (!gid_is_valid(gid)) { r = -EINVAL; goto fail; diff --git a/src/nss-resolve/nss-resolve.c b/src/nss-resolve/nss-resolve.c index bd8e27dc74..85649f67dc 100644 --- a/src/nss-resolve/nss-resolve.c +++ b/src/nss-resolve/nss-resolve.c @@ -29,12 +29,12 @@ #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" #include "string-util.h" #include "util.h" +#include "signal-util.h" NSS_GETHOSTBYNAME_PROTOTYPES(resolve); NSS_GETHOSTBYADDR_PROTOTYPES(resolve); @@ -119,6 +119,13 @@ enum nss_status _nss_resolve_gethostbyname4_r( int *errnop, int *h_errnop, int32_t *ttlp) { + enum nss_status (*fallback)( + const char *name, + struct gaih_addrtuple **pat, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp); + _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; struct gaih_addrtuple *r_tuple, *r_tuple_first = NULL; @@ -128,6 +135,8 @@ enum nss_status _nss_resolve_gethostbyname4_r( char *r_name; int c, r, i = 0; + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + assert(name); assert(pat); assert(buffer); @@ -136,7 +145,7 @@ enum nss_status _nss_resolve_gethostbyname4_r( r = sd_bus_open_system(&bus); if (r < 0) - goto fail; + goto fallback; r = sd_bus_message_new_method_call( bus, @@ -164,28 +173,10 @@ enum nss_status _nss_resolve_gethostbyname4_r( return NSS_STATUS_NOTFOUND; } - if (bus_error_shall_fallback(&error)) { - - enum nss_status (*fallback)( - const char *name, - struct gaih_addrtuple **pat, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp); - - fallback = (enum nss_status (*)(const char *name, - struct gaih_addrtuple **pat, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp)) - find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyname4_r"); - if (fallback) - return fallback(name, pat, buffer, buflen, errnop, h_errnop, ttlp); - } + if (bus_error_shall_fallback(&error)) + goto fallback; - *errnop = -r; - *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; + goto fail; } c = count_addresses(reply, AF_UNSPEC, &canonical); @@ -285,9 +276,20 @@ enum nss_status _nss_resolve_gethostbyname4_r( return NSS_STATUS_SUCCESS; +fallback: + fallback = (enum nss_status (*)(const char *name, + struct gaih_addrtuple **pat, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp)) + find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyname4_r"); + + if (fallback) + return fallback(name, pat, buffer, buflen, errnop, h_errnop, ttlp); + fail: *errnop = -r; - *h_errnop = NO_DATA; + *h_errnop = NO_RECOVERY; return NSS_STATUS_UNAVAIL; } @@ -300,6 +302,15 @@ enum nss_status _nss_resolve_gethostbyname3_r( int32_t *ttlp, char **canonp) { + enum nss_status (*fallback)( + const char *name, + int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp, + char **canonp); + _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; char *r_name, *r_aliases, *r_addr, *r_addr_list; @@ -308,6 +319,8 @@ enum nss_status _nss_resolve_gethostbyname3_r( const char *canonical; int c, r, i = 0; + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + assert(name); assert(result); assert(buffer); @@ -324,7 +337,7 @@ enum nss_status _nss_resolve_gethostbyname3_r( r = sd_bus_open_system(&bus); if (r < 0) - goto fail; + goto fallback; r = sd_bus_message_new_method_call( bus, @@ -352,32 +365,10 @@ enum nss_status _nss_resolve_gethostbyname3_r( return NSS_STATUS_NOTFOUND; } - if (bus_error_shall_fallback(&error)) { - - enum nss_status (*fallback)( - const char *name, - int af, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp, - char **canonp); - - fallback = (enum nss_status (*)(const char *name, - int af, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp, - char **canonp)) - find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyname3_r"); - if (fallback) - return fallback(name, af, result, buffer, buflen, errnop, h_errnop, ttlp, canonp); - } + if (bus_error_shall_fallback(&error)) + goto fallback; - *errnop = -r; - *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; + goto fail; } c = count_addresses(reply, af, &canonical); @@ -490,9 +481,21 @@ enum nss_status _nss_resolve_gethostbyname3_r( return NSS_STATUS_SUCCESS; +fallback: + fallback = (enum nss_status (*)(const char *name, + int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp, + char **canonp)) + find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyname3_r"); + if (fallback) + return fallback(name, af, result, buffer, buflen, errnop, h_errnop, ttlp, canonp); + fail: *errnop = -r; - *h_errnop = NO_DATA; + *h_errnop = NO_RECOVERY; return NSS_STATUS_UNAVAIL; } @@ -504,6 +507,15 @@ enum nss_status _nss_resolve_gethostbyaddr2_r( int *errnop, int *h_errnop, int32_t *ttlp) { + enum nss_status (*fallback)( + const void* addr, socklen_t len, + int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp); + + _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; char *r_name, *r_aliases, *r_addr, *r_addr_list; @@ -513,6 +525,8 @@ enum nss_status _nss_resolve_gethostbyaddr2_r( const char *n; int r, ifindex; + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + assert(addr); assert(result); assert(buffer); @@ -533,7 +547,7 @@ enum nss_status _nss_resolve_gethostbyaddr2_r( r = sd_bus_open_system(&bus); if (r < 0) - goto fail; + goto fallback; r = sd_bus_message_new_method_call( bus, @@ -569,28 +583,9 @@ enum nss_status _nss_resolve_gethostbyaddr2_r( return NSS_STATUS_NOTFOUND; } - if (bus_error_shall_fallback(&error)) { + if (bus_error_shall_fallback(&error)) + goto fallback; - enum nss_status (*fallback)( - const void* addr, socklen_t len, - int af, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp); - - fallback = (enum nss_status (*)( - const void* addr, socklen_t len, - int af, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp)) - find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyaddr2_r"); - - if (fallback) - return fallback(addr, len, af, result, buffer, buflen, errnop, h_errnop, ttlp); - } *errnop = -r; *h_errnop = NO_RECOVERY; @@ -660,7 +655,7 @@ enum nss_status _nss_resolve_gethostbyaddr2_r( p = buffer + idx; memcpy(p, n, l+1); - if (i > 1) + if (i > 0) ((char**) r_aliases)[i-1] = p; i++; @@ -688,9 +683,22 @@ enum nss_status _nss_resolve_gethostbyaddr2_r( return NSS_STATUS_SUCCESS; +fallback: + fallback = (enum nss_status (*)( + const void* addr, socklen_t len, + int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp)) + find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyaddr2_r"); + + if (fallback) + return fallback(addr, len, af, result, buffer, buflen, errnop, h_errnop, ttlp); + fail: *errnop = -r; - *h_errnop = NO_DATA; + *h_errnop = NO_RECOVERY; return NSS_STATUS_UNAVAIL; } diff --git a/src/resolve-host/Makefile b/src/resolve-host/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/resolve-host/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile
\ No newline at end of file diff --git a/src/resolve/RFCs b/src/resolve/RFCs new file mode 100644 index 0000000000..22004a00cd --- /dev/null +++ b/src/resolve/RFCs @@ -0,0 +1,59 @@ +Y = Comprehensively Implemented, to the point appropriate for resolved +D = Comprehensively Implemented, by a dependency of resolved +! = Missing and something we might want to implement +~ = Needs no explicit support or doesn't apply +? = Is this relevant today? + = We are working on this + +Y https://tools.ietf.org/html/rfc1034 → DOMAIN NAMES - CONCEPTS AND FACILITIES +Y https://tools.ietf.org/html/rfc1035 → DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION +? https://tools.ietf.org/html/rfc1101 → DNS Encoding of Network Names and Other Types +Y https://tools.ietf.org/html/rfc1123 → Requirements for Internet Hosts -- Application and Support +~ https://tools.ietf.org/html/rfc1464 → Using the Domain Name System To Store Arbitrary String Attributes +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 +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 +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 +~ https://tools.ietf.org/html/rfc4470 → Minimally Covering NSEC Records and DNSSEC On-line Signing +Y https://tools.ietf.org/html/rfc4501 → Domain Name System Uniform Resource Identifiers +Y https://tools.ietf.org/html/rfc4509 → Use of SHA-256 in DNSSEC Delegation Signer (DS) Resource Records (RRs) +~ https://tools.ietf.org/html/rfc4592 → The Role of Wildcards in the Domain Name System +~ https://tools.ietf.org/html/rfc4697 → Observed DNS Resolution Misbehavior +Y https://tools.ietf.org/html/rfc4795 → Link-Local Multicast Name Resolution (LLMNR) +Y https://tools.ietf.org/html/rfc5011 → Automated Updates of DNS Security (DNSSEC) Trust Anchors +Y https://tools.ietf.org/html/rfc5155 → DNS Security (DNSSEC) Hashed Authenticated Denial of Existence +Y https://tools.ietf.org/html/rfc5452 → Measures for Making DNS More Resilient against Forged Answers +Y https://tools.ietf.org/html/rfc5702 → Use of SHA-2 Algorithms with RSA in DNSKEY and RRSIG Resource Records for DNSSEC +Y https://tools.ietf.org/html/rfc5890 → Internationalized Domain Names for Applications (IDNA): Definitions and Document Framework +Y https://tools.ietf.org/html/rfc5891 → Internationalized Domain Names in Applications (IDNA): Protocol +Y https://tools.ietf.org/html/rfc5966 → DNS Transport over TCP - Implementation Requirements +Y https://tools.ietf.org/html/rfc6303 → Locally Served DNS Zones +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 +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) +Y https://tools.ietf.org/html/rfc7129 → Authenticated Denial of Existence in the DNS +Y https://tools.ietf.org/html/rfc7646 → Definition and Use of DNSSEC Negative Trust Anchors +~ https://tools.ietf.org/html/rfc7719 → DNS Terminology + +Also relevant: + + https://www.iab.org/documents/correspondence-reports-documents/2013-2/iab-statement-dotless-domains-considered-harmful/ diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c index a626ecf01a..49210b2ca9 100644 --- a/src/resolve/dns-type.c +++ b/src/resolve/dns-type.c @@ -19,7 +19,11 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <sys/socket.h> + #include "dns-type.h" +#include "parse-util.h" +#include "string-util.h" typedef const struct { uint16_t type; @@ -38,13 +42,267 @@ int dns_type_from_string(const char *s) { assert(s); sc = lookup_dns_type(s, strlen(s)); - if (!sc) - return _DNS_TYPE_INVALID; + if (sc) + return sc->id; + + s = startswith_no_case(s, "TYPE"); + if (s) { + unsigned x; + + if (safe_atou(s, &x) >= 0 && + x <= UINT16_MAX) + return (int) x; + } + + return _DNS_TYPE_INVALID; +} + +bool dns_type_is_pseudo(uint16_t type) { + + /* Checks whether the specified type is a "pseudo-type". What + * a "pseudo-type" precisely is, is defined only very weakly, + * but apparently entails all RR types that are not actually + * stored as RRs on the server and should hence also not be + * cached. We use this list primarily to validate NSEC type + * bitfields, and to verify what to cache. */ + + return IN_SET(type, + 0, /* A Pseudo RR type, according to RFC 2931 */ + DNS_TYPE_ANY, + DNS_TYPE_AXFR, + DNS_TYPE_IXFR, + DNS_TYPE_OPT, + DNS_TYPE_TSIG, + DNS_TYPE_TKEY + ); +} + +bool dns_class_is_pseudo(uint16_t class) { + return class == DNS_TYPE_ANY; +} + +bool dns_type_is_valid_query(uint16_t type) { + + /* The types valid as questions in packets */ + + return !IN_SET(type, + 0, + DNS_TYPE_OPT, + DNS_TYPE_TSIG, + DNS_TYPE_TKEY, + + /* RRSIG are technically valid as questions, but we refuse doing explicit queries for them, as + * they aren't really payload, but signatures for payload, and cannot be validated on their + * own. After all they are the signatures, and have no signatures of their own validating + * them. */ + DNS_TYPE_RRSIG); +} + +bool dns_type_is_valid_rr(uint16_t type) { + + /* The types valid as RR in packets (but not necessarily + * stored on servers). */ + + return !IN_SET(type, + DNS_TYPE_ANY, + DNS_TYPE_AXFR, + DNS_TYPE_IXFR); +} + +bool dns_class_is_valid_rr(uint16_t class) { + return class != DNS_CLASS_ANY; +} + +bool dns_type_may_redirect(uint16_t type) { + /* The following record types should never be redirected using + * CNAME/DNAME RRs. See + * <https://tools.ietf.org/html/rfc4035#section-2.5>. */ + + if (dns_type_is_pseudo(type)) + return false; + + return !IN_SET(type, + DNS_TYPE_CNAME, + DNS_TYPE_DNAME, + DNS_TYPE_NSEC3, + DNS_TYPE_NSEC, + DNS_TYPE_RRSIG, + DNS_TYPE_NXT, + DNS_TYPE_SIG, + DNS_TYPE_KEY); +} + +bool dns_type_may_wildcard(uint16_t type) { + + /* The following records may not be expanded from wildcard RRsets */ + + if (dns_type_is_pseudo(type)) + return false; + + return !IN_SET(type, + DNS_TYPE_NSEC3, + DNS_TYPE_SOA, + + /* Prohibited by https://tools.ietf.org/html/rfc4592#section-4.4 */ + DNS_TYPE_DNAME); +} + +bool dns_type_apex_only(uint16_t type) { + + /* Returns true for all RR types that may only appear signed in a zone apex */ + + return IN_SET(type, + DNS_TYPE_SOA, + DNS_TYPE_NS, /* this one can appear elsewhere, too, but not signed */ + DNS_TYPE_DNSKEY, + DNS_TYPE_NSEC3PARAM); +} + +bool dns_type_is_dnssec(uint16_t type) { + return IN_SET(type, + DNS_TYPE_DS, + DNS_TYPE_DNSKEY, + DNS_TYPE_RRSIG, + DNS_TYPE_NSEC, + DNS_TYPE_NSEC3, + DNS_TYPE_NSEC3PARAM); +} + +bool dns_type_is_obsolete(uint16_t type) { + return IN_SET(type, + /* Obsoleted by RFC 973 */ + DNS_TYPE_MD, + DNS_TYPE_MF, + DNS_TYPE_MAILA, + + /* Kinda obsoleted by RFC 2505 */ + DNS_TYPE_MB, + DNS_TYPE_MG, + DNS_TYPE_MR, + DNS_TYPE_MINFO, + DNS_TYPE_MAILB, + + /* RFC1127 kinda obsoleted this by recommending against its use */ + DNS_TYPE_WKS, + + /* Declared historical by RFC 6563 */ + DNS_TYPE_A6, - return sc->id; + /* Obsoleted by DNSSEC-bis */ + DNS_TYPE_NXT, + + /* RFC 1035 removed support for concepts that needed this from RFC 883 */ + DNS_TYPE_NULL); +} + +int dns_type_to_af(uint16_t t) { + switch (t) { + + case DNS_TYPE_A: + return AF_INET; + + case DNS_TYPE_AAAA: + return AF_INET6; + + case DNS_TYPE_ANY: + return AF_UNSPEC; + + default: + return -EINVAL; + } +} + +const char *dns_class_to_string(uint16_t class) { + + switch (class) { + + case DNS_CLASS_IN: + return "IN"; + + case DNS_CLASS_ANY: + return "ANY"; + } + + return NULL; +} + +int dns_class_from_string(const char *s) { + + if (!s) + return _DNS_CLASS_INVALID; + + if (strcaseeq(s, "IN")) + return DNS_CLASS_IN; + else if (strcaseeq(s, "ANY")) + return DNS_CLASS_ANY; + + return _DNS_CLASS_INVALID; +} + +const char* tlsa_cert_usage_to_string(uint8_t cert_usage) { + + switch (cert_usage) { + + case 0: + return "CA constraint"; + + case 1: + return "Service certificate constraint"; + + case 2: + return "Trust anchor assertion"; + + case 3: + return "Domain-issued certificate"; + + case 4 ... 254: + return "Unassigned"; + + case 255: + return "Private use"; + } + + return NULL; /* clang cannot count that we covered everything */ } -/* XXX: find an authoritative list of all pseudo types? */ -bool dns_type_is_pseudo(uint16_t n) { - return IN_SET(n, DNS_TYPE_ANY, DNS_TYPE_AXFR, DNS_TYPE_IXFR, DNS_TYPE_OPT); +const char* tlsa_selector_to_string(uint8_t selector) { + switch (selector) { + + case 0: + return "Full Certificate"; + + case 1: + return "SubjectPublicKeyInfo"; + + case 2 ... 254: + return "Unassigned"; + + case 255: + return "Private use"; + } + + return NULL; +} + +const char* tlsa_matching_type_to_string(uint8_t selector) { + + switch (selector) { + + case 0: + return "No hash used"; + + case 1: + return "SHA-256"; + + case 2: + return "SHA-512"; + + case 3 ... 254: + return "Unassigned"; + + case 255: + return "Private use"; + } + + return NULL; } diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h index 2868025ad7..d025544bab 100644 --- a/src/resolve/dns-type.h +++ b/src/resolve/dns-type.h @@ -23,10 +23,6 @@ #include "macro.h" -const char *dns_type_to_string(int type); -int dns_type_from_string(const char *s); -bool dns_type_is_pseudo(uint16_t n); - /* DNS record types, taken from * http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml. */ @@ -91,6 +87,7 @@ enum { DNS_TYPE_TALINK, DNS_TYPE_CDS, DNS_TYPE_CDNSKEY, + DNS_TYPE_OPENPGPKEY, DNS_TYPE_SPF = 0x63, DNS_TYPE_NID, @@ -119,3 +116,41 @@ enum { assert_cc(DNS_TYPE_SSHFP == 44); assert_cc(DNS_TYPE_TLSA == 52); assert_cc(DNS_TYPE_ANY == 255); + +/* DNS record classes, see RFC 1035 */ +enum { + DNS_CLASS_IN = 0x01, + DNS_CLASS_ANY = 0xFF, + + _DNS_CLASS_MAX, + _DNS_CLASS_INVALID = -1 +}; + +bool dns_type_is_pseudo(uint16_t type); +bool dns_type_is_valid_query(uint16_t type); +bool dns_type_is_valid_rr(uint16_t type); +bool dns_type_may_redirect(uint16_t type); +bool dns_type_is_dnssec(uint16_t type); +bool dns_type_is_obsolete(uint16_t type); +bool dns_type_may_wildcard(uint16_t type); +bool dns_type_apex_only(uint16_t type); +int dns_type_to_af(uint16_t t); + +bool dns_class_is_pseudo(uint16_t class); +bool dns_class_is_valid_rr(uint16_t class); + +/* TYPE?? follows http://tools.ietf.org/html/rfc3597#section-5 */ +const char *dns_type_to_string(int type); +int dns_type_from_string(const char *s); + +const char *dns_class_to_string(uint16_t type); +int dns_class_from_string(const char *name); + +/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.2 */ +const char *tlsa_cert_usage_to_string(uint8_t cert_usage); + +/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.3 */ +const char *tlsa_selector_to_string(uint8_t selector); + +/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.4 */ +const char *tlsa_matching_type_to_string(uint8_t selector); diff --git a/src/resolve-host/resolve-host.c b/src/resolve/resolve-tool.c index 0f154d9798..9bee953839 100644 --- a/src/resolve-host/resolve-host.c +++ b/src/resolve/resolve-tool.c @@ -33,16 +33,24 @@ #include "parse-util.h" #include "resolved-def.h" #include "resolved-dns-packet.h" +#include "terminal-util.h" #define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) static int arg_family = AF_UNSPEC; static int arg_ifindex = 0; -static int arg_type = 0; +static uint16_t arg_type = 0; static uint16_t arg_class = 0; static bool arg_legend = true; static uint64_t arg_flags = 0; -static bool arg_resolve_service = false; + +static enum { + MODE_RESOLVE_HOST, + MODE_RESOLVE_RECORD, + MODE_RESOLVE_SERVICE, + MODE_STATISTICS, + MODE_RESET_STATISTICS, +} arg_mode = MODE_RESOLVE_HOST; static void print_source(uint64_t flags, usec_t rtt) { char rtt_str[FORMAT_TIMESTAMP_MAX]; @@ -56,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)); @@ -320,8 +330,7 @@ static int parse_address(const char *s, int *family, union in_addr_union *addres return 0; } -static int resolve_record(sd_bus *bus, const char *name) { - +static int resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_t type) { _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; char ifname[IF_NAMESIZE] = ""; @@ -335,7 +344,7 @@ static int resolve_record(sd_bus *bus, const char *name) { if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); - log_debug("Resolving %s %s %s (interface %s).", name, dns_class_to_string(arg_class), dns_type_to_string(arg_type), isempty(ifname) ? "*" : ifname); + log_debug("Resolving %s %s %s (interface %s).", name, dns_class_to_string(class), dns_type_to_string(type), isempty(ifname) ? "*" : ifname); r = sd_bus_message_new_method_call( bus, @@ -347,8 +356,7 @@ static int resolve_record(sd_bus *bus, const char *name) { if (r < 0) return bus_log_create_error(r); - assert((uint16_t) arg_type == arg_type); - r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, arg_class, arg_type, arg_flags); + r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, class, type, arg_flags); if (r < 0) return bus_log_create_error(r); @@ -369,7 +377,7 @@ static int resolve_record(sd_bus *bus, const char *name) { while ((r = sd_bus_message_enter_container(reply, 'r', "iqqay")) > 0) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - _cleanup_free_ char *s = NULL; + const char *s; uint16_t c, t; int ifindex; const void *d; @@ -399,17 +407,13 @@ static int resolve_record(sd_bus *bus, const char *name) { if (r < 0) return log_oom(); - r = dns_packet_read_rr(p, &rr, NULL); - if (r < 0) { - log_error("Failed to parse RR."); - return r; - } + r = dns_packet_read_rr(p, &rr, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse RR: %m"); - r = dns_resource_record_to_string(rr, &s); - if (r < 0) { - log_error("Failed to format RR."); - return r; - } + s = dns_resource_record_to_string(rr); + if (!s) + return log_oom(); ifname[0] = 0; if (ifindex > 0 && !if_indextoname(ifindex, ifname)) @@ -439,6 +443,127 @@ static int resolve_record(sd_bus *bus, const char *name) { return 0; } +static int resolve_rfc4501(sd_bus *bus, const char *name) { + uint16_t type = 0, class = 0; + const char *p, *q, *n; + int r; + + assert(bus); + assert(name); + assert(startswith(name, "dns:")); + + /* Parse RFC 4501 dns: URIs */ + + p = name + 4; + + if (p[0] == '/') { + const char *e; + + if (p[1] != '/') + goto invalid; + + e = strchr(p + 2, '/'); + if (!e) + goto invalid; + + if (e != p + 2) + log_warning("DNS authority specification not supported; ignoring specified authority."); + + p = e + 1; + } + + q = strchr(p, '?'); + if (q) { + n = strndupa(p, q - p); + q++; + + for (;;) { + const char *f; + + f = startswith_no_case(q, "class="); + if (f) { + _cleanup_free_ char *t = NULL; + const char *e; + + if (class != 0) { + log_error("DNS class specified twice."); + return -EINVAL; + } + + e = strchrnul(f, ';'); + t = strndup(f, e - f); + if (!t) + return log_oom(); + + r = dns_class_from_string(t); + if (r < 0) { + log_error("Unknown DNS class %s.", t); + return -EINVAL; + } + + class = r; + + if (*e == ';') { + q = e + 1; + continue; + } + + break; + } + + f = startswith_no_case(q, "type="); + if (f) { + _cleanup_free_ char *t = NULL; + const char *e; + + if (type != 0) { + log_error("DNS type specified twice."); + return -EINVAL; + } + + e = strchrnul(f, ';'); + t = strndup(f, e - f); + if (!t) + return log_oom(); + + r = dns_type_from_string(t); + if (r < 0) { + log_error("Unknown DNS type %s.", t); + return -EINVAL; + } + + type = r; + + if (*e == ';') { + q = e + 1; + continue; + } + + break; + } + + goto invalid; + } + } else + n = p; + + if (type == 0) + type = arg_type; + if (type == 0) + type = DNS_TYPE_A; + + if (class == 0) + class = arg_class; + if (class == 0) + class = DNS_CLASS_IN; + + return resolve_record(bus, n, class, type); + +invalid: + log_error("Invalid DNS URI: %s", name); + return -EINVAL; +} + static int resolve_service(sd_bus *bus, const char *name, const char *type, const char *domain) { const char *canonical_name, *canonical_type, *canonical_domain; _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; @@ -640,6 +765,147 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons return 0; } +static int show_statistics(sd_bus *bus) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + 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, 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", + "org.freedesktop.resolve1.Manager", + "TransactionStatistics", + &error, + &reply, + "(tt)"); + if (r < 0) + return log_error_errno(r, "Failed to get transaction statistics: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "(tt)", + &n_current_transactions, + &n_total_transactions); + if (r < 0) + return bus_log_parse_error(r); + + printf("%sTransactions%s\n" + "Current Transactions: %" PRIu64 "\n" + " Total Transactions: %" PRIu64 "\n", + ansi_highlight(), + ansi_normal(), + n_current_transactions, + n_total_transactions); + + reply = sd_bus_message_unref(reply); + + r = sd_bus_get_property(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "CacheStatistics", + &error, + &reply, + "(ttt)"); + if (r < 0) + return log_error_errno(r, "Failed to get cache statistics: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "(ttt)", + &cache_size, + &n_cache_hit, + &n_cache_miss); + if (r < 0) + return bus_log_parse_error(r); + + printf("\n%sCache%s\n" + " Current Cache Size: %" PRIu64 "\n" + " Cache Hits: %" PRIu64 "\n" + " Cache Misses: %" PRIu64 "\n", + ansi_highlight(), + ansi_normal(), + cache_size, + n_cache_hit, + n_cache_miss); + + reply = sd_bus_message_unref(reply); + + r = sd_bus_get_property(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "DNSSECStatistics", + &error, + &reply, + "(tttt)"); + if (r < 0) + return log_error_errno(r, "Failed to get DNSSEC statistics: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "(tttt)", + &n_dnssec_secure, + &n_dnssec_insecure, + &n_dnssec_bogus, + &n_dnssec_indeterminate); + if (r < 0) + return bus_log_parse_error(r); + + printf("\n%sDNSSEC Verdicts%s\n" + " Secure: %" PRIu64 "\n" + " Insecure: %" PRIu64 "\n" + " Bogus: %" PRIu64 "\n" + " Indeterminate: %" PRIu64 "\n", + ansi_highlight(), + ansi_normal(), + n_dnssec_secure, + n_dnssec_insecure, + n_dnssec_bogus, + n_dnssec_indeterminate); + + return 0; +} + +static int reset_statistics(sd_bus *bus) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + r = sd_bus_call_method(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResetStatistics", + &error, + NULL, + NULL); + if (r < 0) + return log_error_errno(r, "Failed to reset statistics: %s", bus_error_message(&error, r)); + + return 0; +} + +static void help_protocol_types(void) { + if (arg_legend) + puts("Known protocol types:"); + puts("dns\nllmnr\nllmnr-ipv4\nllmnr-ipv6"); +} + static void help_dns_types(void) { int i; const char *t; @@ -669,21 +935,23 @@ static void help_dns_classes(void) { static void help(void) { printf("%s [OPTIONS...] NAME...\n" "%s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n\n" - "Resolve domain names, IPv4 or IPv6 addresses, resource records, and services.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -4 Resolve IPv4 addresses\n" - " -6 Resolve IPv6 addresses\n" - " -i 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" - " --service Resolve service (SRV)\n" - " --service-address=BOOL Do [not] resolve address for services\n" - " --service-txt=BOOL Do [not] resolve TXT records for services\n" - " --cname=BOOL Do [not] follow CNAME redirects\n" - " --search=BOOL Do [not] use search domains\n" - " --legend=BOOL Do [not] print column headers\n" + "Resolve domain names, IPv4 and IPv6 addresses, DNS resource records, and services.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -4 Resolve IPv4 addresses\n" + " -6 Resolve IPv6 addresses\n" + " -i --interface=INTERFACE Look on interface\n" + " -p --protocol=PROTOCOL|help Look via protocol\n" + " -t --type=TYPE|help Query RR with DNS type\n" + " -c --class=CLASS|help Query RR with DNS class\n" + " --service Resolve service (SRV)\n" + " --service-address=BOOL Do [not] resolve address for services\n" + " --service-txt=BOOL Do [not] resolve TXT records for services\n" + " --cname=BOOL Do [not] follow CNAME redirects\n" + " --search=BOOL Do [not] use search domains\n" + " --legend=BOOL Do [not] print column headers and meta information\n" + " --statistics Show resolver statistics\n" + " --reset-statistics Reset resolver statistics\n" , program_invocation_short_name, program_invocation_short_name); } @@ -696,20 +964,25 @@ static int parse_argv(int argc, char *argv[]) { ARG_SERVICE_ADDRESS, ARG_SERVICE_TXT, ARG_SEARCH, + ARG_STATISTICS, + ARG_RESET_STATISTICS, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "class", required_argument, NULL, 'c' }, - { "legend", required_argument, NULL, ARG_LEGEND }, - { "protocol", required_argument, NULL, 'p' }, - { "cname", required_argument, NULL, ARG_CNAME }, - { "service", no_argument, NULL, ARG_SERVICE }, - { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, - { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, - { "search", required_argument, NULL, ARG_SEARCH }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "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 }, + { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, + { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, + { "search", required_argument, NULL, ARG_SEARCH }, + { "statistics", no_argument, NULL, ARG_STATISTICS, }, + { "reset-statistics", no_argument, NULL, ARG_RESET_STATISTICS }, {} }; @@ -758,13 +1031,15 @@ static int parse_argv(int argc, char *argv[]) { return 0; } - arg_type = dns_type_from_string(optarg); - if (arg_type < 0) { + r = dns_type_from_string(optarg); + if (r < 0) { log_error("Failed to parse RR record type %s", optarg); - return arg_type; + return r; } - assert(arg_type > 0 && (uint16_t) arg_type == arg_type); + arg_type = (uint16_t) r; + assert((int) arg_type == r); + arg_mode = MODE_RESOLVE_RECORD; break; case 'c': @@ -773,11 +1048,13 @@ static int parse_argv(int argc, char *argv[]) { return 0; } - r = dns_class_from_string(optarg, &arg_class); + r = dns_class_from_string(optarg); if (r < 0) { log_error("Failed to parse RR record class %s", optarg); return r; } + arg_class = (uint16_t) r; + assert((int) arg_class == r); break; @@ -790,7 +1067,10 @@ static int parse_argv(int argc, char *argv[]) { break; case 'p': - if (streq(optarg, "dns")) + if (streq(optarg, "help")) { + help_protocol_types(); + return 0; + } else if (streq(optarg, "dns")) arg_flags |= SD_RESOLVED_DNS; else if (streq(optarg, "llmnr")) arg_flags |= SD_RESOLVED_LLMNR; @@ -806,7 +1086,7 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_SERVICE: - arg_resolve_service = true; + arg_mode = MODE_RESOLVE_SERVICE; break; case ARG_CNAME: @@ -849,6 +1129,14 @@ static int parse_argv(int argc, char *argv[]) { arg_flags &= ~SD_RESOLVED_NO_SEARCH; break; + case ARG_STATISTICS: + arg_mode = MODE_STATISTICS; + break; + + case ARG_RESET_STATISTICS: + arg_mode = MODE_RESET_STATISTICS; + break; + case '?': return -EINVAL; @@ -861,7 +1149,7 @@ static int parse_argv(int argc, char *argv[]) { return -EINVAL; } - if (arg_type != 0 && arg_resolve_service) { + if (arg_type != 0 && arg_mode != MODE_RESOLVE_RECORD) { log_error("--service and --type= may not be combined."); return -EINVAL; } @@ -869,6 +1157,9 @@ static int parse_argv(int argc, char *argv[]) { if (arg_type != 0 && arg_class == 0) arg_class = DNS_CLASS_IN; + if (arg_class != 0 && arg_type == 0) + arg_type = DNS_TYPE_A; + return 1 /* work to do */; } @@ -883,20 +1174,61 @@ int main(int argc, char **argv) { if (r <= 0) goto finish; - if (optind >= argc) { - log_error("No arguments passed"); - r = -EINVAL; - goto finish; - } - r = sd_bus_open_system(&bus); if (r < 0) { log_error_errno(r, "sd_bus_open_system: %m"); goto finish; } - if (arg_resolve_service) { + switch (arg_mode) { + case MODE_RESOLVE_HOST: + if (optind >= argc) { + log_error("No arguments passed."); + r = -EINVAL; + goto finish; + } + + while (argv[optind]) { + int family, ifindex, k; + union in_addr_union a; + + if (startswith(argv[optind], "dns:")) + k = resolve_rfc4501(bus, argv[optind]); + else { + k = parse_address(argv[optind], &family, &a, &ifindex); + if (k >= 0) + k = resolve_address(bus, family, &a, ifindex); + else + k = resolve_host(bus, argv[optind]); + } + + if (r == 0) + r = k; + + optind++; + } + break; + + case MODE_RESOLVE_RECORD: + if (optind >= argc) { + log_error("No arguments passed."); + r = -EINVAL; + goto finish; + } + + while (argv[optind]) { + int k; + + k = resolve_record(bus, argv[optind], arg_class, arg_type); + if (r == 0) + r = k; + + optind++; + } + break; + + case MODE_RESOLVE_SERVICE: if (argc < optind + 1) { log_error("Domain specification required."); r = -EINVAL; @@ -909,32 +1241,32 @@ int main(int argc, char **argv) { else if (argc == optind + 3) r = resolve_service(bus, argv[optind], argv[optind+1], argv[optind+2]); else { - log_error("Too many arguments"); + log_error("Too many arguments."); r = -EINVAL; goto finish; } - goto finish; - } + break; - while (argv[optind]) { - int family, ifindex, k; - union in_addr_union a; - - if (arg_type != 0) - k = resolve_record(bus, argv[optind]); - else { - k = parse_address(argv[optind], &family, &a, &ifindex); - if (k >= 0) - k = resolve_address(bus, family, &a, ifindex); - else - k = resolve_host(bus, argv[optind]); + case MODE_STATISTICS: + if (argc > optind) { + log_error("Too many arguments."); + r = -EINVAL; + goto finish; } - if (r == 0) - r = k; + r = show_statistics(bus); + break; + + case MODE_RESET_STATISTICS: + if (argc > optind) { + log_error("Too many arguments."); + r = -EINVAL; + goto finish; + } - optind++; + r = reset_statistics(bus); + break; } finish: diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 1427638233..251e7a50a4 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) { @@ -54,23 +43,38 @@ static int reply_query_state(DnsQuery *q) { case DNS_TRANSACTION_INVALID_REPLY: return sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); - case DNS_TRANSACTION_RESOURCES: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_RESOURCES, "Not enough resources"); + case DNS_TRANSACTION_ERRNO: + return sd_bus_reply_method_errnof(q->request, q->answer_errno, "Lookup failed due to system error: %m"); case DNS_TRANSACTION_ABORTED: return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "Query aborted"); case DNS_TRANSACTION_DNSSEC_FAILED: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "DNSSEC validation failed"); + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s", + dnssec_result_to_string(q->answer_dnssec_result)); + + case DNS_TRANSACTION_NO_TRUST_ANCHOR: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_TRUST_ANCHOR, "No suitable trust anchor known"); + + case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_RR_TYPE_UNSUPPORTED, "Server does not support requested resource record type"); - case DNS_TRANSACTION_FAILURE: { + case DNS_TRANSACTION_NETWORK_DOWN: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NETWORK_DOWN, "Network is down"); + + case DNS_TRANSACTION_NOT_FOUND: + /* We return this as NXDOMAIN. This is only generated when a host doesn't implement LLMNR/TCP, and we + * thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */ + return sd_bus_reply_method_errorf(q->request, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); + + case DNS_TRANSACTION_RCODE_FAILURE: { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; if (q->answer_rcode == DNS_RCODE_NXDOMAIN) - sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", name); + sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); else { const char *rc, *n; - char p[3]; /* the rcode is 4 bits long */ + char p[DECIMAL_STR_MAX(q->answer_rcode)]; rc = dns_rcode_to_string(q->answer_rcode); if (!rc) { @@ -79,7 +83,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); @@ -87,6 +91,7 @@ static int reply_query_state(DnsQuery *q) { case DNS_TRANSACTION_NULL: case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: case DNS_TRANSACTION_SUCCESS: default: assert_not_reached("Impossible state"); @@ -136,8 +141,9 @@ static int append_address(sd_bus_message *reply, DnsResourceRecord *rr, int ifin static void bus_method_resolve_hostname_complete(DnsQuery *q) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + DnsResourceRecord *rr; unsigned added = 0; - int r; + int ifindex, r; assert(q); @@ -148,12 +154,12 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { r = dns_query_process_cname(q); if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); goto finish; } if (r < 0) goto finish; - if (r > 0) /* This was a cname, and the query was restarted. */ + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ return; r = sd_bus_message_new_method_return(q->request, &reply); @@ -164,30 +170,29 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { if (r < 0) goto finish; - if (q->answer) { - DnsResourceRecord *rr; - int ifindex; + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + DnsQuestion *question; - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - r = dns_question_matches_rr(q->question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); - if (r < 0) - goto finish; - if (r == 0) - continue; + question = dns_query_question_for_protocol(q, q->answer_protocol); - r = append_address(reply, rr, ifindex); - if (r < 0) - goto finish; + r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + if (r < 0) + goto finish; + if (r == 0) + continue; - if (!canonical) - canonical = dns_resource_record_ref(rr); + r = append_address(reply, rr, ifindex); + if (r < 0) + goto finish; - added ++; - } + if (!canonical) + canonical = dns_resource_record_ref(rr); + + 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; } @@ -231,7 +236,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; @@ -261,17 +266,22 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, if (r < 0) return r; - r = dns_question_new_address(&question, family, hostname); + r = dns_question_new_address(&question_utf8, family, hostname, false); + if (r < 0) + return r; + + r = dns_question_new_address(&question_idna, family, hostname, true); if (r < 0) return r; - r = dns_query_new(m, &q, question, ifindex, flags); + r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags); if (r < 0) return r; q->request = sd_bus_message_ref(message); q->request_family = family; q->complete = bus_method_resolve_hostname_complete; + q->suppress_unroutable_family = family == AF_UNSPEC; r = dns_query_bus_track(q, message); if (r < 0) @@ -290,6 +300,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; @@ -303,12 +314,12 @@ static void bus_method_resolve_address_complete(DnsQuery *q) { r = dns_query_process_cname(q); if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); goto finish; } if (r < 0) goto finish; - if (r > 0) /* This was a cname, and the query was restarted. */ + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ return; r = sd_bus_message_new_method_return(q->request, &reply); @@ -319,20 +330,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) { @@ -403,7 +414,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; @@ -457,7 +468,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); @@ -469,12 +483,12 @@ static void bus_method_resolve_record_complete(DnsQuery *q) { r = dns_query_process_cname(q); if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); goto finish; } if (r < 0) goto finish; - if (r > 0) /* Following a CNAME */ + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ return; r = sd_bus_message_new_method_return(q->request, &reply); @@ -485,27 +499,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; } @@ -553,6 +564,11 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd if (r == 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid name '%s'", name); + if (!dns_type_is_valid_query(type)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified resource record type %" PRIu16 " may not be used in a query.", type); + if (dns_type_is_obsolete(type)) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Specified DNS resource record type %" PRIu16 " is obsolete.", type); + r = check_ifindex_flags(ifindex, &flags, 0, error); if (r < 0) return r; @@ -569,7 +585,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; @@ -609,13 +625,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) @@ -623,7 +642,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) @@ -660,6 +679,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) @@ -667,7 +687,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) @@ -675,7 +697,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) @@ -733,8 +755,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); @@ -776,7 +800,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; } @@ -797,31 +821,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; } @@ -833,20 +854,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); @@ -889,7 +906,7 @@ static void resolve_service_hostname_complete(DnsQuery *q) { } r = dns_query_process_cname(q); - if (r > 0) /* This was a cname, and the query was restarted. */ + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ return; /* This auxiliary lookup is finished or failed, let's see if all are finished now. */ @@ -910,11 +927,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; @@ -948,8 +965,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); @@ -960,43 +980,56 @@ static void bus_method_resolve_service_complete(DnsQuery *q) { r = dns_query_process_cname(q); if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); goto finish; } if (r < 0) goto finish; - if (r > 0) /* This was a cname, and the query was restarted. */ + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ return; - if (q->answer) { - DnsResourceRecord *rr; - int ifindex; + question = dns_query_question_for_protocol(q, q->answer_protocol); - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - r = dns_question_matches_rr(q->question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; - if (rr->key->type != DNS_TYPE_SRV) - continue; + if (rr->key->type != DNS_TYPE_SRV) + continue; - if ((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; } + + 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; } @@ -1014,8 +1047,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; @@ -1037,10 +1070,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; @@ -1060,23 +1091,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; @@ -1099,17 +1122,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; @@ -1144,7 +1173,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; @@ -1153,7 +1182,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++; @@ -1162,7 +1191,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; } @@ -1189,19 +1218,19 @@ static int bus_property_get_search_domains( assert(reply); assert(m); - r = sd_bus_message_open_container(reply, 'a', "(is)"); + r = sd_bus_message_open_container(reply, 'a', "(isb)"); if (r < 0) return r; LIST_FOREACH(domains, d, m->search_domains) { - r = sd_bus_message_append(reply, "(is)", 0, d->name); + r = sd_bus_message_append(reply, "(isb)", 0, d->name, d->route_only); if (r < 0) return r; } HASHMAP_FOREACH(l, m->links, i) { LIST_FOREACH(domains, d, l->search_domains) { - r = sd_bus_message_append(reply, "is", l->ifindex, d->name); + r = sd_bus_message_append(reply, "(isb)", l->ifindex, d->name, d->route_only); if (r < 0) return r; } @@ -1210,16 +1239,238 @@ static int bus_property_get_search_domains( return sd_bus_message_close_container(reply); } +static int bus_property_get_transaction_statistics( + 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, "(tt)", + (uint64_t) hashmap_size(m->dns_transactions), + (uint64_t) m->n_transactions_total); +} + +static int bus_property_get_cache_statistics( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + uint64_t size = 0, hit = 0, miss = 0; + Manager *m = userdata; + DnsScope *s; + + assert(reply); + assert(m); + + LIST_FOREACH(scopes, s, m->dns_scopes) { + size += dns_cache_size(&s->cache); + hit += s->cache.n_hit; + miss += s->cache.n_miss; + } + + return sd_bus_message_append(reply, "(ttt)", size, hit, miss); +} + +static int bus_property_get_dnssec_statistics( + 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, "(tttt)", + (uint64_t) m->n_dnssec_verdict[DNSSEC_SECURE], + (uint64_t) m->n_dnssec_verdict[DNSSEC_INSECURE], + (uint64_t) m->n_dnssec_verdict[DNSSEC_BOGUS], + (uint64_t) m->n_dnssec_verdict[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; + + assert(message); + assert(m); + + LIST_FOREACH(scopes, s, m->dns_scopes) + s->cache.n_hit = s->cache.n_miss = 0; + + m->n_transactions_total = 0; + zero(m->n_dnssec_verdict); + + 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("SearchDomains", "a(isb)", 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", "ia(sb)", 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, }; @@ -1277,6 +1528,7 @@ int manager_connect_bus(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to install bus reconnect time event: %m"); + (void) sd_event_source_set_description(m->bus_retry_event_source, "bus-retry"); return 0; } @@ -1284,6 +1536,14 @@ int manager_connect_bus(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to register object: %m"); + r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/resolve1/link", "org.freedesktop.resolve1.Link", link_vtable, link_object_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register link objects: %m"); + + r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/resolve1/link", link_node_enumerator, m); + if (r < 0) + return log_error_errno(r, "Failed to register link enumerator: %m"); + r = sd_bus_request_name(m->bus, "org.freedesktop.resolve1", 0); if (r < 0) return log_error_errno(r, "Failed to register name: %m"); diff --git a/src/resolve/resolved-bus.h b/src/resolve/resolved-bus.h index 1e72891178..1ee57ba43d 100644 --- a/src/resolve/resolved-bus.h +++ b/src/resolve/resolved-bus.h @@ -24,3 +24,4 @@ #include "resolved-manager.h" int manager_connect_bus(Manager *m); +int bus_dns_server_append(sd_bus_message *reply, DnsServer *s, bool with_ifindex); diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index 1b2f3e336e..6d8c35164e 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -80,20 +80,34 @@ int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, con int manager_add_search_domain_by_string(Manager *m, const char *domain) { DnsSearchDomain *d; + bool route_only; int r; assert(m); assert(domain); + route_only = *domain == '~'; + if (route_only) + domain++; + + if (dns_name_is_root(domain) || streq(domain, "*")) { + route_only = true; + domain = "."; + } + r = dns_search_domain_find(m->search_domains, domain, &d); if (r < 0) return r; - if (r > 0) { + if (r > 0) dns_search_domain_move_back_and_unmark(d); - return 0; + else { + r = dns_search_domain_new(m, &d, DNS_SEARCH_DOMAIN_SYSTEM, NULL, domain); + if (r < 0) + return r; } - return dns_search_domain_new(m, NULL, DNS_SEARCH_DOMAIN_SYSTEM, NULL, domain); + d->route_only = route_only; + return 0; } int manager_parse_search_domains_and_warn(Manager *m, const char *string) { @@ -200,75 +214,6 @@ int config_parse_search_domains( return 0; } -int config_parse_support( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Support support, *v = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - support = support_from_string(rvalue); - if (support < 0) { - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse support level '%s'. Ignoring.", rvalue); - return 0; - } - - support = r ? SUPPORT_YES : SUPPORT_NO; - } - - *v = support; - return 0; -} - -int config_parse_dnssec( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - Manager *m = data; - DnssecMode mode; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - mode = dnssec_mode_from_string(rvalue); - if (mode < 0) { - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse DNSSEC mode '%s'. Ignoring.", rvalue); - return 0; - } - - mode = r ? DNSSEC_YES : DNSSEC_NO; - } - - m->unicast_scope->dnssec_mode = mode; - return 0; -} - int manager_parse_config_file(Manager *m) { int r; diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h index 668ea02bba..b4ef1b0378 100644 --- a/src/resolve/resolved-conf.h +++ b/src/resolve/resolved-conf.h @@ -35,5 +35,4 @@ const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned len int config_parse_dns_servers(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_search_domains(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_support(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_dnssec(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c index 55e6ffbad7..f74e440531 100644 --- a/src/resolve/resolved-dns-answer.c +++ b/src/resolve/resolved-dns-answer.c @@ -22,6 +22,7 @@ #include "alloc-util.h" #include "dns-domain.h" #include "resolved-dns-answer.h" +#include "resolved-dns-dnssec.h" #include "string-util.h" DnsAnswer *dns_answer_new(unsigned n) { @@ -73,7 +74,7 @@ DnsAnswer *dns_answer_unref(DnsAnswer *a) { return NULL; } -static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) { +static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { assert(rr); if (!a) @@ -82,19 +83,22 @@ static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) if (a->n_rrs >= a->n_allocated) return -ENOSPC; - a->items[a->n_rrs].rr = dns_resource_record_ref(rr); - a->items[a->n_rrs].ifindex = ifindex; - a->n_rrs++; + a->items[a->n_rrs++] = (DnsAnswerItem) { + .rr = dns_resource_record_ref(rr), + .ifindex = ifindex, + .flags = flags, + }; return 1; } static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) { DnsResourceRecord *rr; + DnsAnswerFlags flags; int ifindex, r; - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, source) { - r = dns_answer_add_raw(a, rr, ifindex); + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, source) { + r = dns_answer_add_raw(a, rr, ifindex, flags); if (r < 0) return r; } @@ -102,7 +106,7 @@ static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) { return 0; } -int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) { +int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { unsigned i; int r; @@ -121,28 +125,48 @@ int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex) { if (r < 0) return r; if (r > 0) { - /* Entry already exists, keep the entry with - * the higher RR, or the one with TTL 0 */ + /* Don't mix contradicting TTLs (see below) */ + if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0)) + return -EINVAL; - if (rr->ttl == 0 || (rr->ttl > a->items[i].rr->ttl && a->items[i].rr->ttl != 0)) { + /* Entry already exists, keep the entry with + * the higher RR. */ + if (rr->ttl > a->items[i].rr->ttl) { dns_resource_record_ref(rr); dns_resource_record_unref(a->items[i].rr); a->items[i].rr = rr; } + a->items[i].flags |= flags; return 0; } + + r = dns_resource_key_equal(a->items[i].rr->key, rr->key); + if (r < 0) + return r; + if (r > 0) { + /* There's already an RR of the same RRset in + * place! Let's see if the TTLs more or less + * match. We don't really care if they match + * precisely, but we do care whether one is 0 + * and the other is not. See RFC 2181, Section + * 5.2.*/ + + if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0)) + return -EINVAL; + } } - return dns_answer_add_raw(a, rr, ifindex); + return dns_answer_add_raw(a, rr, ifindex, flags); } static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { DnsResourceRecord *rr; + DnsAnswerFlags flags; int ifindex, r; - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, b) { - r = dns_answer_add(a, rr, ifindex); + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, b) { + r = dns_answer_add(a, rr, ifindex, flags); if (r < 0) return r; } @@ -150,7 +174,7 @@ static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { return 0; } -int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex) { +int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { int r; assert(a); @@ -160,7 +184,7 @@ int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex) { if (r < 0) return r; - return dns_answer_add(*a, rr, ifindex); + return dns_answer_add(*a, rr, ifindex, flags); } int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) { @@ -186,85 +210,204 @@ int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) { soa->soa.expire = 1; soa->soa.minimum = ttl; - return dns_answer_add(a, soa, 0); + return dns_answer_add(a, soa, 0, DNS_ANSWER_AUTHENTICATED); } -int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key) { +int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) { + DnsAnswerFlags flags = 0, i_flags; DnsResourceRecord *i; + bool found = false; int r; assert(key); - if (!a) - return 0; - - DNS_ANSWER_FOREACH(i, a) { + DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { r = dns_resource_key_match_rr(key, i, NULL); if (r < 0) return r; - if (r > 0) + if (r == 0) + continue; + + if (!ret_flags) return 1; + + if (found) + flags &= i_flags; + else { + flags = i_flags; + found = true; + } } - return 0; + if (ret_flags) + *ret_flags = flags; + + return found; } -int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr) { +int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *ret_flags) { + DnsAnswerFlags flags = 0, i_flags; DnsResourceRecord *i; + bool found = false; int r; assert(rr); - DNS_ANSWER_FOREACH(i, a) { + DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { r = dns_resource_record_equal(i, rr); if (r < 0) return r; - if (r > 0) + if (r == 0) + continue; + + if (!ret_flags) return 1; + + if (found) + flags &= i_flags; + else { + flags = i_flags; + found = true; + } } - return 0; + if (ret_flags) + *ret_flags = flags; + + return found; } -int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret) { - DnsResourceRecord *rr; +int dns_answer_contains_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) { + DnsAnswerFlags flags = 0, i_flags; + DnsResourceRecord *i; + bool found = false; + int r; assert(key); - assert(ret); - if (!a) - return 0; + DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { + r = dns_resource_key_equal(i->key, key); + if (r < 0) + return r; + if (r == 0) + continue; + + if (!ret_flags) + return true; + + if (found) + flags &= i_flags; + else { + flags = i_flags; + found = true; + } + } + + if (ret_flags) + *ret_flags = flags; + + return found; +} + +int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a) { + DnsResourceRecord *i; + + DNS_ANSWER_FOREACH(i, a) { + if (IN_SET(i->key->type, DNS_TYPE_NSEC, DNS_TYPE_NSEC3)) + return true; + } + + return false; +} + +int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone) { + DnsResourceRecord *rr; + int r; + + /* Checks whether the specified answer contains at least one NSEC3 RR in the specified zone */ + + DNS_ANSWER_FOREACH(rr, answer) { + const char *p; + + if (rr->key->type != DNS_TYPE_NSEC3) + continue; + + p = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_name_equal(p, zone); + if (r != 0) + return r; + } + + return false; +} + +int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { + DnsResourceRecord *rr, *soa = NULL; + DnsAnswerFlags rr_flags, soa_flags = 0; + int r; + + assert(key); /* For a SOA record we can never find a matching SOA record */ if (key->type == DNS_TYPE_SOA) return 0; - DNS_ANSWER_FOREACH(rr, a) { - if (dns_resource_key_match_soa(key, rr->key)) { - *ret = rr; - return 1; + DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) { + r = dns_resource_key_match_soa(key, rr->key); + if (r < 0) + return r; + if (r > 0) { + + if (soa) { + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(soa->key)); + if (r < 0) + return r; + if (r > 0) + continue; + } + + soa = rr; + soa_flags = rr_flags; } } - return 0; + if (!soa) + return 0; + + if (ret) + *ret = soa; + if (flags) + *flags = soa_flags; + + return 1; } -int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret) { +int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { DnsResourceRecord *rr; + DnsAnswerFlags rr_flags; + int r; assert(key); - if (!a) - return 0; - /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */ - if (key->type == DNS_TYPE_CNAME || key->type == DNS_TYPE_DNAME) + if (!dns_type_may_redirect(key->type)) return 0; - DNS_ANSWER_FOREACH(rr, a) { - if (dns_resource_key_match_cname_or_dname(key, rr->key, NULL)) { + DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) { + r = dns_resource_key_match_cname_or_dname(key, rr->key, NULL); + if (r < 0) + return r; + if (r > 0) { if (ret) *ret = rr; + if (flags) + *flags = rr_flags; return 1; } } @@ -356,20 +499,21 @@ int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) { if ((*a)->n_ref > 1) { _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; + DnsAnswerFlags flags; int ifindex; copy = dns_answer_new((*a)->n_rrs); if (!copy) return -ENOMEM; - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, *a) { + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { r = dns_resource_key_equal(rr->key, key); if (r < 0) return r; if (r > 0) continue; - r = dns_answer_add_raw(copy, rr, ifindex); + r = dns_answer_add_raw(copy, rr, ifindex, flags); if (r < 0) return r; } @@ -407,16 +551,103 @@ int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) { return 1; } -int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key) { +int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) { + bool found = false, other = false; + DnsResourceRecord *rr; + unsigned i; + int r; + + assert(a); + assert(rm); + + /* Remove all entries matching the specified RR from *a */ + + DNS_ANSWER_FOREACH(rr, *a) { + r = dns_resource_record_equal(rr, rm); + if (r < 0) + return r; + if (r > 0) + found = true; + else + other = true; + + if (found && other) + break; + } + + if (!found) + return 0; + + if (!other) { + *a = dns_answer_unref(*a); /* Return NULL for the empty answer */ + return 1; + } + + if ((*a)->n_ref > 1) { + _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; + DnsAnswerFlags flags; + int ifindex; + + copy = dns_answer_new((*a)->n_rrs); + if (!copy) + return -ENOMEM; + + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { + r = dns_resource_record_equal(rr, rm); + if (r < 0) + return r; + if (r > 0) + continue; + + r = dns_answer_add_raw(copy, rr, ifindex, flags); + if (r < 0) + return r; + } + + dns_answer_unref(*a); + *a = copy; + copy = NULL; + + return 1; + } + + /* Only a single reference, edit in-place */ + + i = 0; + for (;;) { + if (i >= (*a)->n_rrs) + break; + + r = dns_resource_record_equal((*a)->items[i].rr, rm); + if (r < 0) + return r; + if (r > 0) { + /* Kill this entry */ + + dns_resource_record_unref((*a)->items[i].rr); + memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); + (*a)->n_rrs --; + continue; + + } else + /* Keep this entry */ + i++; + } + + return 1; +} + +int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags) { DnsResourceRecord *rr_source; int ifindex_source, r; + DnsAnswerFlags flags_source; assert(a); assert(key); /* Copy all RRs matching the specified key from source into *a */ - DNS_ANSWER_FOREACH_IFINDEX(rr_source, ifindex_source, source) { + DNS_ANSWER_FOREACH_FULL(rr_source, ifindex_source, flags_source, source) { r = dns_resource_key_equal(rr_source->key, key); if (r < 0) @@ -429,7 +660,7 @@ int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKe if (r < 0) return r; - r = dns_answer_add(*a, rr_source, ifindex_source); + r = dns_answer_add(*a, rr_source, ifindex_source, flags_source|or_flags); if (r < 0) return r; } @@ -437,6 +668,20 @@ int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKe return 0; } +int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags) { + int r; + + assert(to); + assert(from); + assert(key); + + r = dns_answer_copy_by_key(to, *from, key, or_flags); + if (r < 0) + return r; + + return dns_answer_remove_by_key(from, key); +} + void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) { DnsAnswerItem *items; unsigned i, start, end; @@ -539,3 +784,77 @@ int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free) { return 0; } + +void dns_answer_dump(DnsAnswer *answer, FILE *f) { + DnsResourceRecord *rr; + DnsAnswerFlags flags; + int ifindex; + + if (!f) + f = stdout; + + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { + const char *t; + + fputc('\t', f); + + t = dns_resource_record_to_string(rr); + if (!t) { + log_oom(); + continue; + } + + fputs(t, f); + + if (ifindex != 0 || flags & (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE|DNS_ANSWER_SHARED_OWNER)) + fputs("\t;", f); + + if (ifindex != 0) + printf(" ifindex=%i", ifindex); + if (flags & DNS_ANSWER_AUTHENTICATED) + fputs(" authenticated", f); + if (flags & DNS_ANSWER_CACHEABLE) + fputs(" cachable", f); + if (flags & DNS_ANSWER_SHARED_OWNER) + fputs(" shared-owner", 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 56b462ed7e..1875fd6136 100644 --- a/src/resolve/resolved-dns-answer.h +++ b/src/resolve/resolved-dns-answer.h @@ -32,11 +32,18 @@ typedef struct DnsAnswerItem DnsAnswerItem; * can qualify A and AAAA RRs referring to a local link with the * right ifindex. * - * Note that we usually encode the empty answer as a simple NULL. */ + * Note that we usually encode the the empty DnsAnswer object as a simple NULL. */ + +typedef enum DnsAnswerFlags { + DNS_ANSWER_AUTHENTICATED = 1, /* Item has been authenticated */ + DNS_ANSWER_CACHEABLE = 2, /* Item is subject to caching */ + DNS_ANSWER_SHARED_OWNER = 4, /* For mDNS: RRset may be owner by multiple peers */ +} DnsAnswerFlags; struct DnsAnswerItem { DnsResourceRecord *rr; int ifindex; + DnsAnswerFlags flags; }; struct DnsAnswer { @@ -49,15 +56,18 @@ DnsAnswer *dns_answer_new(unsigned n); DnsAnswer *dns_answer_ref(DnsAnswer *a); DnsAnswer *dns_answer_unref(DnsAnswer *a); -int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex); -int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex); +int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags); +int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags); int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl); -int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key); -int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr); +int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags); +int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *combined_flags); +int dns_answer_contains_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags); +int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a); +int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone); -int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret); -int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret); +int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); +int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret); int dns_answer_extend(DnsAnswer **a, DnsAnswer *b); @@ -68,12 +78,19 @@ int dns_answer_reserve(DnsAnswer **a, unsigned n_free); int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free); int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key); -int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key); +int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rr); + +int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags); +int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags); + +bool dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname); static inline unsigned dns_answer_size(DnsAnswer *a) { return a ? a->n_rrs : 0; } +void dns_answer_dump(DnsAnswer *answer, FILE *f); + DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); #define _DNS_ANSWER_FOREACH(q, kk, a) \ @@ -93,6 +110,36 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); 0; \ }); \ (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ - UNIQ_T(i, q)++, (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0)) + UNIQ_T(i, q)++, \ + (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ + (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0)) #define DNS_ANSWER_FOREACH_IFINDEX(kk, ifindex, a) _DNS_ANSWER_FOREACH_IFINDEX(UNIQ, kk, ifindex, a) + +#define _DNS_ANSWER_FOREACH_FLAGS(q, kk, fl, a) \ + for (unsigned UNIQ_T(i, q) = ({ \ + (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ + (fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \ + 0; \ + }); \ + (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ + UNIQ_T(i, q)++, \ + (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ + (fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0)) + +#define DNS_ANSWER_FOREACH_FLAGS(kk, flags, a) _DNS_ANSWER_FOREACH_FLAGS(UNIQ, kk, flags, a) + +#define _DNS_ANSWER_FOREACH_FULL(q, kk, ifi, fl, a) \ + for (unsigned UNIQ_T(i, q) = ({ \ + (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ + (ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \ + (fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \ + 0; \ + }); \ + (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ + UNIQ_T(i, q)++, \ + (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ + (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0), \ + (fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0)) + +#define DNS_ANSWER_FOREACH_FULL(kk, ifindex, flags, a) _DNS_ANSWER_FOREACH_FULL(UNIQ, kk, ifindex, flags, a) diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index 9ab44400bd..1ac8a223d8 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -26,11 +26,12 @@ #include "resolved-dns-packet.h" #include "string-util.h" -/* Never cache more than 1K entries */ -#define CACHE_MAX 1024 +/* Never cache more than 4K entries. RFC 1536, Section 5 suggests to + * leave DNS caches unbounded, but that's crazy. */ +#define CACHE_MAX 4096 -/* We never keep any item longer than 10min in our cache */ -#define CACHE_TTL_MAX_USEC (10 * USEC_PER_MINUTE) +/* We never keep any item longer than 2h in our cache */ +#define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR) typedef enum DnsCacheItemType DnsCacheItemType; typedef struct DnsCacheItem DnsCacheItem; @@ -42,14 +43,19 @@ enum DnsCacheItemType { }; struct DnsCacheItem { + DnsCacheItemType type; DnsResourceKey *key; DnsResourceRecord *rr; + usec_t until; - DnsCacheItemType type; - unsigned prioq_idx; - bool authenticated; + bool authenticated:1; + bool shared_owner:1; + + int ifindex; int owner_family; union in_addr_union owner_address; + + unsigned prioq_idx; LIST_FIELDS(DnsCacheItem, by_key); }; @@ -64,7 +70,7 @@ static void dns_cache_item_free(DnsCacheItem *i) { DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free); -static void dns_cache_item_remove_and_free(DnsCache *c, DnsCacheItem *i) { +static void dns_cache_item_unlink_and_free(DnsCache *c, DnsCacheItem *i) { DnsCacheItem *first; assert(c); @@ -85,34 +91,55 @@ static void dns_cache_item_remove_and_free(DnsCache *c, DnsCacheItem *i) { dns_cache_item_free(i); } -void dns_cache_flush(DnsCache *c) { - DnsCacheItem *i; +static bool dns_cache_remove_by_rr(DnsCache *c, DnsResourceRecord *rr) { + DnsCacheItem *first, *i; + int r; + + first = hashmap_get(c->by_key, rr->key); + LIST_FOREACH(by_key, i, first) { + r = dns_resource_record_equal(i->rr, rr); + if (r < 0) + return r; + if (r > 0) { + dns_cache_item_unlink_and_free(c, i); + return true; + } + } + + return false; +} + +static bool dns_cache_remove_by_key(DnsCache *c, DnsResourceKey *key) { + DnsCacheItem *first, *i, *n; assert(c); + assert(key); - while ((i = hashmap_first(c->by_key))) - dns_cache_item_remove_and_free(c, i); + first = hashmap_remove(c->by_key, key); + if (!first) + return false; - assert(hashmap_size(c->by_key) == 0); - assert(prioq_size(c->by_expiry) == 0); + LIST_FOREACH_SAFE(by_key, i, n, first) { + prioq_remove(c->by_expiry, i, &i->prioq_idx); + dns_cache_item_free(i); + } - c->by_key = hashmap_free(c->by_key); - c->by_expiry = prioq_free(c->by_expiry); + return true; } -static bool dns_cache_remove(DnsCache *c, DnsResourceKey *key) { - DnsCacheItem *i; - bool exist = false; +void dns_cache_flush(DnsCache *c) { + DnsResourceKey *key; assert(c); - assert(key); - while ((i = hashmap_get(c->by_key, key))) { - dns_cache_item_remove_and_free(c, i); - exist = true; - } + while ((key = hashmap_first_key(c->by_key))) + dns_cache_remove_by_key(c, key); - return exist; + assert(hashmap_size(c->by_key) == 0); + assert(prioq_size(c->by_expiry) == 0); + + c->by_key = hashmap_free(c->by_key); + c->by_expiry = prioq_free(c->by_expiry); } static void dns_cache_make_space(DnsCache *c, unsigned add) { @@ -142,7 +169,7 @@ static void dns_cache_make_space(DnsCache *c, unsigned add) { /* Take an extra reference to the key so that it * doesn't go away in the middle of the remove call */ key = dns_resource_key_ref(i->key); - dns_cache_remove(c, key); + dns_cache_remove_by_key(c, key); } } @@ -154,7 +181,6 @@ void dns_cache_prune(DnsCache *c) { /* Remove all entries that are past their TTL */ for (;;) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; DnsCacheItem *i; i = prioq_peek(c->by_expiry); @@ -167,10 +193,19 @@ void dns_cache_prune(DnsCache *c) { if (i->until > t) break; - /* Take an extra reference to the key so that it - * doesn't go away in the middle of the remove call */ - key = dns_resource_key_ref(i->key); - dns_cache_remove(c, key); + /* Depending whether this is an mDNS shared entry + * either remove only this one RR or the whole + * RRset */ + if (i->shared_owner) + dns_cache_item_unlink_and_free(c, i); + else { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + /* Take an extra reference to the key so that it + * doesn't go away in the middle of the remove call */ + key = dns_resource_key_ref(i->key); + dns_cache_remove_by_key(c, key); + } } } @@ -213,6 +248,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 { @@ -239,10 +287,57 @@ static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) { return NULL; } -static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsResourceRecord *rr, bool authenticated, usec_t timestamp) { +static usec_t calculate_until(DnsResourceRecord *rr, uint32_t nsec_ttl, usec_t timestamp, bool use_soa_minimum) { + uint32_t ttl; + usec_t u; + + assert(rr); + + ttl = MIN(rr->ttl, nsec_ttl); + if (rr->key->type == DNS_TYPE_SOA && use_soa_minimum) { + /* If this is a SOA RR, and it is requested, clamp to + * the SOA's minimum field. This is used when we do + * negative caching, to determine the TTL for the + * negative caching entry. See RFC 2308, Section + * 5. */ + + if (ttl > rr->soa.minimum) + ttl = rr->soa.minimum; + } + + u = ttl * USEC_PER_SEC; + if (u > CACHE_TTL_MAX_USEC) + u = CACHE_TTL_MAX_USEC; + + if (rr->expiry != USEC_INFINITY) { + usec_t left; + + /* Make use of the DNSSEC RRSIG expiry info, if we + * have it */ + + left = LESS_BY(rr->expiry, now(CLOCK_REALTIME)); + if (u > left) + u = left; + } + + return timestamp + u; +} + +static void dns_cache_item_update_positive( + DnsCache *c, + DnsCacheItem *i, + DnsResourceRecord *rr, + bool authenticated, + bool shared_owner, + usec_t timestamp, + int ifindex, + int owner_family, + const union in_addr_union *owner_address) { + assert(c); assert(i); assert(rr); + assert(owner_address); i->type = DNS_CACHE_POSITIVE; @@ -259,8 +354,14 @@ static void dns_cache_item_update_positive(DnsCache *c, DnsCacheItem *i, DnsReso dns_resource_key_unref(i->key); i->key = dns_resource_key_ref(rr->key); + i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); i->authenticated = authenticated; - i->until = timestamp + MIN(rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC); + i->shared_owner = shared_owner; + + i->ifindex = ifindex; + + i->owner_family = owner_family; + i->owner_address = *owner_address; prioq_reshuffle(c->by_expiry, i, &i->prioq_idx); } @@ -269,7 +370,9 @@ static int dns_cache_put_positive( DnsCache *c, DnsResourceRecord *rr, bool authenticated, + bool shared_owner, usec_t timestamp, + int ifindex, int owner_family, const union in_addr_union *owner_address) { @@ -282,9 +385,15 @@ static int dns_cache_put_positive( assert(rr); assert(owner_address); - /* New TTL is 0? Delete the entry... */ + /* Never cache pseudo RRs */ + if (dns_class_is_pseudo(rr->key->class)) + return 0; + if (dns_type_is_pseudo(rr->key->type)) + return 0; + + /* New TTL is 0? Delete this specific entry... */ if (rr->ttl <= 0) { - k = dns_cache_remove(c, rr->key); + k = dns_cache_remove_by_rr(c, rr); if (log_get_max_level() >= LOG_DEBUG) { r = dns_resource_key_to_string(rr->key, &key_str); @@ -300,15 +409,19 @@ static int dns_cache_put_positive( return 0; } - if (rr->key->class == DNS_CLASS_ANY) - return 0; - if (rr->key->type == DNS_TYPE_ANY) - return 0; - - /* Entry exists already? Update TTL and timestamp */ + /* Entry exists already? Update TTL, timestamp and owner*/ existing = dns_cache_get(c, rr); if (existing) { - dns_cache_item_update_positive(c, existing, rr, authenticated, timestamp); + dns_cache_item_update_positive( + c, + existing, + rr, + authenticated, + shared_owner, + timestamp, + ifindex, + owner_family, + owner_address); return 0; } @@ -326,11 +439,13 @@ static int dns_cache_put_positive( i->type = DNS_CACHE_POSITIVE; i->key = dns_resource_key_ref(rr->key); i->rr = dns_resource_record_ref(rr); - i->until = timestamp + MIN(i->rr->ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC); - i->prioq_idx = PRIOQ_IDX_NULL; + i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); + i->authenticated = authenticated; + i->shared_owner = shared_owner; + i->ifindex = ifindex; i->owner_family = owner_family; i->owner_address = *owner_address; - i->authenticated = authenticated; + i->prioq_idx = PRIOQ_IDX_NULL; r = dns_cache_link_item(c, i); if (r < 0) @@ -341,7 +456,7 @@ static int dns_cache_put_positive( if (r < 0) return r; - log_debug("Added cache entry for %s", key_str); + log_debug("Added positive cache entry for %s", key_str); } i = NULL; @@ -353,8 +468,9 @@ static int dns_cache_put_negative( DnsResourceKey *key, int rcode, bool authenticated, + uint32_t nsec_ttl, usec_t timestamp, - uint32_t soa_ttl, + DnsResourceRecord *soa, int owner_family, const union in_addr_union *owner_address) { @@ -364,23 +480,24 @@ static int dns_cache_put_negative( assert(c); assert(key); + assert(soa); assert(owner_address); - dns_cache_remove(c, key); - - if (key->class == DNS_CLASS_ANY) + /* Never cache pseudo RR keys. DNS_TYPE_ANY is particularly + * important to filter out as we use this as a pseudo-type for + * NXDOMAIN entries */ + if (dns_class_is_pseudo(key->class)) return 0; - if (key->type == DNS_TYPE_ANY) - /* This is particularly important to filter out as we use this as a - * pseudo-type for NXDOMAIN entries */ + if (dns_type_is_pseudo(key->type)) return 0; - if (soa_ttl <= 0) { + + if (nsec_ttl <= 0 || soa->soa.minimum <= 0 || soa->ttl <= 0) { if (log_get_max_level() >= LOG_DEBUG) { r = dns_resource_key_to_string(key, &key_str); if (r < 0) return r; - log_debug("Not caching negative entry with zero SOA TTL: %s", key_str); + log_debug("Not caching negative entry with zero SOA/NSEC/NSEC3 TTL: %s", key_str); } return 0; @@ -400,11 +517,11 @@ static int dns_cache_put_negative( return -ENOMEM; i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN; - i->until = timestamp + MIN(soa_ttl * USEC_PER_SEC, CACHE_TTL_MAX_USEC); - i->prioq_idx = PRIOQ_IDX_NULL; + i->until = calculate_until(soa, nsec_ttl, timestamp, true); + i->authenticated = authenticated; i->owner_family = owner_family; i->owner_address = *owner_address; - i->authenticated = authenticated; + i->prioq_idx = PRIOQ_IDX_NULL; if (i->type == DNS_CACHE_NXDOMAIN) { /* NXDOMAIN entries should apply equally to all types, so we use ANY as @@ -412,6 +529,14 @@ static int dns_cache_put_negative( i->key = dns_resource_key_new(key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(key)); if (!i->key) return -ENOMEM; + + /* Make sure to remove any previous entry for this + * specific ANY key. (For non-ANY keys the cache data + * is already cleared by the caller.) Note that we + * don't bother removing positive or NODATA cache + * items in this case, because it would either be slow + * or require explicit indexing by name */ + dns_cache_remove_by_key(c, key); } else i->key = dns_resource_key_ref(key); @@ -431,30 +556,80 @@ static int dns_cache_put_negative( return 0; } +static void dns_cache_remove_previous( + DnsCache *c, + DnsResourceKey *key, + DnsAnswer *answer) { + + DnsResourceRecord *rr; + DnsAnswerFlags flags; + + assert(c); + + /* First, if we were passed a key (i.e. on LLMNR/DNS, but + * not on mDNS), delete all matching old RRs, so that we only + * keep complete by_key in place. */ + if (key) + dns_cache_remove_by_key(c, key); + + /* Second, flush all entries matching the answer, unless this + * is an RR that is explicitly marked to be "shared" between + * peers (i.e. mDNS RRs without the flush-cache bit set). */ + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + if ((flags & DNS_ANSWER_CACHEABLE) == 0) + continue; + + if (flags & DNS_ANSWER_SHARED_OWNER) + continue; + + dns_cache_remove_by_key(c, rr->key); + } +} + +static bool rr_eligible(DnsResourceRecord *rr) { + assert(rr); + + /* When we see an NSEC/NSEC3 RR, we'll only cache it if it is from the lower zone, not the upper zone, since + * that's where the interesting bits are (with exception of DS RRs). Of course, this way we cannot derive DS + * existence from any cached NSEC/NSEC3, but that should be fine. */ + + switch (rr->key->type) { + + case DNS_TYPE_NSEC: + return !bitmap_isset(rr->nsec.types, DNS_TYPE_NS) || + bitmap_isset(rr->nsec.types, DNS_TYPE_SOA); + + case DNS_TYPE_NSEC3: + return !bitmap_isset(rr->nsec3.types, DNS_TYPE_NS) || + bitmap_isset(rr->nsec3.types, DNS_TYPE_SOA); + + default: + return true; + } +} + int dns_cache_put( DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, - unsigned max_rrs, bool authenticated, + uint32_t nsec_ttl, usec_t timestamp, int owner_family, const union in_addr_union *owner_address) { DnsResourceRecord *soa = NULL, *rr; - unsigned cache_keys, i; - int r; + DnsAnswerFlags flags; + unsigned cache_keys; + int r, ifindex; assert(c); + assert(owner_address); - if (key) { - /* First, if we were passed a key, delete all matching old RRs, - * so that we only keep complete by_key in place. */ - dns_cache_remove(c, key); - } + dns_cache_remove_previous(c, key, answer); - if (!answer) { + if (dns_answer_size(answer) <= 0) { if (log_get_max_level() >= LOG_DEBUG) { _cleanup_free_ char *key_str = NULL; @@ -468,19 +643,13 @@ int dns_cache_put( return 0; } - DNS_ANSWER_FOREACH(rr, answer) - if (rr->key->cache_flush) - dns_cache_remove(c, rr->key); - /* We only care for positive replies and NXDOMAINs, on all * other replies we will simply flush the respective entries, * and that's it */ - if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) return 0; - cache_keys = answer->n_rrs; - + cache_keys = dns_answer_size(answer); if (key) cache_keys ++; @@ -491,19 +660,33 @@ int dns_cache_put( timestamp = now(clock_boottime_or_monotonic()); /* Second, add in positive entries for all contained RRs */ - for (i = 0; i < MIN(max_rrs, answer->n_rrs); i++) { - rr = answer->items[i].rr; + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { + if ((flags & DNS_ANSWER_CACHEABLE) == 0) + continue; - r = dns_cache_put_positive(c, rr, authenticated, timestamp, owner_family, owner_address); + r = rr_eligible(rr); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_cache_put_positive( + c, + rr, + flags & DNS_ANSWER_AUTHENTICATED, + flags & DNS_ANSWER_SHARED_OWNER, + timestamp, + ifindex, + owner_family, owner_address); if (r < 0) goto fail; } - if (!key) + if (!key) /* mDNS doesn't know negative caching, really */ return 0; /* Third, add in negative entries if the key has no RR */ - r = dns_answer_match_key(answer, key); + r = dns_answer_match_key(answer, key, NULL); if (r < 0) goto fail; if (r > 0) @@ -512,7 +695,7 @@ int dns_cache_put( /* But not if it has a matching CNAME/DNAME (the negative * caching will be done on the canonical name, not on the * alias) */ - r = dns_answer_find_cname_or_dname(answer, key, NULL); + r = dns_answer_find_cname_or_dname(answer, key, NULL, NULL); if (r < 0) goto fail; if (r > 0) @@ -521,14 +704,26 @@ int dns_cache_put( /* See https://tools.ietf.org/html/rfc2308, which say that a * matching SOA record in the packet is used to to enable * negative caching. */ - - r = dns_answer_find_soa(answer, key, &soa); + r = dns_answer_find_soa(answer, key, &soa, &flags); if (r < 0) goto fail; if (r == 0) return 0; - r = dns_cache_put_negative(c, key, rcode, authenticated, timestamp, MIN(soa->soa.minimum, soa->ttl), owner_family, owner_address); + /* Refuse using the SOA data if it is unsigned, but the key is + * signed */ + if (authenticated && (flags & DNS_ANSWER_AUTHENTICATED) == 0) + return 0; + + r = dns_cache_put_negative( + c, + key, + rcode, + authenticated, + nsec_ttl, + timestamp, + soa, + owner_family, owner_address); if (r < 0) goto fail; @@ -539,10 +734,14 @@ fail: * added, just in case */ if (key) - dns_cache_remove(c, key); + dns_cache_remove_by_key(c, key); + + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + if ((flags & DNS_ANSWER_CACHEABLE) == 0) + continue; - for (i = 0; i < answer->n_rrs; i++) - dns_cache_remove(c, answer->items[i].rr->key); + dns_cache_remove_by_key(c, rr->key); + } return r; } @@ -570,11 +769,7 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D if (i && i->type == DNS_CACHE_NXDOMAIN) return i; - /* The following record types should never be redirected. See - * <https://tools.ietf.org/html/rfc4035#section-2.5>. */ - if (!IN_SET(k->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME, - DNS_TYPE_NSEC3, DNS_TYPE_NSEC, DNS_TYPE_RRSIG, - DNS_TYPE_NXT, DNS_TYPE_SIG, DNS_TYPE_KEY)) { + if (dns_type_may_redirect(k->type)) { /* Check if we have a CNAME record instead */ i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_CNAME, n)); if (i) @@ -582,8 +777,6 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D /* OK, let's look for cached DNAME records. */ for (;;) { - char label[DNS_LABEL_MAX]; - if (isempty(n)) return NULL; @@ -592,13 +785,13 @@ static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, D return i; /* Jump one label ahead */ - r = dns_label_unescape(&n, label, sizeof(label)); + r = dns_name_parent(&n); if (r <= 0) return NULL; } } - if (k-> type != DNS_TYPE_NSEC) { + if (k->type != DNS_TYPE_NSEC) { /* Check if we have an NSEC record instead for the name. */ i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_NSEC, n)); if (i) @@ -637,6 +830,8 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r log_debug("Ignoring cache for ANY lookup: %s", key_str); } + c->n_miss++; + *ret = NULL; *rcode = DNS_RCODE_SUCCESS; return 0; @@ -654,6 +849,8 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r log_debug("Cache miss for %s", key_str); } + c->n_miss++; + *ret = NULL; *rcode = DNS_RCODE_SUCCESS; return 0; @@ -674,7 +871,10 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r have_non_authenticated = true; } - if (nsec && key->type != DNS_TYPE_NSEC) { + if (nsec && !IN_SET(key->type, DNS_TYPE_NSEC, DNS_TYPE_DS)) { + /* Note that we won't derive information for DS RRs from an NSEC, because we only cache NSEC RRs from + * the lower-zone of a zone cut, but the DS RRs are on the upper zone. */ + if (log_get_max_level() >= LOG_DEBUG) { r = dns_resource_key_to_string(key, &key_str); if (r < 0) @@ -691,9 +891,15 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r *rcode = DNS_RCODE_SUCCESS; *authenticated = nsec->authenticated; - return !bitmap_isset(nsec->rr->nsec.types, key->type) && - !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) && - !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME); + if (!bitmap_isset(nsec->rr->nsec.types, key->type) && + !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) && + !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME)) { + c->n_hit++; + return 1; + } + + c->n_miss++; + return 0; } if (log_get_max_level() >= LOG_DEBUG) { @@ -708,6 +914,8 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r } if (n <= 0) { + c->n_hit++; + *ret = NULL; *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS; *authenticated = have_authenticated && !have_non_authenticated; @@ -722,11 +930,13 @@ int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **r if (!j->rr) continue; - r = dns_answer_add(answer, j->rr, 0); + r = dns_answer_add(answer, j->rr, j->ifindex, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0); if (r < 0) return r; } + c->n_hit++; + *ret = answer; *rcode = DNS_RCODE_SUCCESS; *authenticated = have_authenticated && !have_non_authenticated; @@ -784,12 +994,10 @@ int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p) { DnsCacheItem *j; LIST_FOREACH(by_key, j, i) { - _cleanup_free_ char *t = NULL; - if (!j->rr) continue; - if (!dns_key_is_shared(j->rr->key)) + if (!j->shared_owner) continue; r = dns_packet_append_rr(p, j->rr, NULL, NULL); @@ -837,13 +1045,13 @@ void dns_cache_dump(DnsCache *cache, FILE *f) { DnsCacheItem *j; LIST_FOREACH(by_key, j, i) { - _cleanup_free_ char *t = NULL; fputc('\t', f); if (j->rr) { - r = dns_resource_record_to_string(j->rr, &t); - if (r < 0) { + const char *t; + t = dns_resource_record_to_string(j->rr); + if (!t) { log_oom(); continue; } @@ -851,13 +1059,14 @@ void dns_cache_dump(DnsCache *cache, FILE *f) { fputs(t, f); fputc('\n', f); } else { - r = dns_resource_key_to_string(j->key, &t); + _cleanup_free_ char *z = NULL; + r = dns_resource_key_to_string(j->key, &z); if (r < 0) { log_oom(); continue; } - fputs(t, f); + fputs(z, f); fputs(" -- ", f); fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f); fputc('\n', f); @@ -872,3 +1081,10 @@ bool dns_cache_is_empty(DnsCache *cache) { return hashmap_isempty(cache->by_key); } + +unsigned dns_cache_size(DnsCache *cache) { + if (!cache) + return 0; + + return hashmap_size(cache->by_key); +} diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h index 0f28bbe543..e61b285df4 100644 --- a/src/resolve/resolved-dns-cache.h +++ b/src/resolve/resolved-dns-cache.h @@ -29,6 +29,8 @@ typedef struct DnsCache { Hashmap *by_key; Prioq *by_expiry; + unsigned n_hit; + unsigned n_miss; } DnsCache; #include "resolved-dns-answer.h" @@ -39,7 +41,7 @@ typedef struct DnsCache { void dns_cache_flush(DnsCache *c); void dns_cache_prune(DnsCache *c); -int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, unsigned max_rrs, bool authenticated, usec_t timestamp, int owner_family, const union in_addr_union *owner_address); +int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, bool authenticated, uint32_t nsec_ttl, usec_t timestamp, int owner_family, const union in_addr_union *owner_address); int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **answer, bool *authenticated); int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address); @@ -47,4 +49,6 @@ int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_ void dns_cache_dump(DnsCache *cache, FILE *f); bool dns_cache_is_empty(DnsCache *cache); +unsigned dns_cache_size(DnsCache *cache); + int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p); diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index df12e86167..21cf161494 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -19,40 +19,26 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#ifdef HAVE_GCRYPT #include <gcrypt.h> +#endif #include "alloc-util.h" #include "dns-domain.h" +#include "hexdecoct.h" #include "resolved-dns-dnssec.h" #include "resolved-dns-packet.h" #include "string-table.h" -/* Open question: - * - * How does the DNSSEC canonical form of a hostname with a label - * containing a dot look like, the way DNS-SD does it? - * - * TODO: - * - * - Iterative validation - * - NSEC proof of non-existance - * - NSEC3 proof of non-existance - * - Make trust anchor store read additional DS+DNSKEY data from disk - * - wildcard zones compatibility - * - multi-label zone compatibility - * - DNSSEC cname/dname compatibility - * - per-interface DNSSEC setting - * - DSA support - * - EC support? - * - * */ - #define VERIFY_RRS_MAX 256 #define MAX_KEY_SIZE (32*1024) /* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */ #define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE) +/* Maximum number of NSEC3 iterations we'll do. RFC5155 says 2500 shall be the maximum useful value */ +#define NSEC3_ITERATIONS_MAX 2500 + /* * The DNSSEC Chain of trust: * @@ -64,23 +50,9 @@ * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS */ -static bool dnssec_algorithm_supported(int algorithm) { - return IN_SET(algorithm, - DNSSEC_ALGORITHM_RSASHA1, - DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, - DNSSEC_ALGORITHM_RSASHA256, - DNSSEC_ALGORITHM_RSASHA512); -} - -static bool dnssec_digest_supported(int digest) { - return IN_SET(digest, - DNSSEC_DIGEST_SHA1, - DNSSEC_DIGEST_SHA256); -} - -uint16_t dnssec_keytag(DnsResourceRecord *dnskey) { +uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) { const uint8_t *p; - uint32_t sum; + uint32_t sum, f; size_t i; /* The algorithm from RFC 4034, Appendix B. */ @@ -88,8 +60,12 @@ uint16_t dnssec_keytag(DnsResourceRecord *dnskey) { assert(dnskey); assert(dnskey->key->type == DNS_TYPE_DNSKEY); - sum = (uint32_t) dnskey->dnskey.flags + - ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm); + f = (uint32_t) dnskey->dnskey.flags; + + if (mask_revoke) + f &= ~DNSKEY_FLAG_REVOKE; + + sum = f + ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm); p = dnskey->dnskey.key; @@ -101,6 +77,70 @@ uint16_t dnssec_keytag(DnsResourceRecord *dnskey) { return sum & UINT32_C(0xFFFF); } +int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { + size_t c = 0; + int r; + + /* Converts the specified hostname into DNSSEC canonicalized + * form. */ + + if (buffer_max < 2) + return -ENOBUFS; + + for (;;) { + r = dns_label_unescape(&n, buffer, buffer_max); + if (r < 0) + return r; + if (r == 0) + break; + + if (buffer_max < (size_t) r + 2) + return -ENOBUFS; + + /* The DNSSEC canonical form is not clear on what to + * do with dots appearing in labels, the way DNS-SD + * does it. Refuse it for now. */ + + if (memchr(buffer, '.', r)) + return -EINVAL; + + ascii_strlower_n(buffer, (size_t) r); + buffer[r] = '.'; + + buffer += r + 1; + c += r + 1; + + buffer_max -= r + 1; + } + + if (c <= 0) { + /* Not even a single label: this is the root domain name */ + + assert(buffer_max > 2); + buffer[0] = '.'; + buffer[1] = 0; + + return 1; + } + + return (int) c; +} + +#ifdef HAVE_GCRYPT + +static void initialize_libgcrypt(void) { + const char *p; + + if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) + return; + + p = gcry_check_version("1.4.5"); + assert(p); + + gcry_control(GCRYCTL_DISABLE_SECMEM); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); +} + static int rr_compare(const void *a, const void *b) { DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b; size_t m; @@ -115,21 +155,21 @@ static int rr_compare(const void *a, const void *b) { assert(*y); assert((*y)->wire_format); - m = MIN((*x)->wire_format_size, (*y)->wire_format_size); + m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y)); - r = memcmp((*x)->wire_format, (*y)->wire_format, m); + r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m); if (r != 0) return r; - if ((*x)->wire_format_size < (*y)->wire_format_size) + if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) return -1; - else if ((*x)->wire_format_size > (*y)->wire_format_size) + else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) return 1; return 0; } -static int dnssec_rsa_verify( +static int dnssec_rsa_verify_raw( const char *hash_algorithm, const void *signature, size_t signature_size, const void *data, size_t data_size, @@ -219,6 +259,196 @@ finish: return r; } +static int dnssec_rsa_verify( + const char *hash_algorithm, + const void *hash, size_t hash_size, + DnsResourceRecord *rrsig, + DnsResourceRecord *dnskey) { + + size_t exponent_size, modulus_size; + void *exponent, *modulus; + + assert(hash_algorithm); + assert(hash); + assert(hash_size > 0); + assert(rrsig); + assert(dnskey); + + if (*(uint8_t*) dnskey->dnskey.key == 0) { + /* exponent is > 255 bytes long */ + + exponent = (uint8_t*) dnskey->dnskey.key + 3; + exponent_size = + ((size_t) (((uint8_t*) dnskey->dnskey.key)[1]) << 8) | + ((size_t) ((uint8_t*) dnskey->dnskey.key)[2]); + + if (exponent_size < 256) + return -EINVAL; + + if (3 + exponent_size >= dnskey->dnskey.key_size) + return -EINVAL; + + modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size; + modulus_size = dnskey->dnskey.key_size - 3 - exponent_size; + + } else { + /* exponent is <= 255 bytes long */ + + exponent = (uint8_t*) dnskey->dnskey.key + 1; + exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0]; + + if (exponent_size <= 0) + return -EINVAL; + + if (1 + exponent_size >= dnskey->dnskey.key_size) + return -EINVAL; + + modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size; + modulus_size = dnskey->dnskey.key_size - 1 - exponent_size; + } + + return dnssec_rsa_verify_raw( + hash_algorithm, + rrsig->rrsig.signature, rrsig->rrsig.signature_size, + hash, hash_size, + exponent, exponent_size, + modulus, modulus_size); +} + +static int dnssec_ecdsa_verify_raw( + const char *hash_algorithm, + const char *curve, + const void *signature_r, size_t signature_r_size, + const void *signature_s, size_t signature_s_size, + const void *data, size_t data_size, + const void *key, size_t key_size) { + + gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; + gcry_mpi_t q = NULL, r = NULL, s = NULL; + gcry_error_t ge; + int k; + + assert(hash_algorithm); + + ge = gcry_mpi_scan(&r, GCRYMPI_FMT_USG, signature_r, signature_r_size, NULL); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature_s, signature_s_size, NULL); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_mpi_scan(&q, GCRYMPI_FMT_USG, key, key_size, NULL); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&signature_sexp, + NULL, + "(sig-val (ecdsa (r %m) (s %m)))", + r, + s); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&data_sexp, + NULL, + "(data (flags rfc6979) (hash %s %b))", + hash_algorithm, + (int) data_size, + data); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&public_key_sexp, + NULL, + "(public-key (ecc (curve %s) (q %m)))", + curve, + q); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp); + if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE) + k = 0; + else if (ge != 0) { + log_debug("ECDSA signature check failed: %s", gpg_strerror(ge)); + k = -EIO; + } else + k = 1; +finish: + if (r) + gcry_mpi_release(r); + if (s) + gcry_mpi_release(s); + if (q) + gcry_mpi_release(q); + + if (public_key_sexp) + gcry_sexp_release(public_key_sexp); + if (signature_sexp) + gcry_sexp_release(signature_sexp); + if (data_sexp) + gcry_sexp_release(data_sexp); + + return k; +} + +static int dnssec_ecdsa_verify( + const char *hash_algorithm, + int algorithm, + const void *hash, size_t hash_size, + DnsResourceRecord *rrsig, + DnsResourceRecord *dnskey) { + + const char *curve; + size_t key_size; + uint8_t *q; + + assert(hash); + assert(hash_size); + assert(rrsig); + assert(dnskey); + + if (algorithm == DNSSEC_ALGORITHM_ECDSAP256SHA256) { + key_size = 32; + curve = "NIST P-256"; + } else if (algorithm == DNSSEC_ALGORITHM_ECDSAP384SHA384) { + key_size = 48; + curve = "NIST P-384"; + } else + return -EOPNOTSUPP; + + if (dnskey->dnskey.key_size != key_size * 2) + return -EINVAL; + + if (rrsig->rrsig.signature_size != key_size * 2) + return -EINVAL; + + q = alloca(key_size*2 + 1); + q[0] = 0x04; /* Prepend 0x04 to indicate an uncompressed key */ + memcpy(q+1, dnskey->dnskey.key, key_size*2); + + return dnssec_ecdsa_verify_raw( + hash_algorithm, + curve, + rrsig->rrsig.signature, key_size, + (uint8_t*) rrsig->rrsig.signature + key_size, key_size, + hash, hash_size, + q, key_size*2+1); +} + static void md_add_uint8(gcry_md_hd_t md, uint8_t v) { gcry_md_write(md, &v, sizeof(v)); } @@ -233,6 +463,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; @@ -245,8 +526,9 @@ static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) { expiration = rrsig->rrsig.expiration * USEC_PER_SEC; inception = rrsig->rrsig.inception * USEC_PER_SEC; + /* Consider inverted validity intervals as expired */ if (inception > expiration) - return -EKEYREJECTED; + return true; /* Permit a certain amount of clock skew of 10% of the valid * time range. This takes inspiration from unbound's @@ -268,21 +550,85 @@ static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) { return realtime < inception || realtime > expiration; } +static int algorithm_to_gcrypt_md(uint8_t algorithm) { + + /* Translates a DNSSEC signature algorithm into a gcrypt + * digest identifier. + * + * Note that we implement all algorithms listed as "Must + * implement" and "Recommended to Implement" in RFC6944. We + * don't implement any algorithms that are listed as + * "Optional" or "Must Not Implement". Specifically, we do not + * implement RSAMD5, DSASHA1, DH, DSA-NSEC3-SHA1, and + * GOST-ECC. */ + + switch (algorithm) { + + case DNSSEC_ALGORITHM_RSASHA1: + case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: + return GCRY_MD_SHA1; + + case DNSSEC_ALGORITHM_RSASHA256: + case DNSSEC_ALGORITHM_ECDSAP256SHA256: + return GCRY_MD_SHA256; + + case DNSSEC_ALGORITHM_ECDSAP384SHA384: + return GCRY_MD_SHA384; + + case DNSSEC_ALGORITHM_RSASHA512: + return GCRY_MD_SHA512; + + default: + return -EOPNOTSUPP; + } +} + +static void dnssec_fix_rrset_ttl( + DnsResourceRecord *list[], + unsigned n, + DnsResourceRecord *rrsig, + usec_t realtime) { + + unsigned k; + + assert(list); + assert(n > 0); + assert(rrsig); + + for (k = 0; k < n; k++) { + DnsResourceRecord *rr = list[k]; + + /* Pick the TTL as the minimum of the RR's TTL, the + * RR's original TTL according to the RRSIG and the + * RRSIG's own TTL, see RFC 4035, Section 5.3.3 */ + rr->ttl = MIN3(rr->ttl, rrsig->rrsig.original_ttl, rrsig->ttl); + rr->expiry = rrsig->rrsig.expiration * USEC_PER_SEC; + + /* Copy over information about the signer and wildcard source of synthesis */ + rr->n_skip_labels_source = rrsig->n_skip_labels_source; + rr->n_skip_labels_signer = rrsig->n_skip_labels_signer; + } + + rrsig->expiry = rrsig->rrsig.expiration * USEC_PER_SEC; +} + int dnssec_verify_rrset( DnsAnswer *a, - DnsResourceKey *key, + const DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result) { uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX]; - size_t exponent_size, modulus_size, hash_size; - void *exponent, *modulus, *hash; DnsResourceRecord **list, *rr; + const char *source, *name; gcry_md_hd_t md = NULL; + int r, md_algorithm; size_t k, n = 0; - int r; + size_t hash_size; + void *hash; + bool wildcard; assert(key); assert(rrsig); @@ -295,11 +641,21 @@ int dnssec_verify_rrset( * using the signature "rrsig" and the key "dnskey". It's * assumed the RRSIG and DNSKEY match. */ - if (!dnssec_algorithm_supported(rrsig->rrsig.algorithm)) - return -EOPNOTSUPP; + md_algorithm = algorithm_to_gcrypt_md(rrsig->rrsig.algorithm); + if (md_algorithm == -EOPNOTSUPP) { + *result = DNSSEC_UNSUPPORTED_ALGORITHM; + return 0; + } + if (md_algorithm < 0) + return md_algorithm; - if (a->n_rrs > VERIFY_RRS_MAX) - return -E2BIG; + 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) @@ -309,8 +665,54 @@ int dnssec_verify_rrset( return 0; } + name = DNS_RESOURCE_KEY_NAME(key); + + /* Some keys may only appear signed in the zone apex, and are invalid anywhere else. (SOA, NS...) */ + if (dns_type_apex_only(rrsig->rrsig.type_covered)) { + r = dns_name_equal(rrsig->rrsig.signer, name); + if (r < 0) + return r; + if (r == 0) { + *result = DNSSEC_INVALID; + return 0; + } + } + + /* OTOH DS RRs may not appear in the zone apex, but are valid everywhere else. */ + if (rrsig->rrsig.type_covered == DNS_TYPE_DS) { + r = dns_name_equal(rrsig->rrsig.signer, name); + if (r < 0) + return r; + if (r > 0) { + *result = DNSSEC_INVALID; + return 0; + } + } + + /* Determine the "Source of Synthesis" and whether this is a wildcard RRSIG */ + r = dns_name_suffix(name, rrsig->rrsig.labels, &source); + if (r < 0) + return r; + if (r > 0 && !dns_type_may_wildcard(rrsig->rrsig.type_covered)) { + /* We refuse to validate NSEC3 or SOA RRs that are synthesized from wildcards */ + *result = DNSSEC_INVALID; + return 0; + } + if (r == 1) { + /* If we stripped a single label, then let's see if that maybe was "*". If so, we are not really + * synthesized from a wildcard, we are the wildcard itself. Treat that like a normal name. */ + r = dns_name_startswith(name, "*"); + if (r < 0) + return r; + if (r > 0) + source = name; + + wildcard = r == 0; + } else + wildcard = r > 0; + /* Collect all relevant RRs in a single array, so that we can look at the RRset */ - list = newa(DnsResourceRecord *, a->n_rrs); + list = newa(DnsResourceRecord *, dns_answer_size(a)); DNS_ANSWER_FOREACH(rr, a) { r = dns_resource_key_equal(key, rr->key); @@ -325,6 +727,9 @@ int dnssec_verify_rrset( return r; list[n++] = rr; + + if (n > VERIFY_RRS_MAX) + return -E2BIG; } if (n <= 0) @@ -334,28 +739,12 @@ int dnssec_verify_rrset( qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare); /* OK, the RRs are now in canonical order. Let's calculate the digest */ - switch (rrsig->rrsig.algorithm) { - - case DNSSEC_ALGORITHM_RSASHA1: - case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: - gcry_md_open(&md, GCRY_MD_SHA1, 0); - hash_size = 20; - break; - - case DNSSEC_ALGORITHM_RSASHA256: - gcry_md_open(&md, GCRY_MD_SHA256, 0); - hash_size = 32; - break; + initialize_libgcrypt(); - case DNSSEC_ALGORITHM_RSASHA512: - gcry_md_open(&md, GCRY_MD_SHA512, 0); - hash_size = 64; - break; - - default: - assert_not_reached("Unknown digest"); - } + hash_size = gcry_md_get_algo_dlen(md_algorithm); + assert(hash_size > 0); + gcry_md_open(&md, md_algorithm, 0); if (!md) return -EIO; @@ -372,25 +761,30 @@ int dnssec_verify_rrset( goto finish; gcry_md_write(md, wire_format_name, r); + /* Convert the source of synthesis into wire format */ + r = dns_name_to_wire_format(source, wire_format_name, sizeof(wire_format_name), true); + if (r < 0) + goto finish; + for (k = 0; k < n; k++) { size_t l; + rr = list[k]; - r = dns_name_to_wire_format(DNS_RESOURCE_KEY_NAME(rr->key), wire_format_name, sizeof(wire_format_name), true); - if (r < 0) - goto finish; + /* Hash the source of synthesis. If this is a wildcard, then prefix it with the *. label */ + if (wildcard) + gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2); gcry_md_write(md, wire_format_name, r); md_add_uint16(md, rr->key->type); md_add_uint16(md, rr->key->class); md_add_uint32(md, rrsig->rrsig.original_ttl); - assert(rr->wire_format_rdata_offset <= rr->wire_format_size); - l = rr->wire_format_size - rr->wire_format_rdata_offset; + l = DNS_RESOURCE_RECORD_RDATA_SIZE(rr); assert(l <= 0xFFFF); md_add_uint16(md, (uint16_t) l); - gcry_md_write(md, (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset, l); + gcry_md_write(md, DNS_RESOURCE_RECORD_RDATA(rr), l); } hash = gcry_md_read(md, 0); @@ -399,57 +793,44 @@ int dnssec_verify_rrset( goto finish; } - if (*(uint8_t*) dnskey->dnskey.key == 0) { - /* exponent is > 255 bytes long */ - - exponent = (uint8_t*) dnskey->dnskey.key + 3; - exponent_size = - ((size_t) (((uint8_t*) dnskey->dnskey.key)[0]) << 8) | - ((size_t) ((uint8_t*) dnskey->dnskey.key)[1]); - - if (exponent_size < 256) { - r = -EINVAL; - goto finish; - } - - if (3 + exponent_size >= dnskey->dnskey.key_size) { - r = -EINVAL; - goto finish; - } - - modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size; - modulus_size = dnskey->dnskey.key_size - 3 - exponent_size; - - } else { - /* exponent is <= 255 bytes long */ - - exponent = (uint8_t*) dnskey->dnskey.key + 1; - exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0]; - - if (exponent_size <= 0) { - r = -EINVAL; - goto finish; - } + switch (rrsig->rrsig.algorithm) { - if (1 + exponent_size >= dnskey->dnskey.key_size) { - r = -EINVAL; - goto finish; - } + case DNSSEC_ALGORITHM_RSASHA1: + case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: + case DNSSEC_ALGORITHM_RSASHA256: + case DNSSEC_ALGORITHM_RSASHA512: + r = dnssec_rsa_verify( + gcry_md_algo_name(md_algorithm), + hash, hash_size, + rrsig, + dnskey); + break; - modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size; - modulus_size = dnskey->dnskey.key_size - 1 - exponent_size; + case DNSSEC_ALGORITHM_ECDSAP256SHA256: + case DNSSEC_ALGORITHM_ECDSAP384SHA384: + r = dnssec_ecdsa_verify( + gcry_md_algo_name(md_algorithm), + rrsig->rrsig.algorithm, + hash, hash_size, + rrsig, + dnskey); + break; } - r = dnssec_rsa_verify( - gcry_md_algo_name(gcry_md_get_algo(md)), - rrsig->rrsig.signature, rrsig->rrsig.signature_size, - hash, hash_size, - exponent, exponent_size, - modulus, modulus_size); if (r < 0) goto finish; - *result = r ? DNSSEC_VALIDATED : DNSSEC_INVALID; + /* Now, fix the ttl, expiry, and remember the synthesizing source and the signer */ + if (r > 0) + dnssec_fix_rrset_ttl(list, n, rrsig, realtime); + + if (r == 0) + *result = DNSSEC_INVALID; + else if (wildcard) + *result = DNSSEC_VALIDATED_WILDCARD; + else + *result = DNSSEC_VALIDATED; + r = 0; finish: @@ -457,7 +838,7 @@ finish: return r; } -int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) { +int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) { assert(rrsig); assert(dnskey); @@ -474,18 +855,20 @@ int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnske return 0; if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) return 0; + if (!revoked_ok && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE)) + return 0; if (dnskey->dnskey.protocol != 3) return 0; if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm) return 0; - if (dnssec_keytag(dnskey) != rrsig->rrsig.key_tag) + if (dnssec_keytag(dnskey, false) != rrsig->rrsig.key_tag) return 0; return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer); } -int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig) { +int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) { assert(key); assert(rrsig); @@ -503,19 +886,20 @@ int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig) { int dnssec_verify_rrset_search( DnsAnswer *a, - DnsResourceKey *key, + const DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, - DnssecResult *result) { + DnssecResult *result, + DnsResourceRecord **ret_rrsig) { - bool found_rrsig = false, found_dnskey = false; + bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false; DnsResourceRecord *rrsig; int r; assert(key); assert(result); - /* Verifies all RRs from "a" that match the key "key", against DNSKEY and DS RRs in "validated_dnskeys" */ + /* Verifies all RRs from "a" that match the key "key" against DNSKEYs in "validated_dnskeys" */ if (!a || a->n_rrs <= 0) return -ENODATA; @@ -523,7 +907,9 @@ int dnssec_verify_rrset_search( /* Iterate through each RRSIG RR. */ DNS_ANSWER_FOREACH(rrsig, a) { DnsResourceRecord *dnskey; + DnsAnswerFlags flags; + /* Is this an RRSIG RR that applies to RRs matching our key? */ r = dnssec_key_match_rrsig(key, rrsig); if (r < 0) return r; @@ -533,17 +919,19 @@ int dnssec_verify_rrset_search( found_rrsig = true; /* Look for a matching key */ - DNS_ANSWER_FOREACH(dnskey, validated_dnskeys) { + DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) { DnssecResult one_result; - r = dnssec_rrsig_match_dnskey(rrsig, dnskey); + if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) + continue; + + /* Is this a DNSKEY RR that matches they key of our RRSIG? */ + r = dnssec_rrsig_match_dnskey(rrsig, dnskey, false); if (r < 0) return r; if (r == 0) continue; - found_dnskey = true; - /* Take the time here, if it isn't set yet, so * that we do all validations with the same * time. */ @@ -556,101 +944,112 @@ int dnssec_verify_rrset_search( * combination. */ r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result); - if (r < 0 && r != EOPNOTSUPP) + if (r < 0) return r; - if (one_result == DNSSEC_VALIDATED) { - *result = DNSSEC_VALIDATED; + + switch (one_result) { + + case DNSSEC_VALIDATED: + case DNSSEC_VALIDATED_WILDCARD: + /* Yay, the RR has been validated, + * return immediately, but fix up the expiry */ + if (ret_rrsig) + *ret_rrsig = rrsig; + + *result = one_result; return 0; - } - /* If the signature is invalid, or done using - an unsupported algorithm, let's try another - key and/or signature. After all they - key_tags and stuff are not unique, and - might be shared by multiple keys. */ + case DNSSEC_INVALID: + /* If the signature is invalid, let's try another + key and/or signature. After all they + key_tags and stuff are not unique, and + might be shared by multiple keys. */ + found_invalid = true; + continue; + + case DNSSEC_UNSUPPORTED_ALGORITHM: + /* If the key algorithm is + unsupported, try another + RRSIG/DNSKEY pair, but remember we + encountered this, so that we can + return a proper error when we + encounter nothing better. */ + found_unsupported_algorithm = true; + continue; + + case DNSSEC_SIGNATURE_EXPIRED: + /* If the signature is expired, try + another one, but remember it, so + that we can return this */ + found_expired_rrsig = true; + continue; + + default: + assert_not_reached("Unexpected DNSSEC validation result"); + } } } - if (found_dnskey) + if (found_expired_rrsig) + *result = DNSSEC_SIGNATURE_EXPIRED; + else if (found_unsupported_algorithm) + *result = DNSSEC_UNSUPPORTED_ALGORITHM; + else if (found_invalid) *result = DNSSEC_INVALID; else if (found_rrsig) *result = DNSSEC_MISSING_KEY; else *result = DNSSEC_NO_SIGNATURE; + if (ret_rrsig) + *ret_rrsig = NULL; + return 0; } -int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { - size_t c = 0; +int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) { + DnsResourceRecord *rr; int r; - /* Converts the specified hostname into DNSSEC canonicalized - * form. */ + /* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */ - if (buffer_max < 2) - return -ENOBUFS; - - for (;;) { - size_t i; - - r = dns_label_unescape(&n, buffer, buffer_max); + DNS_ANSWER_FOREACH(rr, a) { + r = dnssec_key_match_rrsig(key, rr); if (r < 0) return r; - if (r == 0) - break; - if (r > 0) { - int k; - - /* DNSSEC validation is always done on the ASCII version of the label */ - k = dns_label_apply_idna(buffer, r, buffer, buffer_max); - if (k < 0) - return k; - if (k > 0) - r = k; - } - - if (buffer_max < (size_t) r + 2) - return -ENOBUFS; - - /* The DNSSEC canonical form is not clear on what to - * do with dots appearing in labels, the way DNS-SD - * does it. Refuse it for now. */ + if (r > 0) + return 1; + } - if (memchr(buffer, '.', r)) - return -EINVAL; + return 0; +} - for (i = 0; i < (size_t) r; i ++) { - if (buffer[i] >= 'A' && buffer[i] <= 'Z') - buffer[i] = buffer[i] - 'A' + 'a'; - } +static int digest_to_gcrypt_md(uint8_t algorithm) { - buffer[r] = '.'; + /* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */ - buffer += r + 1; - c += r + 1; + switch (algorithm) { - buffer_max -= r + 1; - } + case DNSSEC_DIGEST_SHA1: + return GCRY_MD_SHA1; - if (c <= 0) { - /* Not even a single label: this is the root domain name */ + case DNSSEC_DIGEST_SHA256: + return GCRY_MD_SHA256; - assert(buffer_max > 2); - buffer[0] = '.'; - buffer[1] = 0; + case DNSSEC_DIGEST_SHA384: + return GCRY_MD_SHA384; - return 1; + default: + return -EOPNOTSUPP; } - - return (int) c; } -int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) { - gcry_md_hd_t md = NULL; +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; + int md_algorithm, r; void *result; - int r; assert(dnskey); assert(ds); @@ -663,48 +1062,41 @@ int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds) { return -EINVAL; if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) return -EKEYREJECTED; + if (!mask_revoke && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE)) + return -EKEYREJECTED; if (dnskey->dnskey.protocol != 3) return -EKEYREJECTED; if (dnskey->dnskey.algorithm != ds->ds.algorithm) return 0; - if (dnssec_keytag(dnskey) != ds->ds.key_tag) + if (dnssec_keytag(dnskey, mask_revoke) != ds->ds.key_tag) return 0; - if (!dnssec_digest_supported(ds->ds.digest_type)) - return -EOPNOTSUPP; - - switch (ds->ds.digest_type) { + initialize_libgcrypt(); - case DNSSEC_DIGEST_SHA1: - - if (ds->ds.digest_size != 20) - return 0; + md_algorithm = digest_to_gcrypt_md(ds->ds.digest_type); + if (md_algorithm < 0) + return md_algorithm; - gcry_md_open(&md, GCRY_MD_SHA1, 0); - break; + hash_size = gcry_md_get_algo_dlen(md_algorithm); + assert(hash_size > 0); - case DNSSEC_DIGEST_SHA256: - - if (ds->ds.digest_size != 32) - return 0; - - gcry_md_open(&md, GCRY_MD_SHA256, 0); - break; + if (ds->ds.digest_size != hash_size) + return 0; - default: - assert_not_reached("Unknown digest"); - } + r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name)); + if (r < 0) + return r; + gcry_md_open(&md, md_algorithm, 0); if (!md) return -EIO; - r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name)); - if (r < 0) - goto finish; - gcry_md_write(md, owner_name, r); - md_add_uint16(md, dnskey->dnskey.flags); + if (mask_revoke) + md_add_uint16(md, dnskey->dnskey.flags & ~DNSKEY_FLAG_REVOKE); + else + md_add_uint16(md, dnskey->dnskey.flags); md_add_uint8(md, dnskey->dnskey.protocol); md_add_uint8(md, dnskey->dnskey.algorithm); gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size); @@ -722,8 +1114,9 @@ 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; assert(dnskey); @@ -731,12 +1124,25 @@ int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ if (dnskey->key->type != DNS_TYPE_DNSKEY) return 0; - DNS_ANSWER_FOREACH(ds, validated_ds) { + DNS_ANSWER_FOREACH_FLAGS(ds, flags, validated_ds) { + + if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) + continue; if (ds->key->type != DNS_TYPE_DS) continue; + if (ds->key->class != dnskey->key->class) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(ds->key)); + if (r < 0) + return r; + if (r == 0) + continue; - r = dnssec_verify_dnskey(dnskey, ds); + r = dnssec_verify_dnskey_by_ds(dnskey, ds, false); + if (IN_SET(r, -EKEYREJECTED, -EOPNOTSUPP)) + return 0; /* The DNSKEY is revoked or otherwise invalid, or we don't support the digest algorithm */ if (r < 0) return r; if (r > 0) @@ -746,20 +1152,1062 @@ int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ return 0; } -static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = { - [DNSSEC_NO] = "no", - [DNSSEC_TRUST] = "trust", - [DNSSEC_YES] = "yes", -}; -DEFINE_STRING_TABLE_LOOKUP(dnssec_mode, DnssecMode); +static int nsec3_hash_to_gcrypt_md(uint8_t algorithm) { + + /* Translates a DNSSEC NSEC3 hash algorithm into a gcrypt digest identifier */ + + switch (algorithm) { + + case NSEC3_ALGORITHM_SHA1: + return GCRY_MD_SHA1; + + default: + return -EOPNOTSUPP; + } +} + +int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { + uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX]; + gcry_md_hd_t md = NULL; + size_t hash_size; + int algorithm; + void *result; + unsigned k; + int r; + + assert(nsec3); + assert(name); + assert(ret); + + if (nsec3->key->type != DNS_TYPE_NSEC3) + return -EINVAL; + + if (nsec3->nsec3.iterations > NSEC3_ITERATIONS_MAX) { + log_debug("Ignoring NSEC3 RR %s with excessive number of iterations.", dns_resource_record_to_string(nsec3)); + return -EOPNOTSUPP; + } + + algorithm = nsec3_hash_to_gcrypt_md(nsec3->nsec3.algorithm); + if (algorithm < 0) + return algorithm; + + initialize_libgcrypt(); + + hash_size = gcry_md_get_algo_dlen(algorithm); + assert(hash_size > 0); + + if (nsec3->nsec3.next_hashed_name_size != hash_size) + return -EINVAL; + + r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true); + if (r < 0) + return r; + + gcry_md_open(&md, algorithm, 0); + if (!md) + return -EIO; + + gcry_md_write(md, wire_format, r); + gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); + + result = gcry_md_read(md, 0); + if (!result) { + r = -EIO; + goto finish; + } + + for (k = 0; k < nsec3->nsec3.iterations; k++) { + uint8_t tmp[hash_size]; + memcpy(tmp, result, hash_size); + + gcry_md_reset(md); + gcry_md_write(md, tmp, hash_size); + gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); + + result = gcry_md_read(md, 0); + if (!result) { + r = -EIO; + goto finish; + } + } + + memcpy(ret, result, hash_size); + r = (int) hash_size; + +finish: + gcry_md_close(md); + return r; +} + +static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) { + const char *a, *b; + int r; + + assert(rr); + + if (rr->key->type != DNS_TYPE_NSEC3) + return 0; + + /* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */ + if (!IN_SET(rr->nsec3.flags, 0, 1)) + return 0; + + /* Ignore NSEC3 RRs whose algorithm we don't know */ + if (nsec3_hash_to_gcrypt_md(rr->nsec3.algorithm) < 0) + return 0; + /* Ignore NSEC3 RRs with an excessive number of required iterations */ + if (rr->nsec3.iterations > NSEC3_ITERATIONS_MAX) + return 0; + + /* Ignore NSEC3 RRs generated from wildcards. If these NSEC3 RRs weren't correctly signed we can't make this + * check (since rr->n_skip_labels_source is -1), but that's OK, as we won't trust them anyway in that case. */ + if (rr->n_skip_labels_source != 0 && rr->n_skip_labels_source != (unsigned) -1) + return 0; + /* Ignore NSEC3 RRs that are located anywhere else than one label below the zone */ + if (rr->n_skip_labels_signer != 1 && rr->n_skip_labels_signer != (unsigned) -1) + return 0; + + if (!nsec3) + return 1; + + /* If a second NSEC3 RR is specified, also check if they are from the same zone. */ + + if (nsec3 == rr) /* Shortcut */ + return 1; + + if (rr->key->class != nsec3->key->class) + return 0; + if (rr->nsec3.algorithm != nsec3->nsec3.algorithm) + return 0; + if (rr->nsec3.iterations != nsec3->nsec3.iterations) + return 0; + if (rr->nsec3.salt_size != nsec3->nsec3.salt_size) + return 0; + if (memcmp(rr->nsec3.salt, nsec3->nsec3.salt, rr->nsec3.salt_size) != 0) + return 0; + + a = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_parent(&a); /* strip off hash */ + if (r < 0) + return r; + if (r == 0) + return 0; + + b = DNS_RESOURCE_KEY_NAME(nsec3->key); + r = dns_name_parent(&b); /* strip off hash */ + if (r < 0) + return r; + if (r == 0) + return 0; + + /* Make sure both have the same parent */ + return dns_name_equal(a, b); +} + +static int nsec3_hashed_domain_format(const uint8_t *hashed, size_t hashed_size, const char *zone, char **ret) { + _cleanup_free_ char *l = NULL; + char *j; + + assert(hashed); + assert(hashed_size > 0); + assert(zone); + assert(ret); + + l = base32hexmem(hashed, hashed_size, false); + if (!l) + return -ENOMEM; + + j = strjoin(l, ".", zone, NULL); + if (!j) + return -ENOMEM; + + *ret = j; + return (int) hashed_size; +} + +static int nsec3_hashed_domain_make(DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) { + uint8_t hashed[DNSSEC_HASH_SIZE_MAX]; + int hashed_size; + + assert(nsec3); + assert(domain); + assert(zone); + assert(ret); + + hashed_size = dnssec_nsec3_hash(nsec3, domain, hashed); + if (hashed_size < 0) + return hashed_size; + + return nsec3_hashed_domain_format(hashed, (size_t) hashed_size, zone, ret); +} + +/* See RFC 5155, Section 8 + * First try to find a NSEC3 record that matches our query precisely, if that fails, find the closest + * enclosure. Secondly, find a proof that there is no closer enclosure and either a proof that there + * is no wildcard domain as a direct descendant of the closest enclosure, or find an NSEC3 record that + * matches the wildcard domain. + * + * Based on this we can prove either the existence of the record in @key, or NXDOMAIN or NODATA, or + * that there is no proof either way. The latter is the case if a the proof of non-existence of a given + * name uses an NSEC3 record with the opt-out bit set. Lastly, if we are given insufficient NSEC3 records + * to conclude anything we indicate this by returning NO_RR. */ +static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { + _cleanup_free_ char *next_closer_domain = NULL, *wildcard_domain = NULL; + const char *zone, *p, *pp = NULL, *wildcard; + DnsResourceRecord *rr, *enclosure_rr, *zone_rr, *wildcard_rr = NULL; + DnsAnswerFlags flags; + int hashed_size, r; + bool a, no_closer = false, no_wildcard = false, optout = false; + + assert(key); + assert(result); + + /* First step, find the zone name and the NSEC3 parameters of the zone. + * it is sufficient to look for the longest common suffix we find with + * any NSEC3 RR in the response. Any NSEC3 record will do as all NSEC3 + * records from a given zone in a response must use the same + * parameters. */ + zone = DNS_RESOURCE_KEY_NAME(key); + for (;;) { + DNS_ANSWER_FOREACH_FLAGS(zone_rr, flags, answer) { + r = nsec3_is_good(zone_rr, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(zone_rr->key), 1, zone); + if (r < 0) + return r; + if (r > 0) + goto found_zone; + } + + /* Strip one label from the front */ + r = dns_name_parent(&zone); + if (r < 0) + return r; + if (r == 0) + break; + } + + *result = DNSSEC_NSEC_NO_RR; + return 0; + +found_zone: + /* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */ + p = DNS_RESOURCE_KEY_NAME(key); + for (;;) { + _cleanup_free_ char *hashed_domain = NULL; + + hashed_size = nsec3_hashed_domain_make(zone_rr, p, zone, &hashed_domain); + if (hashed_size == -EOPNOTSUPP) { + *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM; + return 0; + } + if (hashed_size < 0) + return hashed_size; + + DNS_ANSWER_FOREACH_FLAGS(enclosure_rr, flags, answer) { + + r = nsec3_is_good(enclosure_rr, zone_rr); + if (r < 0) + return r; + if (r == 0) + continue; + + if (enclosure_rr->nsec3.next_hashed_name_size != (size_t) hashed_size) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(enclosure_rr->key), hashed_domain); + if (r < 0) + return r; + if (r > 0) { + a = flags & DNS_ANSWER_AUTHENTICATED; + goto found_closest_encloser; + } + } + + /* We didn't find the closest encloser with this name, + * but let's remember this domain name, it might be + * the next closer name */ + + pp = p; + + /* Strip one label from the front */ + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + break; + } + + *result = DNSSEC_NSEC_NO_RR; + return 0; + +found_closest_encloser: + /* We found a closest encloser in 'p'; next closer is 'pp' */ + + if (!pp) { + /* We have an exact match! If we area looking for a DS RR, then we must insist that we got the NSEC3 RR + * from the parent. Otherwise the one from the child. Do so, by checking whether SOA and NS are + * appropriately set. */ + + if (key->type == DNS_TYPE_DS) { + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) + return -EBADMSG; + } else { + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) && + !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) + return -EBADMSG; + } + + /* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */ + if (bitmap_isset(enclosure_rr->nsec3.types, key->type)) + *result = DNSSEC_NSEC_FOUND; + else if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_CNAME)) + *result = DNSSEC_NSEC_CNAME; + else + *result = DNSSEC_NSEC_NODATA; + + if (authenticated) + *authenticated = a; + if (ttl) + *ttl = enclosure_rr->ttl; + + return 0; + } + + /* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */ + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_DNAME)) + return -EBADMSG; + + /* Ensure that this data is from the delegated domain + * (i.e. originates from the "lower" DNS server), and isn't + * just glue records (i.e. doesn't originate from the "upper" + * DNS server). */ + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) && + !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) + return -EBADMSG; + + /* Prove that there is no next closer and whether or not there is a wildcard domain. */ + + wildcard = strjoina("*.", p); + r = nsec3_hashed_domain_make(enclosure_rr, wildcard, zone, &wildcard_domain); + if (r < 0) + return r; + if (r != hashed_size) + return -EBADMSG; + + r = nsec3_hashed_domain_make(enclosure_rr, pp, zone, &next_closer_domain); + if (r < 0) + return r; + if (r != hashed_size) + return -EBADMSG; + + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + _cleanup_free_ char *next_hashed_domain = NULL; + + r = nsec3_is_good(rr, zone_rr); + if (r < 0) + return r; + if (r == 0) + continue; + + r = nsec3_hashed_domain_format(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, zone, &next_hashed_domain); + if (r < 0) + return r; + + r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), next_closer_domain, next_hashed_domain); + if (r < 0) + return r; + if (r > 0) { + if (rr->nsec3.flags & 1) + optout = true; + + a = a && (flags & DNS_ANSWER_AUTHENTICATED); + + no_closer = true; + } + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), wildcard_domain); + if (r < 0) + return r; + if (r > 0) { + a = a && (flags & DNS_ANSWER_AUTHENTICATED); + + wildcard_rr = rr; + } + + r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), wildcard_domain, next_hashed_domain); + if (r < 0) + return r; + if (r > 0) { + if (rr->nsec3.flags & 1) + /* This only makes sense if we have a wildcard delegation, which is + * very unlikely, see RFC 4592, Section 4.2, but we cannot rely on + * this not happening, so hence cannot simply conclude NXDOMAIN as + * we would wish */ + optout = true; + + a = a && (flags & DNS_ANSWER_AUTHENTICATED); + + no_wildcard = true; + } + } + + if (wildcard_rr && no_wildcard) + return -EBADMSG; + + if (!no_closer) { + *result = DNSSEC_NSEC_NO_RR; + return 0; + } + + if (wildcard_rr) { + /* A wildcard exists that matches our query. */ + if (optout) + /* This is not specified in any RFC to the best of my knowledge, but + * if the next closer enclosure is covered by an opt-out NSEC3 RR + * it means that we cannot prove that the source of synthesis is + * correct, as there may be a closer match. */ + *result = DNSSEC_NSEC_OPTOUT; + else if (bitmap_isset(wildcard_rr->nsec3.types, key->type)) + *result = DNSSEC_NSEC_FOUND; + else if (bitmap_isset(wildcard_rr->nsec3.types, DNS_TYPE_CNAME)) + *result = DNSSEC_NSEC_CNAME; + else + *result = DNSSEC_NSEC_NODATA; + } else { + if (optout) + /* The RFC only specifies that we have to care for optout for NODATA for + * DS records. However, children of an insecure opt-out delegation should + * also be considered opt-out, rather than verified NXDOMAIN. + * Note that we do not require a proof of wildcard non-existence if the + * next closer domain is covered by an opt-out, as that would not provide + * any additional information. */ + *result = DNSSEC_NSEC_OPTOUT; + else if (no_wildcard) + *result = DNSSEC_NSEC_NXDOMAIN; + else { + *result = DNSSEC_NSEC_NO_RR; + + return 0; + } + } + + if (authenticated) + *authenticated = a; + + if (ttl) + *ttl = enclosure_rr->ttl; + + 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) { + bool have_nsec3 = false, covering_rr_authenticated = false, wildcard_rr_authenticated = false; + DnsResourceRecord *rr, *covering_rr = NULL, *wildcard_rr = NULL; + DnsAnswerFlags flags; + const char *name; + int r; + + assert(key); + assert(result); + + /* 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; + + have_nsec3 = have_nsec3 || (rr->key->type == DNS_TYPE_NSEC3); + + if (rr->key->type != DNS_TYPE_NSEC) + continue; + + /* The following checks only make sense for NSEC RRs that are not expanded from a wildcard */ + r = dns_resource_record_is_synthetic(rr); + if (r < 0) + return r; + if (r > 0) + continue; + + /* 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 (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; + } + + 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; + } + + /* Check if the name we are looking for is an empty non-terminal within the owner or next name + * of the NSEC RR. */ + r = dnssec_nsec_in_path(rr, name); + if (r < 0) + return r; + if (r > 0) { + *result = DNSSEC_NSEC_NODATA; + + if (authenticated) + *authenticated = flags & DNS_ANSWER_AUTHENTICATED; + if (ttl) + *ttl = rr->ttl; + + return 0; + } + + /* The following two "covering" checks, are not useful if the NSEC is from the parent */ + r = dnssec_nsec_from_parent_zone(rr, name); + if (r < 0) + return r; + if (r > 0) + continue; + + /* Check if this NSEC RR proves the absence of an explicit RR under this name */ + r = dnssec_nsec_covers(rr, name); + if (r < 0) + return r; + if (r > 0 && (!covering_rr || !covering_rr_authenticated)) { + covering_rr = rr; + covering_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; + } + + /* Check if this NSEC RR proves the absence of a wildcard RR under this name */ + r = dnssec_nsec_covers_wildcard(rr, name); + if (r < 0) + return r; + if (r > 0 && (!wildcard_rr || !wildcard_rr_authenticated)) { + wildcard_rr = rr; + wildcard_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; + } + } + + if (covering_rr && wildcard_rr) { + /* If we could prove that neither the name itself, nor the wildcard at the closest encloser exists, we + * proved the NXDOMAIN case. */ + *result = DNSSEC_NSEC_NXDOMAIN; + + if (authenticated) + *authenticated = covering_rr_authenticated && wildcard_rr_authenticated; + if (ttl) + *ttl = MIN(covering_rr->ttl, wildcard_rr->ttl); + + return 0; + } + + /* OK, this was not sufficient. Let's see if NSEC3 can help. */ + if (have_nsec3) + return dnssec_test_nsec3(answer, key, result, authenticated, ttl); + + /* No approproate NSEC RR found, report this. */ + *result = DNSSEC_NSEC_NO_RR; + return 0; +} + +static int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name, const char *zone, bool *authenticated) { + DnsResourceRecord *rr; + DnsAnswerFlags flags; + int r; + + assert(name); + assert(zone); + + /* Checks whether there's an NSEC/NSEC3 that proves that the specified 'name' is non-existing in the specified + * 'zone'. The 'zone' must be a suffix of the 'name'. */ + + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + bool found = false; + + if (rr->key->type != type && type != DNS_TYPE_ANY) + continue; + + switch (rr->key->type) { + + case DNS_TYPE_NSEC: + + /* We only care for NSEC RRs from the indicated zone */ + r = dns_resource_record_is_signer(rr, zone); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), name, rr->nsec.next_domain_name); + if (r < 0) + return r; + + found = r > 0; + break; + + case DNS_TYPE_NSEC3: { + _cleanup_free_ char *hashed_domain = NULL, *next_hashed_domain = NULL; + + /* We only care for NSEC3 RRs from the indicated zone */ + r = dns_resource_record_is_signer(rr, zone); + if (r < 0) + return r; + if (r == 0) + continue; + + r = nsec3_is_good(rr, NULL); + if (r < 0) + return r; + if (r == 0) + break; + + /* Format the domain we are testing with the NSEC3 RR's hash function */ + r = nsec3_hashed_domain_make( + rr, + name, + zone, + &hashed_domain); + if (r < 0) + return r; + if ((size_t) r != rr->nsec3.next_hashed_name_size) + break; + + /* Format the NSEC3's next hashed name as proper domain name */ + r = nsec3_hashed_domain_format( + rr->nsec3.next_hashed_name, + rr->nsec3.next_hashed_name_size, + zone, + &next_hashed_domain); + if (r < 0) + return r; + + r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), hashed_domain, next_hashed_domain); + if (r < 0) + return r; + + found = r > 0; + break; + } + + default: + continue; + } + + if (found) { + if (authenticated) + *authenticated = flags & DNS_ANSWER_AUTHENTICATED; + return 1; + } + } + + return 0; +} + +static int dnssec_test_positive_wildcard_nsec3( + DnsAnswer *answer, + const char *name, + const char *source, + const char *zone, + bool *authenticated) { + + const char *next_closer = NULL; + int r; + + /* Run a positive NSEC3 wildcard proof. Specifically: + * + * A proof that the the "next closer" of the generating wildcard does not exist. + * + * Note a key difference between the NSEC3 and NSEC versions of the proof. NSEC RRs don't have to exist for + * empty non-transients. NSEC3 RRs however have to. This means it's sufficient to check if the next closer name + * exists for the NSEC3 RR and we are done. + * + * To prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f all we have to check is that + * c.d.e.f does not exist. */ + + for (;;) { + next_closer = name; + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r == 0) + return 0; + + r = dns_name_equal(name, source); + if (r < 0) + return r; + if (r > 0) + break; + } + + return dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC3, next_closer, zone, authenticated); +} + +static int dnssec_test_positive_wildcard_nsec( + DnsAnswer *answer, + const char *name, + const char *source, + const char *zone, + bool *_authenticated) { + + bool authenticated = true; + int r; + + /* Run a positive NSEC wildcard proof. Specifically: + * + * A proof that there's neither a wildcard name nor a non-wildcard name that is a suffix of the name "name" and + * a prefix of the synthesizing source "source" in the zone "zone". + * + * See RFC 5155, Section 8.8 and RFC 4035, Section 5.3.4 + * + * Note that if we want to prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f, then we + * have to prove that none of the following exist: + * + * 1) a.b.c.d.e.f + * 2) *.b.c.d.e.f + * 3) b.c.d.e.f + * 4) *.c.d.e.f + * 5) c.d.e.f + * + */ + + for (;;) { + _cleanup_free_ char *wc = NULL; + bool a = false; + + /* Check if there's an NSEC or NSEC3 RR that proves that the mame we determined is really non-existing, + * i.e between the owner name and the next name of an NSEC RR. */ + r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, name, zone, &a); + if (r <= 0) + return r; + + authenticated = authenticated && a; + + /* Strip one label off */ + r = dns_name_parent(&name); + if (r <= 0) + return r; + + /* Did we reach the source of synthesis? */ + r = dns_name_equal(name, source); + if (r < 0) + return r; + if (r > 0) { + /* Successful exit */ + *_authenticated = authenticated; + return 1; + } + + /* Safety check, that the source of synthesis is still our suffix */ + r = dns_name_endswith(name, source); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + + /* Replace the label we stripped off with an asterisk */ + wc = strappend("*.", name); + if (!wc) + return -ENOMEM; + + /* And check if the proof holds for the asterisk name, too */ + r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, wc, zone, &a); + if (r <= 0) + return r; + + authenticated = authenticated && a; + /* In the next iteration we'll check the non-asterisk-prefixed version */ + } +} + +int dnssec_test_positive_wildcard( + DnsAnswer *answer, + const char *name, + const char *source, + const char *zone, + bool *authenticated) { + + int r; + + assert(name); + assert(source); + assert(zone); + assert(authenticated); + + r = dns_answer_contains_zone_nsec3(answer, zone); + if (r < 0) + return r; + if (r > 0) + return dnssec_test_positive_wildcard_nsec3(answer, name, source, zone, authenticated); + else + return dnssec_test_positive_wildcard_nsec(answer, name, source, zone, authenticated); +} + +#else + +int dnssec_verify_rrset( + DnsAnswer *a, + const DnsResourceKey *key, + DnsResourceRecord *rrsig, + DnsResourceRecord *dnskey, + usec_t realtime, + DnssecResult *result) { + + return -EOPNOTSUPP; +} + +int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) { + + return -EOPNOTSUPP; +} + +int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) { + + return -EOPNOTSUPP; +} + +int dnssec_verify_rrset_search( + DnsAnswer *a, + const DnsResourceKey *key, + DnsAnswer *validated_dnskeys, + usec_t realtime, + DnssecResult *result, + DnsResourceRecord **ret_rrsig) { + + return -EOPNOTSUPP; +} + +int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) { + + return -EOPNOTSUPP; +} + +int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) { + + return -EOPNOTSUPP; +} + +int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { + + return -EOPNOTSUPP; +} + +int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { + + return -EOPNOTSUPP; +} + +int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { + + return -EOPNOTSUPP; +} + +int dnssec_test_positive_wildcard( + DnsAnswer *answer, + const char *name, + const char *source, + const char *zone, + bool *authenticated) { + + return -EOPNOTSUPP; +} + +#endif static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = { [DNSSEC_VALIDATED] = "validated", + [DNSSEC_VALIDATED_WILDCARD] = "validated-wildcard", [DNSSEC_INVALID] = "invalid", - [DNSSEC_UNSIGNED] = "unsigned", + [DNSSEC_SIGNATURE_EXPIRED] = "signature-expired", + [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm", [DNSSEC_NO_SIGNATURE] = "no-signature", [DNSSEC_MISSING_KEY] = "missing-key", - [DNSSEC_SIGNATURE_EXPIRED] = "signature-expired", + [DNSSEC_UNSIGNED] = "unsigned", [DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary", + [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch", + [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server", }; DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult); + +static const char* const dnssec_verdict_table[_DNSSEC_VERDICT_MAX] = { + [DNSSEC_SECURE] = "secure", + [DNSSEC_INSECURE] = "insecure", + [DNSSEC_BOGUS] = "bogus", + [DNSSEC_INDETERMINATE] = "indeterminate", +}; +DEFINE_STRING_TABLE_LOOKUP(dnssec_verdict, DnssecVerdict); diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h index f0825ba23f..4542f0aa89 100644 --- a/src/resolve/resolved-dns-dnssec.h +++ b/src/resolve/resolved-dns-dnssec.h @@ -21,56 +21,84 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -typedef enum DnssecMode DnssecMode; typedef enum DnssecResult DnssecResult; +typedef enum DnssecVerdict DnssecVerdict; #include "dns-domain.h" #include "resolved-dns-answer.h" #include "resolved-dns-rr.h" -enum DnssecMode { - /* No DNSSEC validation is done */ - DNSSEC_NO, - - /* Trust the AD bit sent by the server. UNSAFE! */ - DNSSEC_TRUST, - - /* Validate locally, if the server knows DO, but if not, don't. Don't trust the AD bit */ - DNSSEC_YES, - - _DNSSEC_MODE_MAX, - _DNSSEC_MODE_INVALID = -1 -}; - enum DnssecResult { + /* These five are returned by dnssec_verify_rrset() */ DNSSEC_VALIDATED, + DNSSEC_VALIDATED_WILDCARD, /* Validated via a wildcard RRSIG, further NSEC/NSEC3 checks necessary */ DNSSEC_INVALID, - DNSSEC_UNSIGNED, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_UNSUPPORTED_ALGORITHM, + + /* These two are added by dnssec_verify_rrset_search() */ DNSSEC_NO_SIGNATURE, DNSSEC_MISSING_KEY, - DNSSEC_SIGNATURE_EXPIRED, + + /* These two are added by the DnsTransaction logic */ + DNSSEC_UNSIGNED, DNSSEC_FAILED_AUXILIARY, + DNSSEC_NSEC_MISMATCH, + DNSSEC_INCOMPATIBLE_SERVER, + _DNSSEC_RESULT_MAX, _DNSSEC_RESULT_INVALID = -1 }; +enum DnssecVerdict { + DNSSEC_SECURE, + DNSSEC_INSECURE, + DNSSEC_BOGUS, + DNSSEC_INDETERMINATE, + + _DNSSEC_VERDICT_MAX, + _DNSSEC_VERDICT_INVALID = -1 +}; + #define DNSSEC_CANONICAL_HOSTNAME_MAX (DNS_HOSTNAME_MAX + 2) -int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey); -int dnssec_key_match_rrsig(DnsResourceKey *key, DnsResourceRecord *rrsig); +/* The longest digest we'll ever generate, of all digest algorithms we support */ +#define DNSSEC_HASH_SIZE_MAX (MAX(20, 32)) + +int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok); +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_rrset(DnsAnswer *answer, DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result); -int dnssec_verify_rrset_search(DnsAnswer *answer, DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, DnssecResult *result); +int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke); +int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds); -int dnssec_verify_dnskey(DnsResourceRecord *dnskey, DnsResourceRecord *ds); -int dnssec_verify_dnskey_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds); +int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key); -uint16_t dnssec_keytag(DnsResourceRecord *dnskey); +uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke); int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max); -const char* dnssec_mode_to_string(DnssecMode m) _const_; -DnssecMode dnssec_mode_from_string(const char *s) _pure_; +int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret); + +typedef enum DnssecNsecResult { + DNSSEC_NSEC_NO_RR, /* No suitable NSEC/NSEC3 RR found */ + DNSSEC_NSEC_CNAME, /* Didn't find what was asked for, but did find CNAME */ + DNSSEC_NSEC_UNSUPPORTED_ALGORITHM, + DNSSEC_NSEC_NXDOMAIN, + DNSSEC_NSEC_NODATA, + DNSSEC_NSEC_FOUND, + DNSSEC_NSEC_OPTOUT, +} DnssecNsecResult; + +int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl); + + +int dnssec_test_positive_wildcard(DnsAnswer *a, const char *name, const char *source, const char *zone, bool *authenticated); const char* dnssec_result_to_string(DnssecResult m) _const_; DnssecResult dnssec_result_from_string(const char *s) _pure_; + +const char* dnssec_verdict_to_string(DnssecVerdict m) _const_; +DnssecVerdict dnssec_verdict_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index e90500ce70..5cbe20832f 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -58,6 +58,7 @@ int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { p->size = p->rindex = DNS_PACKET_HEADER_SIZE; p->allocated = a; p->protocol = protocol; + p->opt_start = p->opt_size = (size_t) -1; p->n_ref = 1; *ret = p; @@ -80,7 +81,7 @@ void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool trun h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, 0 /* opcode */, 0 /* c */, - 0/* tc */, + 0 /* tc */, 0 /* t */, 0 /* ra */, 0 /* ad */, @@ -171,8 +172,7 @@ DnsPacket *dns_packet_unref(DnsPacket *p) { assert(p->n_ref > 0); - if (p->more) - dns_packet_unref(p->more); + dns_packet_unref(p->more); if (p->n_ref == 1) dns_packet_free(p); @@ -439,10 +439,15 @@ int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_ return 0; } -int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start) { +int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, bool canonical_candidate, size_t *start) { uint8_t *w; int r; + /* Append a label to a packet. Optionally, does this in DNSSEC + * canonical form, if this label is marked as a candidate for + * it, and the canonical form logic is enabled for the + * packet */ + assert(p); assert(d); @@ -455,18 +460,14 @@ int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, size_t *start *(w++) = (uint8_t) l; - if (p->canonical_form) { + if (p->canonical_form && canonical_candidate) { size_t i; /* Generate in canonical form, as defined by DNSSEC * RFC 4034, Section 6.2, i.e. all lower-case. */ - for (i = 0; i < l; i++) { - if (d[i] >= 'A' && d[i] <= 'Z') - w[i] = (uint8_t) (d[i] - 'A' + 'a'); - else - w[i] = (uint8_t) d[i]; - } + for (i = 0; i < l; i++) + w[i] = (uint8_t) ascii_tolower(d[i]); } else /* Otherwise, just copy the string unaltered. This is * essential for DNS-SD, where the casing of labels @@ -480,6 +481,7 @@ int dns_packet_append_name( DnsPacket *p, const char *name, bool allow_compression, + bool canonical_candidate, size_t *start) { size_t saved_size; @@ -493,11 +495,10 @@ int dns_packet_append_name( saved_size = p->size; - while (*name) { - _cleanup_free_ char *s = NULL; + while (!dns_name_is_root(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)); @@ -513,32 +514,23 @@ int dns_packet_append_name( } } - s = strdup(name); - if (!s) { - r = -ENOMEM; - goto fail; - } - r = dns_label_unescape(&name, label, sizeof(label)); 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, &n); + r = dns_packet_append_label(p, label, r, canonical_candidate, &n); if (r < 0) goto fail; if (allow_compression) { + _cleanup_free_ char *s = NULL; + + s = strdup(z); + if (!s) { + r = -ENOMEM; + goto fail; + } + r = hashmap_ensure_allocated(&p->names, &dns_name_hash_ops); if (r < 0) goto fail; @@ -575,7 +567,7 @@ int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) saved_size = p->size; - r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, NULL); + r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, true, NULL); if (r < 0) goto fail; @@ -597,7 +589,7 @@ fail: return r; } -static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, uint8_t *types, size_t *start) { +static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, const uint8_t *types, size_t *start) { size_t saved_size; int r; @@ -638,7 +630,6 @@ static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) { int r; assert(p); - assert(types); saved_size = p->size; @@ -654,15 +645,16 @@ static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) { } window = n >> 8; - entry = n & 255; bitmaps[entry / 8] |= 1 << (7 - (entry % 8)); } - r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); - if (r < 0) - goto fail; + if (bitmaps[entry / 8] != 0) { + r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); + if (r < 0) + goto fail; + } if (start) *start = saved_size; @@ -674,7 +666,7 @@ fail: } /* Append the OPT pseudo-RR described in RFC6891 */ -int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start) { +int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start) { size_t saved_size; int r; @@ -682,6 +674,11 @@ int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, /* we must never advertise supported packet size smaller than the legacy max */ assert(max_udp_size >= DNS_PACKET_UNICAST_SIZE_MAX); + if (p->opt_start != (size_t) -1) + return -EBUSY; + + assert(p->opt_size == (size_t) -1); + saved_size = p->size; /* empty name */ @@ -710,10 +707,48 @@ int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, goto fail; /* RDLENGTH */ - r = dns_packet_append_uint16(p, 0, NULL); + + if (edns0_do) { + /* If DO is on, also append RFC6975 Algorithm data */ + + static const uint8_t rfc6975[] = { + + 0, 5, /* OPTION_CODE: DAU */ + 0, 6, /* LIST_LENGTH */ + DNSSEC_ALGORITHM_RSASHA1, + DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, + DNSSEC_ALGORITHM_RSASHA256, + DNSSEC_ALGORITHM_RSASHA512, + DNSSEC_ALGORITHM_ECDSAP256SHA256, + DNSSEC_ALGORITHM_ECDSAP384SHA384, + + 0, 6, /* OPTION_CODE: DHU */ + 0, 3, /* LIST_LENGTH */ + DNSSEC_DIGEST_SHA1, + DNSSEC_DIGEST_SHA256, + DNSSEC_DIGEST_SHA384, + + 0, 7, /* OPTION_CODE: N3U */ + 0, 1, /* LIST_LENGTH */ + NSEC3_ALGORITHM_SHA1, + }; + + r = dns_packet_append_uint16(p, sizeof(rfc6975), NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rfc6975, sizeof(rfc6975), NULL); + } else + r = dns_packet_append_uint16(p, 0, NULL); + if (r < 0) goto fail; + DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) + 1); + + p->opt_start = saved_size; + p->opt_size = p->size - saved_size; + if (start) *start = saved_size; @@ -724,6 +759,27 @@ fail: return r; } +int dns_packet_truncate_opt(DnsPacket *p) { + assert(p); + + if (p->opt_start == (size_t) -1) { + assert(p->opt_size == (size_t) -1); + return 0; + } + + assert(p->opt_size != (size_t) -1); + assert(DNS_PACKET_ARCOUNT(p) > 0); + + if (p->opt_start + p->opt_size != p->size) + return -EBUSY; + + dns_packet_truncate(p, p->opt_start); + DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) - 1); + p->opt_start = p->opt_size = (size_t) -1; + + return 1; +} + int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start) { size_t saved_size, rdlength_offset, end, rdlength, rds; int r; @@ -763,14 +819,14 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->srv.name, true, NULL); + r = dns_packet_append_name(p, rr->srv.name, true, false, NULL); break; case DNS_TYPE_PTR: case DNS_TYPE_NS: case DNS_TYPE_CNAME: case DNS_TYPE_DNAME: - r = dns_packet_append_name(p, rr->ptr.name, true, NULL); + r = dns_packet_append_name(p, rr->ptr.name, true, false, NULL); break; case DNS_TYPE_HINFO: @@ -813,11 +869,11 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star break; case DNS_TYPE_SOA: - r = dns_packet_append_name(p, rr->soa.mname, true, NULL); + r = dns_packet_append_name(p, rr->soa.mname, true, false, NULL); if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->soa.rname, true, NULL); + r = dns_packet_append_name(p, rr->soa.rname, true, false, NULL); if (r < 0) goto fail; @@ -845,7 +901,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->mx.exchange, true, NULL); + r = dns_packet_append_name(p, rr->mx.exchange, true, false, NULL); break; case DNS_TYPE_LOC: @@ -949,7 +1005,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star if (r < 0) goto fail; - r = dns_packet_append_name(p, rr->rrsig.signer, false, NULL); + r = dns_packet_append_name(p, rr->rrsig.signer, false, true, NULL); if (r < 0) goto fail; @@ -957,7 +1013,7 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star break; case DNS_TYPE_NSEC: - r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, NULL); + r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, false, NULL); if (r < 0) goto fail; @@ -1002,11 +1058,28 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *star break; + case DNS_TYPE_TLSA: + r = dns_packet_append_uint8(p, rr->tlsa.cert_usage, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->tlsa.selector, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->tlsa.matching_type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->tlsa.data, rr->tlsa.data_size, NULL); + break; + case DNS_TYPE_OPT: + case DNS_TYPE_OPENPGPKEY: case _DNS_TYPE_INVALID: /* unparseable */ default: - r = dns_packet_append_blob(p, rr->generic.data, rr->generic.size, NULL); + r = dns_packet_append_blob(p, rr->generic.data, rr->generic.data_size, NULL); break; } if (r < 0) @@ -1015,7 +1088,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; } @@ -1039,7 +1112,6 @@ fail: return r; } - int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) { assert(p); @@ -1449,9 +1521,9 @@ fail: return r; } -int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) { +int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start) { _cleanup_free_ char *name = NULL; - bool cache_flush = true; + bool cache_flush = false; uint16_t class, type; DnsResourceKey *key; size_t saved_rindex; @@ -1477,10 +1549,10 @@ int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) { if (p->protocol == DNS_PROTOCOL_MDNS) { /* See RFC6762, Section 10.2 */ - if (class & MDNS_RR_CACHE_FLUSH) + if (type != DNS_TYPE_OPT && (class & MDNS_RR_CACHE_FLUSH)) { class &= ~MDNS_RR_CACHE_FLUSH; - else - cache_flush = false; + cache_flush = true; + } } key = dns_resource_key_new_consume(class, type, name); @@ -1489,11 +1561,11 @@ int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start) { goto fail; } - key->cache_flush = cache_flush; - name = NULL; *ret = key; + if (ret_cache_flush) + *ret_cache_flush = cache_flush; if (start) *start = saved_rindex; @@ -1509,11 +1581,12 @@ static bool loc_size_ok(uint8_t size) { return m <= 9 && e <= 9 && (m > 0 || e == 0); } -int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { +int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; size_t saved_rindex, offset; uint16_t rdlength; + bool cache_flush; int r; assert(p); @@ -1521,14 +1594,12 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { saved_rindex = p->rindex; - r = dns_packet_read_key(p, &key, NULL); + r = dns_packet_read_key(p, &key, &cache_flush, NULL); if (r < 0) goto fail; - if (key->class == DNS_CLASS_ANY || - key->type == DNS_TYPE_ANY || - key->type == DNS_TYPE_AXFR || - key->type == DNS_TYPE_IXFR) { + if (!dns_class_is_valid_rr(key->class)|| + !dns_type_is_valid_rr(key->type)) { r = -EBADMSG; goto fail; } @@ -1543,6 +1614,11 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { if (r < 0) goto fail; + /* RFC 2181, Section 8, suggests to + * treat a TTL with the MSB set as a zero TTL. */ + if (rr->ttl & UINT32_C(0x80000000)) + rr->ttl = 0; + r = dns_packet_read_uint16(p, &rdlength, NULL); if (r < 0) goto fail; @@ -1851,7 +1927,7 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { case DNS_TYPE_NSEC: { /* - * RFC6762, section 18.14 explicly states mDNS should use name compression. + * RFC6762, section 18.14 explictly states mDNS should use name compression. * This contradicts RFC3845, section 2.1.1 */ @@ -1917,10 +1993,36 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { break; } + case DNS_TYPE_TLSA: + r = dns_packet_read_uint8(p, &rr->tlsa.cert_usage, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->tlsa.selector, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->tlsa.matching_type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_memdup(p, rdlength - 3, + &rr->tlsa.data, &rr->tlsa.data_size, + NULL); + if (rr->tlsa.data_size <= 0) { + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + r = -EBADMSG; + goto fail; + } + + break; + case DNS_TYPE_OPT: /* we only care about the header of OPT for now. */ + case DNS_TYPE_OPENPGPKEY: default: unparseable: - r = dns_packet_read_memdup(p, rdlength, &rr->generic.data, &rr->generic.size, NULL); + r = dns_packet_read_memdup(p, rdlength, &rr->generic.data, &rr->generic.data_size, NULL); if (r < 0) goto fail; break; @@ -1935,6 +2037,8 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { *ret = rr; rr = NULL; + if (ret_cache_flush) + *ret_cache_flush = cache_flush; if (start) *start = saved_rindex; @@ -1944,6 +2048,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.data_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; @@ -1967,11 +2113,22 @@ int dns_packet_extract(DnsPacket *p) { for (i = 0; i < n; i++) { _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + bool cache_flush; - r = dns_packet_read_key(p, &key, NULL); + r = dns_packet_read_key(p, &key, &cache_flush, NULL); if (r < 0) goto finish; + if (cache_flush) { + r = -EBADMSG; + goto finish; + } + + if (!dns_type_is_valid_query(key->type)) { + r = -EBADMSG; + goto finish; + } + r = dns_question_add(question, key); if (r < 0) goto finish; @@ -1980,6 +2137,9 @@ int dns_packet_extract(DnsPacket *p) { n = DNS_PACKET_RRCOUNT(p); if (n > 0) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *previous = NULL; + bool bad_opt = false; + answer = dns_answer_new(n); if (!answer) { r = -ENOMEM; @@ -1988,22 +2148,84 @@ int dns_packet_extract(DnsPacket *p) { for (i = 0; i < n; i++) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + bool cache_flush; - r = dns_packet_read_rr(p, &rr, NULL); + r = dns_packet_read_rr(p, &rr, &cache_flush, NULL); 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) { - if (p->opt) - return -EBADMSG; + 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))) { + /* 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; + } + + 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; + } + + if (!opt_is_good(rr, &has_rfc6975)) { + log_debug("Malformed OPT RR, ignoring."); + bad_opt = true; + continue; + } + + if (has_rfc6975) { + /* If the 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 routers 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 { - r = dns_answer_add(answer, rr, p->ifindex); + + /* 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) | + (p->protocol == DNS_PROTOCOL_MDNS && !cache_flush ? DNS_ANSWER_SHARED_OWNER : 0)); if (r < 0) goto finish; } + + /* Remember this RR, so that we potentically can merge it's ->key object with the next RR. Note + * that we only do this if we actually decided to keep the RR around. */ + dns_resource_record_unref(previous); + previous = dns_resource_record_ref(rr); } + + 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 5b6a71dc01..c53431576b 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -76,6 +76,7 @@ struct DnsPacket { size_t size, allocated, rindex; void *_data; /* don't access directly, use DNS_PACKET_DATA()! */ Hashmap *names; /* For name compression */ + size_t opt_start, opt_size; /* Parsed data */ DnsQuestion *question; @@ -118,7 +119,17 @@ static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) { #define DNS_PACKET_RA(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 7) & 1) #define DNS_PACKET_AD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 5) & 1) #define DNS_PACKET_CD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 4) & 1) -#define DNS_PACKET_RCODE(p) (be16toh(DNS_PACKET_HEADER(p)->flags) & 15) + +static inline uint16_t DNS_PACKET_RCODE(DnsPacket *p) { + uint16_t rcode; + + if (p->opt) + rcode = (uint16_t) (p->opt->ttl >> 24); + else + rcode = 0; + + return rcode | (be16toh(DNS_PACKET_HEADER(p)->flags) & 15); +} /* LLMNR defines some bits differently */ #define DNS_PACKET_LLMNR_C(p) DNS_PACKET_AA(p) @@ -169,13 +180,14 @@ int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start); int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start); int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start); int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start); -int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, size_t *start); -int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, size_t *start); +int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, bool canonical_candidate, size_t *start); +int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, bool canonical_candidate, size_t *start); int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, size_t *start); int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start); -int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start); +int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start); void dns_packet_truncate(DnsPacket *p, size_t sz); +int dns_packet_truncate_opt(DnsPacket *p); int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start); int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start); @@ -185,8 +197,8 @@ int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start); int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start); int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start); int dns_packet_read_name(DnsPacket *p, char **ret, bool allow_compression, size_t *start); -int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, size_t *start); -int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start); +int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start); +int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start); void dns_packet_rewind(DnsPacket *p, size_t idx); @@ -201,6 +213,7 @@ static inline bool DNS_PACKET_SHALL_CACHE(DnsPacket *p) { return in_addr_is_localhost(p->family, &p->sender) == 0; } +/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 */ enum { DNS_RCODE_SUCCESS = 0, DNS_RCODE_FORMERR = 1, @@ -234,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 a6565f2ba2..b8bdff9dfa 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -21,9 +21,13 @@ #include "alloc-util.h" #include "dns-domain.h" +#include "dns-type.h" #include "hostname-util.h" #include "local-addresses.h" #include "resolved-dns-query.h" +#include "resolved-dns-synthesize.h" +#include "resolved-etc-hosts.h" +#include "string-util.h" /* How long to wait for the query in total */ #define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC) @@ -90,17 +94,20 @@ static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) { assert(c); - if (c->search_domain && c->search_domain->linked) { + if (c->search_domain && c->search_domain->linked) next = c->search_domain->domains_next; + else + next = dns_scope_get_search_domains(c->scope); + for (;;) { if (!next) /* We hit the end of the list */ return 0; - } else { - next = dns_scope_get_search_domains(c->scope); + if (!next->route_only) + break; - if (!next) /* OK, there's nothing. */ - return 0; + /* Skip over route-only domains */ + next = next->domains_next; } dns_search_domain_unref(c->search_domain); @@ -155,6 +162,7 @@ static int dns_query_candidate_go(DnsQueryCandidate *c) { DnsTransaction *t; Iterator i; int r; + unsigned n = 0; assert(c); @@ -166,8 +174,14 @@ static int dns_query_candidate_go(DnsQueryCandidate *c) { r = dns_transaction_go(t); if (r < 0) return r; + + n++; } + /* If there was nothing to start, then let's proceed immediately */ + if (n == 0) + dns_query_candidate_notify(c); + return 0; } @@ -179,12 +193,20 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { assert(c); if (c->error_code != 0) - return DNS_TRANSACTION_RESOURCES; + return DNS_TRANSACTION_ERRNO; SET_FOREACH(t, c->transactions, i) { switch (t->state) { + case DNS_TRANSACTION_NULL: + /* If there's a NULL transaction pending, then + * this means not all transactions where + * started yet, and we were called from within + * the stackframe that is supposed to start + * remaining transactions. In this case, + * simply claim the candidate is pending. */ + case DNS_TRANSACTION_PENDING: case DNS_TRANSACTION_VALIDATING: /* If there's one transaction currently in @@ -197,9 +219,6 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { state = t->state; break; - case DNS_TRANSACTION_NULL: - assert_not_reached("Transaction not started?"); - default: if (state != DNS_TRANSACTION_SUCCESS) state = t->state; @@ -211,7 +230,33 @@ static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { return state; } +static bool dns_query_candidate_is_routable(DnsQueryCandidate *c, uint16_t type) { + int family; + + assert(c); + + /* Checks whether the specified RR type matches an address family that is routable on the link(s) the scope of + * this candidate belongs to. Specifically, whether there's a routable IPv4 address on it if we query an A RR, + * or a routable IPv6 address if we query an AAAA RR. */ + + if (!c->query->suppress_unroutable_family) + return true; + + if (c->scope->protocol != DNS_PROTOCOL_DNS) + return true; + + family = dns_type_to_af(type); + if (family < 0) + return true; + + if (c->scope->link) + return link_relevant(c->scope->link, family, false); + else + return manager_routable(c->scope->manager, family); +} + static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) { + DnsQuestion *question; DnsResourceKey *key; int n = 0, r; @@ -219,17 +264,29 @@ 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; + DnsResourceKey *qkey; + + if (!dns_query_candidate_is_routable(c, key->type)) + continue; if (c->search_domain) { r = dns_resource_key_new_append_suffix(&new_key, key, c->search_domain->name); if (r < 0) goto fail; - } - r = dns_query_candidate_add_transaction(c, new_key ?: key); + qkey = new_key; + } else + qkey = key; + + if (!dns_scope_good_key(c->scope, qkey)) + continue; + + r = dns_query_candidate_add_transaction(c, qkey); if (r < 0) goto fail; @@ -300,6 +357,26 @@ 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_errno = 0; + 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; @@ -313,16 +390,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--; @@ -333,17 +412,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; @@ -352,20 +464,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; + + 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; - for (i = 0; i < question->n_keys; i++) { - _cleanup_free_ char *p; + 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); @@ -441,7 +573,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) { @@ -464,413 +596,73 @@ fail: return r; } -static int SYNTHESIZE_IFINDEX(int ifindex) { - - /* When the caller asked for resolving on a specific - * interface, we synthesize the answer for that - * interface. However, if nothing specific was claimed and we - * only return localhost RRs, we synthesize the answer for - * localhost. */ - - if (ifindex > 0) - return ifindex; - - return LOOPBACK_IFINDEX; -} - -static int SYNTHESIZE_FAMILY(uint64_t flags) { - - /* Picks an address family depending on set flags. This is - * purely for synthesized answers, where the family we return - * for the reply should match what was requested in the - * question, even though we are synthesizing the answer - * here. */ - - if (!(flags & SD_RESOLVED_DNS)) { - if (flags & SD_RESOLVED_LLMNR_IPV4) - return AF_INET; - if (flags & SD_RESOLVED_LLMNR_IPV6) - return AF_INET6; - } - - return AF_UNSPEC; -} - -static DnsProtocol SYNTHESIZE_PROTOCOL(uint64_t flags) { - - /* Similar as SYNTHESIZE_FAMILY() but does this for the - * protocol. If resolving via DNS was requested, we claim it - * was DNS. Similar, if nothing specific was - * requested. However, if only resolving via LLMNR was - * requested we return that. */ - - if (flags & SD_RESOLVED_DNS) - return DNS_PROTOCOL_DNS; - if (flags & SD_RESOLVED_LLMNR) - return DNS_PROTOCOL_LLMNR; - - return DNS_PROTOCOL_DNS; -} - -static int dns_type_to_af(uint16_t t) { - switch (t) { - - case DNS_TYPE_A: - return AF_INET; - - case DNS_TYPE_AAAA: - return AF_INET6; - - case DNS_TYPE_ANY: - return AF_UNSPEC; - - default: - return -EINVAL; - } -} - -static int synthesize_localhost_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) { - int r; - - assert(q); - assert(key); - assert(answer); - - r = dns_answer_reserve(answer, 2); - if (r < 0) - return r; - - if (IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_ANY)) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, DNS_RESOURCE_KEY_NAME(key)); - if (!rr) - return -ENOMEM; - - rr->a.in_addr.s_addr = htobe32(INADDR_LOOPBACK); - - r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(q->ifindex)); - if (r < 0) - return r; - } - - if (IN_SET(key->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, DNS_RESOURCE_KEY_NAME(key)); - if (!rr) - return -ENOMEM; - - rr->aaaa.in6_addr = in6addr_loopback; - - r = dns_answer_add(*answer, rr, SYNTHESIZE_IFINDEX(q->ifindex)); - if (r < 0) - return r; - } - - return 0; -} - -static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, int ifindex) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, from); - if (!rr) - return -ENOMEM; - - rr->ptr.name = strdup(to); - if (!rr->ptr.name) - return -ENOMEM; - - return dns_answer_add(*answer, rr, ifindex); -} - -static int synthesize_localhost_ptr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) { - int r; - - assert(q); - assert(key); - assert(answer); - - r = dns_answer_reserve(answer, 1); - if (r < 0) - return r; - - if (IN_SET(key->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) { - r = answer_add_ptr(answer, DNS_RESOURCE_KEY_NAME(key), "localhost", SYNTHESIZE_IFINDEX(q->ifindex)); - if (r < 0) - return r; - } - - return 0; -} - -static int answer_add_addresses_rr( - DnsAnswer **answer, - const char *name, - struct local_address *addresses, - unsigned n_addresses) { - - unsigned j; - int r; - - assert(answer); - assert(name); - - r = dns_answer_reserve(answer, n_addresses); - if (r < 0) - return r; - - for (j = 0; j < n_addresses; j++) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - r = dns_resource_record_new_address(&rr, addresses[j].family, &addresses[j].address, name); - if (r < 0) - return r; - - r = dns_answer_add(*answer, rr, addresses[j].ifindex); - if (r < 0) - return r; - } - - return 0; -} - -static int answer_add_addresses_ptr( - DnsAnswer **answer, - const char *name, - struct local_address *addresses, - unsigned n_addresses, - int af, const union in_addr_union *match) { - - unsigned j; - int r; - - assert(answer); - assert(name); - - for (j = 0; j < n_addresses; j++) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - if (af != AF_UNSPEC) { - - if (addresses[j].family != af) - continue; - - if (match && !in_addr_equal(af, match, &addresses[j].address)) - continue; - } - - r = dns_answer_reserve(answer, 1); - if (r < 0) - return r; - - r = dns_resource_record_new_reverse(&rr, addresses[j].family, &addresses[j].address, name); - if (r < 0) - return r; - - r = dns_answer_add(*answer, rr, addresses[j].ifindex); - if (r < 0) - return r; - } - - return 0; -} - -static int synthesize_system_hostname_rr(DnsQuery *q, DnsResourceKey *key, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n = 0, af; - - assert(q); - assert(key); - assert(answer); - - af = dns_type_to_af(key->type); - if (af >= 0) { - n = local_addresses(q->manager->rtnl, q->ifindex, af, &addresses); - if (n < 0) - return n; - - if (n == 0) { - struct local_address buffer[2]; - - /* If we have no local addresses then use ::1 - * and 127.0.0.2 as local ones. */ - - if (af == AF_INET || af == AF_UNSPEC) - buffer[n++] = (struct local_address) { - .family = AF_INET, - .ifindex = SYNTHESIZE_IFINDEX(q->ifindex), - .address.in.s_addr = htobe32(0x7F000002), - }; - - if (af == AF_INET6 || af == AF_UNSPEC) - buffer[n++] = (struct local_address) { - .family = AF_INET6, - .ifindex = SYNTHESIZE_IFINDEX(q->ifindex), - .address.in6 = in6addr_loopback, - }; - - return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), buffer, n); - } - } - - return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n); -} - -static int synthesize_system_hostname_ptr(DnsQuery *q, int af, const union in_addr_union *address, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n, r; - - assert(q); - assert(address); - assert(answer); - - if (af == AF_INET && address->in.s_addr == htobe32(0x7F000002)) { - - /* Always map the IPv4 address 127.0.0.2 to the local - * hostname, in addition to "localhost": */ - - r = dns_answer_reserve(answer, 3); - if (r < 0) - return r; - - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->llmnr_hostname, SYNTHESIZE_IFINDEX(q->ifindex)); - if (r < 0) - return r; - - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", q->manager->mdns_hostname, SYNTHESIZE_IFINDEX(q->ifindex)); - if (r < 0) - return r; - - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", SYNTHESIZE_IFINDEX(q->ifindex)); - if (r < 0) - return r; - - return 0; - } - - n = local_addresses(q->manager->rtnl, q->ifindex, af, &addresses); - if (n < 0) - return n; - - r = answer_add_addresses_ptr(answer, q->manager->llmnr_hostname, addresses, n, af, address); - if (r < 0) - return r; - - 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) { - _cleanup_free_ struct local_address *addresses = NULL; - int n = 0, af; - - assert(q); - assert(key); - assert(answer); - - af = dns_type_to_af(key->type); - if (af >= 0) { - n = local_gateways(q->manager->rtnl, q->ifindex, af, &addresses); - if (n < 0) - return n; - } - - return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n); -} - -static int synthesize_gateway_ptr(DnsQuery *q, int af, const union in_addr_union *address, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n; - - assert(q); - assert(address); - assert(answer); - - n = local_gateways(q->manager->rtnl, q->ifindex, af, &addresses); - if (n < 0) - return n; - - return answer_add_addresses_ptr(answer, "gateway", addresses, n, af, address); -} - static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - unsigned i; int r; assert(q); assert(state); - /* Tries to synthesize localhost RR replies where appropriate */ + /* Tries to synthesize localhost RR replies (and others) where appropriate. Note that this is done *after* the + * the normal lookup finished. The data from the network hence takes precedence over the data we + * synthesize. (But note that many scopes refuse to resolve certain domain names) */ if (!IN_SET(*state, - DNS_TRANSACTION_FAILURE, + DNS_TRANSACTION_RCODE_FAILURE, DNS_TRANSACTION_NO_SERVERS, DNS_TRANSACTION_TIMEOUT, - DNS_TRANSACTION_ATTEMPTS_MAX_REACHED)) + DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, + DNS_TRANSACTION_NETWORK_DOWN, + DNS_TRANSACTION_NOT_FOUND)) return 0; - for (i = 0; i < q->question->n_keys; i++) { - 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) - continue; + r = dns_synthesize_answer( + q->manager, + q->question_utf8, + q->ifindex, + &answer); - name = DNS_RESOURCE_KEY_NAME(q->question->keys[i]); - - if (is_localhost(name)) { - - r = synthesize_localhost_rr(q, q->question->keys[i], &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize localhost RRs: %m"); - - } else if (manager_is_own_hostname(q->manager, name)) { + if (r <= 0) + return r; - r = synthesize_system_hostname_rr(q, q->question->keys[i], &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize system hostname RRs: %m"); + dns_query_reset_answer(q); - } else if (is_gateway_hostname(name)) { + q->answer = answer; + answer = NULL; + q->answer_rcode = DNS_RCODE_SUCCESS; + q->answer_protocol = dns_synthesize_protocol(q->flags); + q->answer_family = dns_synthesize_family(q->flags); + q->answer_authenticated = true; - r = synthesize_gateway_rr(q, q->question->keys[i], &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize gateway RRs: %m"); + *state = DNS_TRANSACTION_SUCCESS; - } 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) { + return 1; +} - r = synthesize_localhost_ptr(q, q->question->keys[i], &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize localhost PTR RRs: %m"); +static int dns_query_try_etc_hosts(DnsQuery *q) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + int r; - } else if (dns_name_address(name, &af, &address) > 0) { + assert(q); - r = synthesize_system_hostname_ptr(q, af, &address, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize system hostname PTR RR: %m"); + /* Looks in /etc/hosts for matching entries. Note that this is done *before* the normal lookup is done. The + * data from /etc/hosts hence takes precedence over the network. */ - r = synthesize_gateway_ptr(q, af, &address, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize gateway hostname PTR RR: %m"); - } - } + r = manager_etc_hosts_lookup( + q->manager, + q->question_utf8, + &answer); + if (r <= 0) + return r; - if (!answer) - return 0; + dns_query_reset_answer(q); - dns_answer_unref(q->answer); q->answer = answer; answer = NULL; - q->answer_rcode = DNS_RCODE_SUCCESS; - q->answer_protocol = SYNTHESIZE_PROTOCOL(q->flags); - q->answer_family = SYNTHESIZE_FAMILY(q->flags); - - *state = DNS_TRANSACTION_SUCCESS; + q->answer_protocol = dns_synthesize_protocol(q->flags); + q->answer_family = dns_synthesize_family(q->flags); + q->answer_authenticated = true; return 1; } @@ -879,7 +671,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); @@ -887,13 +678,21 @@ 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); + r = dns_query_try_etc_hosts(q); + if (r < 0) + return r; + if (r > 0) { + dns_query_complete(q, DNS_TRANSACTION_SUCCESS); + return 1; + } 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) @@ -918,7 +717,10 @@ int dns_query_go(DnsQuery *q) { if (found == DNS_SCOPE_NO) { DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; - dns_query_synthesize_reply(q, &state); + r = dns_query_synthesize_reply(q, &state); + if (r < 0) + return r; + dns_query_complete(q, state); return 1; } @@ -929,6 +731,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) @@ -942,10 +749,7 @@ int dns_query_go(DnsQuery *q) { goto fail; } - q->answer = dns_answer_unref(q->answer); - q->answer_rcode = 0; - q->answer_family = AF_UNSPEC; - q->answer_protocol = _DNS_PROTOCOL_INVALID; + dns_query_reset_answer(q); r = sd_event_add_time( q->manager->event, @@ -956,6 +760,8 @@ int dns_query_go(DnsQuery *q) { if (r < 0) goto fail; + (void) sd_event_source_set_description(q->timeout_event_source, "query-timeout"); + q->state = DNS_TRANSACTION_PENDING; q->block_ready++; @@ -981,6 +787,7 @@ fail: static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; bool has_authenticated = false, has_non_authenticated = false; + DnssecResult dnssec_result_authenticated = _DNSSEC_RESULT_INVALID, dnssec_result_non_authenticated = _DNSSEC_RESULT_INVALID; DnsTransaction *t; Iterator i; int r; @@ -988,11 +795,23 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { assert(q); if (!c) { - dns_query_synthesize_reply(q, &state); + r = dns_query_synthesize_reply(q, &state); + if (r < 0) + goto fail; + dns_query_complete(q, state); return; } + if (c->error_code != 0) { + /* If the candidate had an error condition of its own, start with that. */ + state = DNS_TRANSACTION_ERRNO; + q->answer = dns_answer_unref(q->answer); + q->answer_rcode = 0; + q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + q->answer_errno = c->error_code; + } + SET_FOREACH(t, c->transactions, i) { switch (t->state) { @@ -1000,16 +819,19 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { case DNS_TRANSACTION_SUCCESS: { /* We found a successfuly reply, merge it into the answer */ r = dns_answer_extend(&q->answer, t->answer); - if (r < 0) { - dns_query_complete(q, DNS_TRANSACTION_RESOURCES); - return; - } + if (r < 0) + goto fail; + q->answer_rcode = t->answer_rcode; + q->answer_errno = 0; - if (t->answer_authenticated) + if (t->answer_authenticated) { has_authenticated = true; - else + dnssec_result_authenticated = t->answer_dnssec_result; + } else { has_non_authenticated = true; + dnssec_result_non_authenticated = t->answer_dnssec_result; + } state = DNS_TRANSACTION_SUCCESS; break; @@ -1026,28 +848,40 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { /* Any kind of failure? Store the data away, * if there's nothing stored yet. */ - if (state != DNS_TRANSACTION_SUCCESS) { - - dns_answer_unref(q->answer); - q->answer = dns_answer_ref(t->answer); - q->answer_rcode = t->answer_rcode; + if (state == DNS_TRANSACTION_SUCCESS) + continue; - state = t->state; - } + q->answer = dns_answer_unref(q->answer); + q->answer_rcode = t->answer_rcode; + q->answer_dnssec_result = t->answer_dnssec_result; + q->answer_errno = t->answer_errno; + state = t->state; break; } } + if (state == DNS_TRANSACTION_SUCCESS) { + q->answer_authenticated = has_authenticated && !has_non_authenticated; + q->answer_dnssec_result = q->answer_authenticated ? dnssec_result_authenticated : dnssec_result_non_authenticated; + } + q->answer_protocol = c->scope->protocol; q->answer_family = c->scope->family; - q->answer_authenticated = has_authenticated && !has_non_authenticated; dns_search_domain_unref(q->answer_search_domain); q->answer_search_domain = dns_search_domain_ref(c->search_domain); - dns_query_synthesize_reply(q, &state); + r = dns_query_synthesize_reply(q, &state); + if (r < 0) + goto fail; + dns_query_complete(q, state); + return; + +fail: + q->answer_errno = -r; + dns_query_complete(q, DNS_TRANSACTION_ERRNO); } void dns_query_ready(DnsQuery *q) { @@ -1100,26 +934,61 @@ 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)); + } + + if (r == 0 && k == 0) /* No actual cname happened? */ + return -ELOOP; + + if (q->answer_protocol == DNS_PROTOCOL_DNS) { + /* Don't permit CNAME redirects from unicast DNS to LLMNR or MulticastDNS, so that global resources + * cannot invade the local namespace. The opposite way we permit: local names may redirect to global + * ones. */ + + q->flags &= ~(SD_RESOLVED_LLMNR|SD_RESOLVED_MDNS); /* mask away the local protocols */ + } + + /* Turn off searching for the new name */ + q->flags |= SD_RESOLVED_NO_SEARCH; + + 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); - dns_query_stop(q); q->state = DNS_TRANSACTION_NULL; return 0; @@ -1127,23 +996,25 @@ static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) int dns_query_process_cname(DnsQuery *q) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL; + DnsQuestion *question; DnsResourceRecord *rr; int r; assert(q); - if (q->state != DNS_TRANSACTION_SUCCESS) - return 0; + if (!IN_SET(q->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_NULL)) + return DNS_QUERY_NOMATCH; - DNS_ANSWER_FOREACH(rr, q->answer) { + question = dns_query_question_for_protocol(q, q->answer_protocol); - r = dns_question_matches_rr(q->question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + DNS_ANSWER_FOREACH(rr, q->answer) { + r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); if (r < 0) return r; if (r > 0) - return 0; /* The answer matches directly, no need to follow cnames */ + return DNS_QUERY_MATCH; /* The answer matches directly, no need to follow cnames */ - r = dns_question_matches_cname(q->question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + r = dns_question_matches_cname_or_dname(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); if (r < 0) return r; if (r > 0 && !cname) @@ -1151,7 +1022,7 @@ int dns_query_process_cname(DnsQuery *q) { } if (!cname) - return 0; /* No cname to follow */ + return DNS_QUERY_NOMATCH; /* No match and no cname to follow */ if (q->flags & SD_RESOLVED_NO_CNAME) return -ELOOP; @@ -1163,20 +1034,16 @@ int dns_query_process_cname(DnsQuery *q) { /* Let's see if the answer can already answer the new * redirected question */ - DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(q->question, rr, NULL); - if (r < 0) - return r; - if (r > 0) - return 0; /* It can answer it, yay! */ - } + r = dns_query_process_cname(q); + if (r != DNS_QUERY_NOMATCH) + return r; /* OK, it cannot, let's begin with the new query */ r = dns_query_go(q); if (r < 0) return r; - return 1; /* We return > 0, if we restarted the query for a new cname */ + return DNS_QUERY_RESTARTED; /* We restarted the query for a new cname */ } static int on_bus_track(sd_bus_track *t, void *userdata) { @@ -1208,3 +1075,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 d7f96c3ca4..75c2c14c1f 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -59,10 +59,20 @@ 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; + /* If true, A or AAAA RR lookups will be suppressed on links with no routable address of the matching address + * family */ + bool suppress_unroutable_family; + DnsTransactionState state; unsigned n_cname_redirects; @@ -72,10 +82,12 @@ struct DnsQuery { /* Discovered data */ DnsAnswer *answer; int answer_rcode; + DnssecResult answer_dnssec_result; + bool answer_authenticated; DnsProtocol answer_protocol; int answer_family; DnsSearchDomain *answer_search_domain; - bool answer_authenticated; + int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ /* Bus client information */ sd_bus_message *request; @@ -83,6 +95,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); @@ -94,10 +107,16 @@ struct DnsQuery { LIST_FIELDS(DnsQuery, auxiliary_queries); }; +enum { + DNS_QUERY_MATCH, + DNS_QUERY_NOMATCH, + DNS_QUERY_RESTARTED, +}; + DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c); void dns_query_candidate_notify(DnsQueryCandidate *c); -int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question, int family, uint64_t flags); +int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question_utf8, DnsQuestion *question_idna, int family, uint64_t flags); DnsQuery *dns_query_free(DnsQuery *q); int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for); @@ -109,4 +128,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 74c9d87319..783ec7516c 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -25,11 +25,13 @@ #include "dns-domain.h" #include "dns-type.h" #include "hexdecoct.h" +#include "resolved-dns-dnssec.h" #include "resolved-dns-packet.h" #include "resolved-dns-rr.h" #include "string-table.h" #include "string-util.h" #include "strv.h" +#include "terminal-util.h" DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name) { DnsResourceKey *k; @@ -184,7 +186,7 @@ int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) { return 1; } -int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain) { +int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain) { int r; assert(key); @@ -261,16 +263,13 @@ int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey * /* Checks whether 'soa' is a SOA record for the specified key. */ - if (soa->class != DNS_CLASS_IN) + if (soa->class != key->class) return 0; if (soa->type != DNS_TYPE_SOA) return 0; - if (!dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(soa))) - return 0; - - return 1; + return dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(soa)); } static void dns_resource_key_hash_func(const void *i, struct siphash *state) { @@ -311,9 +310,12 @@ const struct hash_ops dns_resource_key_hash_ops = { int dns_resource_key_to_string(const DnsResourceKey *key, char **ret) { char cbuf[strlen("CLASS") + DECIMAL_STR_MAX(uint16_t)], tbuf[strlen("TYPE") + DECIMAL_STR_MAX(uint16_t)]; - const char *c, *t; + const char *c, *t, *n; char *s; + /* If we cannot convert the CLASS/TYPE into a known string, + use the format recommended by RFC 3597, Section 5. */ + c = dns_class_to_string(key->class); if (!c) { sprintf(cbuf, "CLASS%u", key->class); @@ -326,13 +328,54 @@ int dns_resource_key_to_string(const DnsResourceKey *key, char **ret) { t = tbuf; } - if (asprintf(&s, "%s. %s %-5s", DNS_RESOURCE_KEY_NAME(key), c, t) < 0) + n = DNS_RESOURCE_KEY_NAME(key); + if (asprintf(&s, "%s%s %s %-5s", n, endswith(n, ".") ? "" : ".", c, t) < 0) return -ENOMEM; *ret = s; 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; @@ -342,6 +385,8 @@ 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; } @@ -443,6 +488,11 @@ DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) { case DNS_TYPE_AAAA: break; + case DNS_TYPE_TLSA: + free(rr->tlsa.data); + break; + + case DNS_TYPE_OPENPGPKEY: default: free(rr->generic.data); } @@ -451,6 +501,7 @@ DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) { dns_resource_key_unref(rr->key); } + free(rr->to_string); free(rr); return NULL; @@ -520,6 +571,10 @@ int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const u return 0; } +#define FIELD_EQUAL(a, b, field) \ + ((a).field ## _size == (b).field ## _size && \ + memcmp((a).field, (b).field, (a).field ## _size) == 0) + int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b) { int r; @@ -601,36 +656,30 @@ int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecor return a->ds.key_tag == b->ds.key_tag && a->ds.algorithm == b->ds.algorithm && a->ds.digest_type == b->ds.digest_type && - a->ds.digest_size == b->ds.digest_size && - memcmp(a->ds.digest, b->ds.digest, a->ds.digest_size) == 0; + FIELD_EQUAL(a->ds, b->ds, digest); case DNS_TYPE_SSHFP: return a->sshfp.algorithm == b->sshfp.algorithm && a->sshfp.fptype == b->sshfp.fptype && - a->sshfp.fingerprint_size == b->sshfp.fingerprint_size && - memcmp(a->sshfp.fingerprint, b->sshfp.fingerprint, a->sshfp.fingerprint_size) == 0; + FIELD_EQUAL(a->sshfp, b->sshfp, fingerprint); case DNS_TYPE_DNSKEY: return a->dnskey.flags == b->dnskey.flags && a->dnskey.protocol == b->dnskey.protocol && a->dnskey.algorithm == b->dnskey.algorithm && - a->dnskey.key_size == b->dnskey.key_size && - memcmp(a->dnskey.key, b->dnskey.key, a->dnskey.key_size) == 0; + FIELD_EQUAL(a->dnskey, b->dnskey, key); case DNS_TYPE_RRSIG: /* do the fast comparisons first */ - if (a->rrsig.type_covered != b->rrsig.type_covered || - a->rrsig.algorithm != b->rrsig.algorithm || - a->rrsig.labels != b->rrsig.labels || - a->rrsig.original_ttl != b->rrsig.original_ttl || - a->rrsig.expiration != b->rrsig.expiration || - a->rrsig.inception != b->rrsig.inception || - a->rrsig.key_tag != b->rrsig.key_tag || - a->rrsig.signature_size != b->rrsig.signature_size || - memcmp(a->rrsig.signature, b->rrsig.signature, a->rrsig.signature_size) != 0) - return false; - - return dns_name_equal(a->rrsig.signer, b->rrsig.signer); + return a->rrsig.type_covered == b->rrsig.type_covered && + a->rrsig.algorithm == b->rrsig.algorithm && + a->rrsig.labels == b->rrsig.labels && + a->rrsig.original_ttl == b->rrsig.original_ttl && + a->rrsig.expiration == b->rrsig.expiration && + a->rrsig.inception == b->rrsig.inception && + a->rrsig.key_tag == b->rrsig.key_tag && + FIELD_EQUAL(a->rrsig, b->rrsig, signature) && + dns_name_equal(a->rrsig.signer, b->rrsig.signer); case DNS_TYPE_NSEC: return dns_name_equal(a->nsec.next_domain_name, b->nsec.next_domain_name) && @@ -638,16 +687,20 @@ int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecor case DNS_TYPE_NSEC3: return a->nsec3.algorithm == b->nsec3.algorithm && - a->nsec3.flags == b->nsec3.flags && - a->nsec3.iterations == b->nsec3.iterations && - a->nsec3.salt_size == b->nsec3.salt_size && - memcmp(a->nsec3.salt, b->nsec3.salt, a->nsec3.salt_size) == 0 && - memcmp(a->nsec3.next_hashed_name, b->nsec3.next_hashed_name, a->nsec3.next_hashed_name_size) == 0 && - bitmap_equal(a->nsec3.types, b->nsec3.types); + a->nsec3.flags == b->nsec3.flags && + a->nsec3.iterations == b->nsec3.iterations && + FIELD_EQUAL(a->nsec3, b->nsec3, salt) && + FIELD_EQUAL(a->nsec3, b->nsec3, next_hashed_name) && + bitmap_equal(a->nsec3.types, b->nsec3.types); + + case DNS_TYPE_TLSA: + return a->tlsa.cert_usage == b->tlsa.cert_usage && + a->tlsa.selector == b->tlsa.selector && + a->tlsa.matching_type == b->tlsa.matching_type && + FIELD_EQUAL(a->tlsa, b->tlsa, data); default: - return a->generic.size == b->generic.size && - memcmp(a->generic.data, b->generic.data, a->generic.size) == 0; + return FIELD_EQUAL(a->generic, b->generic, data); } } @@ -766,16 +819,19 @@ static char *format_txt(DnsTxtItem *first) { return s; } -int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { +const char *dns_resource_record_to_string(DnsResourceRecord *rr) { _cleanup_free_ char *k = NULL, *t = NULL; char *s; int r; assert(rr); + if (rr->to_string) + return rr->to_string; + r = dns_resource_key_to_string(rr->key, &k); if (r < 0) - return r; + return NULL; switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { @@ -787,7 +843,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { rr->srv.port, strna(rr->srv.name)); if (r < 0) - return -ENOMEM; + return NULL; break; case DNS_TYPE_PTR: @@ -796,25 +852,25 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { case DNS_TYPE_DNAME: s = strjoin(k, " ", rr->ptr.name, NULL); if (!s) - return -ENOMEM; + return NULL; break; case DNS_TYPE_HINFO: s = strjoin(k, " ", rr->hinfo.cpu, " ", rr->hinfo.os, NULL); if (!s) - return -ENOMEM; + return NULL; break; case DNS_TYPE_SPF: /* exactly the same as TXT */ case DNS_TYPE_TXT: t = format_txt(rr->txt.items); if (!t) - return -ENOMEM; + return NULL; s = strjoin(k, " ", t, NULL); if (!s) - return -ENOMEM; + return NULL; break; case DNS_TYPE_A: { @@ -822,22 +878,22 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { r = in_addr_to_string(AF_INET, (const union in_addr_union*) &rr->a.in_addr, &x); if (r < 0) - return r; + return NULL; s = strjoin(k, " ", x, NULL); if (!s) - return -ENOMEM; + return NULL; break; } case DNS_TYPE_AAAA: r = in_addr_to_string(AF_INET6, (const union in_addr_union*) &rr->aaaa.in6_addr, &t); if (r < 0) - return r; + return NULL; s = strjoin(k, " ", t, NULL); if (!s) - return -ENOMEM; + return NULL; break; case DNS_TYPE_SOA: @@ -851,7 +907,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { rr->soa.expire, rr->soa.minimum); if (r < 0) - return -ENOMEM; + return NULL; break; case DNS_TYPE_MX: @@ -860,7 +916,7 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { rr->mx.priority, rr->mx.exchange); if (r < 0) - return -ENOMEM; + return NULL; break; case DNS_TYPE_LOC: @@ -873,17 +929,17 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { rr->loc.horiz_pre, rr->loc.vert_pre); if (!t) - return -ENOMEM; + return NULL; s = strjoin(k, " ", t, NULL); if (!s) - return -ENOMEM; + return NULL; break; case DNS_TYPE_DS: t = hexmem(rr->ds.digest, rr->ds.digest_size); if (!t) - return -ENOMEM; + return NULL; r = asprintf(&s, "%s %u %u %u %s", k, @@ -892,13 +948,13 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { rr->ds.digest_type, t); if (r < 0) - return -ENOMEM; + return NULL; break; case DNS_TYPE_SSHFP: t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); if (!t) - return -ENOMEM; + return NULL; r = asprintf(&s, "%s %u %u %s", k, @@ -906,81 +962,113 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { rr->sshfp.fptype, t); if (r < 0) - return -ENOMEM; + return NULL; break; case DNS_TYPE_DNSKEY: { - const char *alg; + _cleanup_free_ char *alg = NULL; + char *ss; + int n, n1; + uint16_t key_tag; - alg = dnssec_algorithm_to_string(rr->dnskey.algorithm); + key_tag = dnssec_keytag(rr, true); - t = base64mem(rr->dnskey.key, rr->dnskey.key_size); - if (!t) - return -ENOMEM; + r = dnssec_algorithm_to_string_alloc(rr->dnskey.algorithm, &alg); + if (r < 0) + return NULL; - r = asprintf(&s, "%s %u %u %.*s%.*u %s", + r = asprintf(&s, "%s %n%u %u %s %n", k, + &n1, rr->dnskey.flags, rr->dnskey.protocol, - alg ? -1 : 0, alg, - alg ? 0 : 1, alg ? 0u : (unsigned) rr->dnskey.algorithm, - t); + alg, + &n); if (r < 0) - return -ENOMEM; + return NULL; + + r = base64_append(&s, n, + rr->dnskey.key, rr->dnskey.key_size, + 8, columns()); + if (r < 0) + return NULL; + + r = asprintf(&ss, "%s\n" + "%*s-- Flags:%s%s%s\n" + "%*s-- Key tag: %u", + s, + n1, "", + rr->dnskey.flags & DNSKEY_FLAG_SEP ? " SEP" : "", + rr->dnskey.flags & DNSKEY_FLAG_REVOKE ? " REVOKE" : "", + rr->dnskey.flags & DNSKEY_FLAG_ZONE_KEY ? " ZONE_KEY" : "", + n1, "", + key_tag); + if (r < 0) + return NULL; + free(s); + s = ss; + break; } case DNS_TYPE_RRSIG: { - const char *type, *alg; + _cleanup_free_ char *alg = NULL; char expiration[strlen("YYYYMMDDHHmmSS") + 1], inception[strlen("YYYYMMDDHHmmSS") + 1]; + const char *type; + int n; type = dns_type_to_string(rr->rrsig.type_covered); - alg = dnssec_algorithm_to_string(rr->rrsig.algorithm); - t = base64mem(rr->rrsig.signature, rr->rrsig.signature_size); - if (!t) - return -ENOMEM; + r = dnssec_algorithm_to_string_alloc(rr->rrsig.algorithm, &alg); + if (r < 0) + return NULL; r = format_timestamp_dns(expiration, sizeof(expiration), rr->rrsig.expiration); if (r < 0) - return r; + return NULL; r = format_timestamp_dns(inception, sizeof(inception), rr->rrsig.inception); if (r < 0) - return r; + return NULL; /* TYPE?? follows * http://tools.ietf.org/html/rfc3597#section-5 */ - r = asprintf(&s, "%s %s%.*u %.*s%.*u %u %u %s %s %u %s %s", + r = asprintf(&s, "%s %s%.*u %s %u %u %s %s %u %s %n", k, type ?: "TYPE", type ? 0 : 1, type ? 0u : (unsigned) rr->rrsig.type_covered, - alg ? -1 : 0, alg, - alg ? 0 : 1, alg ? 0u : (unsigned) rr->rrsig.algorithm, + alg, rr->rrsig.labels, rr->rrsig.original_ttl, expiration, inception, rr->rrsig.key_tag, rr->rrsig.signer, - t); + &n); if (r < 0) - return -ENOMEM; + return NULL; + + r = base64_append(&s, n, + rr->rrsig.signature, rr->rrsig.signature_size, + 8, columns()); + if (r < 0) + return NULL; + break; } case DNS_TYPE_NSEC: t = format_types(rr->nsec.types); if (!t) - return -ENOMEM; + return NULL; r = asprintf(&s, "%s %s %s", k, rr->nsec.next_domain_name, t); if (r < 0) - return -ENOMEM; + return NULL; break; case DNS_TYPE_NSEC3: { @@ -989,16 +1077,16 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { if (rr->nsec3.salt_size > 0) { salt = hexmem(rr->nsec3.salt, rr->nsec3.salt_size); if (!salt) - return -ENOMEM; + return NULL; } hash = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false); if (!hash) - return -ENOMEM; + return NULL; t = format_types(rr->nsec3.types); if (!t) - return -ENOMEM; + return NULL; r = asprintf(&s, "%s %"PRIu8" %"PRIu8" %"PRIu16" %s %s %s", k, @@ -1009,24 +1097,82 @@ int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret) { hash, t); if (r < 0) - return -ENOMEM; + return NULL; + + break; + } + + case DNS_TYPE_TLSA: { + const char *cert_usage, *selector, *matching_type; + char *ss; + int n; + + cert_usage = tlsa_cert_usage_to_string(rr->tlsa.cert_usage); + selector = tlsa_selector_to_string(rr->tlsa.selector); + matching_type = tlsa_matching_type_to_string(rr->tlsa.matching_type); + + r = asprintf(&s, "%s %u %u %u %n", + k, + rr->tlsa.cert_usage, + rr->tlsa.selector, + rr->tlsa.matching_type, + &n); + if (r < 0) + return NULL; + r = base64_append(&s, n, + rr->tlsa.data, rr->tlsa.data_size, + 8, columns()); + if (r < 0) + return NULL; + + r = asprintf(&ss, "%s\n" + "%*s-- Cert. usage: %s\n" + "%*s-- Selector: %s\n" + "%*s-- Matching type: %s", + s, + n - 6, "", cert_usage, + n - 6, "", selector, + n - 6, "", matching_type); + if (r < 0) + return NULL; + free(s); + s = ss; + + break; + } + + case DNS_TYPE_OPENPGPKEY: { + int n; + + r = asprintf(&s, "%s %n", + k, + &n); + if (r < 0) + return NULL; + + r = base64_append(&s, n, + rr->generic.data, rr->generic.data_size, + 8, columns()); + if (r < 0) + return NULL; break; } default: - t = hexmem(rr->generic.data, rr->generic.size); + t = hexmem(rr->generic.data, rr->generic.data_size); if (!t) - return -ENOMEM; + return NULL; - r = asprintf(&s, "%s \\# %zu %s", k, rr->generic.size, t); + /* Format as documented in RFC 3597, Section 5 */ + r = asprintf(&s, "%s \\# %zu %s", k, rr->generic.data_size, t); if (r < 0) - return -ENOMEM; + return NULL; break; } - *ret = s; - return 0; + rr->to_string = s; + return s; } int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) { @@ -1074,34 +1220,247 @@ int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) { return 0; } -const char *dns_class_to_string(uint16_t class) { +int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret) { + const char *n; + int r; + + assert(rr); + assert(ret); - switch (class) { + /* Returns the RRset's signer, if it is known. */ - case DNS_CLASS_IN: - return "IN"; + if (rr->n_skip_labels_signer == (unsigned) -1) + return -ENODATA; - case DNS_CLASS_ANY: - return "ANY"; - } + 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; - return NULL; + *ret = n; + return 0; } -int dns_class_from_string(const char *s, uint16_t *class) { - assert(s); - assert(class); +int dns_resource_record_source(DnsResourceRecord *rr, const char **ret) { + const char *n; + int r; - if (strcaseeq(s, "IN")) - *class = DNS_CLASS_IN; - else if (strcaseeq(s, "ANY")) - *class = DNS_CLASS_ANY; - else + assert(rr); + assert(ret); + + /* Returns the RRset's synthesizing source, if it is known. */ + + if (rr->n_skip_labels_source == (unsigned) -1) + return -ENODATA; + + n = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_skip(n, rr->n_skip_labels_source, &n); + if (r < 0) + return r; + if (r == 0) return -EINVAL; + *ret = n; return 0; } +int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone) { + const char *signer; + int r; + + assert(rr); + + r = dns_resource_record_signer(rr, &signer); + if (r < 0) + return r; + + return dns_name_equal(zone, signer); +} + +int dns_resource_record_is_synthetic(DnsResourceRecord *rr) { + int r; + + assert(rr); + + /* Returns > 0 if the RR is generated from a wildcard, and is not the asterisk name itself */ + + if (rr->n_skip_labels_source == (unsigned) -1) + return -ENODATA; + + if (rr->n_skip_labels_source == 0) + return 0; + + if (rr->n_skip_labels_source > 1) + return 1; + + r = dns_name_startswith(DNS_RESOURCE_KEY_NAME(rr->key), "*"); + if (r < 0) + return r; + + return !r; +} + +static void dns_resource_record_hash_func(const void *i, struct siphash *state) { + const DnsResourceRecord *rr = i; + + assert(rr); + + dns_resource_key_hash_func(rr->key, state); + + switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { + + case DNS_TYPE_SRV: + siphash24_compress(&rr->srv.priority, sizeof(rr->srv.priority), state); + siphash24_compress(&rr->srv.weight, sizeof(rr->srv.weight), state); + siphash24_compress(&rr->srv.port, sizeof(rr->srv.port), state); + dns_name_hash_func(rr->srv.name, state); + break; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + dns_name_hash_func(rr->ptr.name, state); + break; + + case DNS_TYPE_HINFO: + string_hash_func(rr->hinfo.cpu, state); + string_hash_func(rr->hinfo.os, state); + break; + + case DNS_TYPE_TXT: + case DNS_TYPE_SPF: { + DnsTxtItem *j; + + LIST_FOREACH(items, j, rr->txt.items) { + siphash24_compress(j->data, j->length, state); + + /* Add an extra NUL byte, so that "a" followed by "b" doesn't result in the same hash as "ab" + * followed by "". */ + siphash24_compress_byte(0, state); + } + break; + } + + case DNS_TYPE_A: + siphash24_compress(&rr->a.in_addr, sizeof(rr->a.in_addr), state); + break; + + case DNS_TYPE_AAAA: + siphash24_compress(&rr->aaaa.in6_addr, sizeof(rr->aaaa.in6_addr), state); + break; + + case DNS_TYPE_SOA: + dns_name_hash_func(rr->soa.mname, state); + dns_name_hash_func(rr->soa.rname, state); + siphash24_compress(&rr->soa.serial, sizeof(rr->soa.serial), state); + siphash24_compress(&rr->soa.refresh, sizeof(rr->soa.refresh), state); + siphash24_compress(&rr->soa.retry, sizeof(rr->soa.retry), state); + siphash24_compress(&rr->soa.expire, sizeof(rr->soa.expire), state); + siphash24_compress(&rr->soa.minimum, sizeof(rr->soa.minimum), state); + break; + + case DNS_TYPE_MX: + siphash24_compress(&rr->mx.priority, sizeof(rr->mx.priority), state); + dns_name_hash_func(rr->mx.exchange, state); + break; + + case DNS_TYPE_LOC: + siphash24_compress(&rr->loc.version, sizeof(rr->loc.version), state); + siphash24_compress(&rr->loc.size, sizeof(rr->loc.size), state); + siphash24_compress(&rr->loc.horiz_pre, sizeof(rr->loc.horiz_pre), state); + siphash24_compress(&rr->loc.vert_pre, sizeof(rr->loc.vert_pre), state); + siphash24_compress(&rr->loc.latitude, sizeof(rr->loc.latitude), state); + siphash24_compress(&rr->loc.longitude, sizeof(rr->loc.longitude), state); + siphash24_compress(&rr->loc.altitude, sizeof(rr->loc.altitude), state); + break; + + case DNS_TYPE_SSHFP: + siphash24_compress(&rr->sshfp.algorithm, sizeof(rr->sshfp.algorithm), state); + siphash24_compress(&rr->sshfp.fptype, sizeof(rr->sshfp.fptype), state); + siphash24_compress(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, state); + break; + + case DNS_TYPE_DNSKEY: + siphash24_compress(&rr->dnskey.flags, sizeof(rr->dnskey.flags), state); + siphash24_compress(&rr->dnskey.protocol, sizeof(rr->dnskey.protocol), state); + siphash24_compress(&rr->dnskey.algorithm, sizeof(rr->dnskey.algorithm), state); + siphash24_compress(rr->dnskey.key, rr->dnskey.key_size, state); + break; + + case DNS_TYPE_RRSIG: + siphash24_compress(&rr->rrsig.type_covered, sizeof(rr->rrsig.type_covered), state); + siphash24_compress(&rr->rrsig.algorithm, sizeof(rr->rrsig.algorithm), state); + siphash24_compress(&rr->rrsig.labels, sizeof(rr->rrsig.labels), state); + siphash24_compress(&rr->rrsig.original_ttl, sizeof(rr->rrsig.original_ttl), state); + siphash24_compress(&rr->rrsig.expiration, sizeof(rr->rrsig.expiration), state); + siphash24_compress(&rr->rrsig.inception, sizeof(rr->rrsig.inception), state); + siphash24_compress(&rr->rrsig.key_tag, sizeof(rr->rrsig.key_tag), state); + dns_name_hash_func(rr->rrsig.signer, state); + siphash24_compress(rr->rrsig.signature, rr->rrsig.signature_size, state); + break; + + case DNS_TYPE_NSEC: + dns_name_hash_func(rr->nsec.next_domain_name, state); + /* FIXME: we leave out the type bitmap here. Hash + * would be better if we'd take it into account + * too. */ + break; + + case DNS_TYPE_DS: + siphash24_compress(&rr->ds.key_tag, sizeof(rr->ds.key_tag), state); + siphash24_compress(&rr->ds.algorithm, sizeof(rr->ds.algorithm), state); + siphash24_compress(&rr->ds.digest_type, sizeof(rr->ds.digest_type), state); + siphash24_compress(rr->ds.digest, rr->ds.digest_size, state); + break; + + case DNS_TYPE_NSEC3: + siphash24_compress(&rr->nsec3.algorithm, sizeof(rr->nsec3.algorithm), state); + siphash24_compress(&rr->nsec3.flags, sizeof(rr->nsec3.flags), state); + siphash24_compress(&rr->nsec3.iterations, sizeof(rr->nsec3.iterations), state); + siphash24_compress(rr->nsec3.salt, rr->nsec3.salt_size, state); + siphash24_compress(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, state); + /* FIXME: We leave the bitmaps out */ + break; + + case DNS_TYPE_TLSA: + siphash24_compress(&rr->tlsa.cert_usage, sizeof(rr->tlsa.cert_usage), state); + siphash24_compress(&rr->tlsa.selector, sizeof(rr->tlsa.selector), state); + siphash24_compress(&rr->tlsa.matching_type, sizeof(rr->tlsa.matching_type), state); + siphash24_compress(&rr->tlsa.data, rr->tlsa.data_size, state); + break; + + case DNS_TYPE_OPENPGPKEY: + default: + siphash24_compress(rr->generic.data, rr->generic.data_size, state); + break; + } +} + +static int dns_resource_record_compare_func(const void *a, const void *b) { + const DnsResourceRecord *x = a, *y = b; + int ret; + + ret = dns_resource_key_compare_func(x->key, y->key); + if (ret != 0) + return ret; + + if (dns_resource_record_equal(x, y)) + return 0; + + /* This is a bit dirty, we don't implement proper odering, but + * the hashtable doesn't need ordering anyway, hence we don't + * care. */ + return x < y ? -1 : 1; +} + +const struct hash_ops dns_resource_record_hash_ops = { + .hash = dns_resource_record_hash_func, + .compare = dns_resource_record_compare_func, +}; + DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i) { DnsTxtItem *n; @@ -1135,6 +1494,7 @@ bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) { } static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = { + /* Mnemonics as listed on https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ [DNSSEC_ALGORITHM_RSAMD5] = "RSAMD5", [DNSSEC_ALGORITHM_DH] = "DH", [DNSSEC_ALGORITHM_DSA] = "DSA", @@ -1144,14 +1504,20 @@ static const char* const dnssec_algorithm_table[_DNSSEC_ALGORITHM_MAX_DEFINED] = [DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1] = "RSASHA1-NSEC3-SHA1", [DNSSEC_ALGORITHM_RSASHA256] = "RSASHA256", [DNSSEC_ALGORITHM_RSASHA512] = "RSASHA512", + [DNSSEC_ALGORITHM_ECC_GOST] = "ECC-GOST", + [DNSSEC_ALGORITHM_ECDSAP256SHA256] = "ECDSAP256SHA256", + [DNSSEC_ALGORITHM_ECDSAP384SHA384] = "ECDSAP384SHA384", [DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT", [DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS", [DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID", }; -DEFINE_STRING_TABLE_LOOKUP(dnssec_algorithm, int); +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_algorithm, int, 255); static const char* const dnssec_digest_table[_DNSSEC_DIGEST_MAX_DEFINED] = { - [DNSSEC_DIGEST_SHA1] = "SHA1", - [DNSSEC_DIGEST_SHA256] = "SHA256", + /* Names as listed on https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ + [DNSSEC_DIGEST_SHA1] = "SHA-1", + [DNSSEC_DIGEST_SHA256] = "SHA-256", + [DNSSEC_DIGEST_GOST_R_34_11_94] = "GOST_R_34.11-94", + [DNSSEC_DIGEST_SHA384] = "SHA-384", }; -DEFINE_STRING_TABLE_LOOKUP(dnssec_digest, int); +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_digest, int, 255); diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index 632ee59994..37c4487332 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -33,17 +33,10 @@ typedef struct DnsResourceKey DnsResourceKey; typedef struct DnsResourceRecord DnsResourceRecord; typedef struct DnsTxtItem DnsTxtItem; -/* DNS record classes, see RFC 1035 */ -enum { - DNS_CLASS_IN = 0x01, - DNS_CLASS_ANY = 0xFF, - _DNS_CLASS_MAX, - _DNS_CLASS_INVALID = -1 -}; - /* DNSKEY RR flags */ -#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8) #define DNSKEY_FLAG_SEP (UINT16_C(1) << 0) +#define DNSKEY_FLAG_REVOKE (UINT16_C(1) << 7) +#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8) /* mDNS RR flags */ #define MDNS_RR_CACHE_FLUSH (UINT16_C(1) << 15) @@ -59,8 +52,11 @@ enum { DNSSEC_ALGORITHM_RSASHA1, DNSSEC_ALGORITHM_DSA_NSEC3_SHA1, DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, - DNSSEC_ALGORITHM_RSASHA256 = 8, /* RFC 5702 */ - DNSSEC_ALGORITHM_RSASHA512 = 10, /* RFC 5702 */ + DNSSEC_ALGORITHM_RSASHA256 = 8, /* RFC 5702 */ + DNSSEC_ALGORITHM_RSASHA512 = 10, /* RFC 5702 */ + DNSSEC_ALGORITHM_ECC_GOST = 12, /* RFC 5933 */ + DNSSEC_ALGORITHM_ECDSAP256SHA256 = 13, /* RFC 6605 */ + DNSSEC_ALGORITHM_ECDSAP384SHA384 = 14, /* RFC 6605 */ DNSSEC_ALGORITHM_INDIRECT = 252, DNSSEC_ALGORITHM_PRIVATEDNS, DNSSEC_ALGORITHM_PRIVATEOID, @@ -71,15 +67,23 @@ enum { * https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ enum { DNSSEC_DIGEST_SHA1 = 1, - DNSSEC_DIGEST_SHA256 = 2, + DNSSEC_DIGEST_SHA256 = 2, /* RFC 4509 */ + DNSSEC_DIGEST_GOST_R_34_11_94 = 3, /* RFC 5933 */ + DNSSEC_DIGEST_SHA384 = 4, /* RFC 6605 */ _DNSSEC_DIGEST_MAX_DEFINED }; +/* DNSSEC NSEC3 hash algorithms, see + * https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml */ +enum { + NSEC3_ALGORITHM_SHA1 = 1, + _NSEC3_ALGORITHM_MAX_DEFINED +}; + 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()! */ - bool cache_flush:1; }; /* Creates a temporary resource key. This is only useful to quickly @@ -104,16 +108,28 @@ 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; - size_t size; + size_t data_size; } generic, opt; struct { @@ -159,6 +175,7 @@ struct DnsResourceRecord { char *exchange; } mx; + /* https://tools.ietf.org/html/rfc1876 */ struct { uint8_t version; uint8_t size; @@ -169,14 +186,6 @@ struct DnsResourceRecord { uint32_t altitude; } loc; - struct { - uint16_t key_tag; - uint8_t algorithm; - uint8_t digest_type; - void *digest; - size_t digest_size; - } ds; - /* https://tools.ietf.org/html/rfc4255#section-3.1 */ struct { uint8_t algorithm; @@ -214,6 +223,15 @@ struct DnsResourceRecord { Bitmap *types; } nsec; + /* https://tools.ietf.org/html/rfc4034#section-5.1 */ + struct { + uint16_t key_tag; + uint8_t algorithm; + uint8_t digest_type; + void *digest; + size_t digest_size; + } ds; + struct { uint8_t algorithm; uint8_t flags; @@ -224,11 +242,20 @@ struct DnsResourceRecord { size_t next_hashed_name_size; Bitmap *types; } nsec3; + + /* https://tools.ietf.org/html/draft-ietf-dane-protocol-23 */ + struct { + uint8_t cert_usage; + uint8_t selector; + uint8_t matching_type; + void *data; + size_t data_size; + } tlsa; }; }; static inline const char* DNS_RESOURCE_KEY_NAME(const DnsResourceKey *key) { - if (_unlikely_(!key)) + if (!key) return NULL; if (key->_name) @@ -237,6 +264,27 @@ static inline const char* DNS_RESOURCE_KEY_NAME(const DnsResourceKey *key) { return (char*) key + sizeof(DnsResourceKey); } +static inline const void* DNS_RESOURCE_RECORD_RDATA(DnsResourceRecord *rr) { + if (!rr) + return NULL; + + if (!rr->wire_format) + return NULL; + + assert(rr->wire_format_rdata_offset <= rr->wire_format_size); + return (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset; +} + +static inline size_t DNS_RESOURCE_RECORD_RDATA_SIZE(DnsResourceRecord *rr) { + if (!rr) + return 0; + if (!rr->wire_format) + return 0; + + assert(rr->wire_format_rdata_offset <= rr->wire_format_size); + return rr->wire_format_size - rr->wire_format_rdata_offset; +} + DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name); DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname); int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name); @@ -245,7 +293,7 @@ DnsResourceKey* dns_resource_key_ref(DnsResourceKey *key); DnsResourceKey* dns_resource_key_unref(DnsResourceKey *key); bool dns_resource_key_is_address(const DnsResourceKey *key); int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b); -int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain); +int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain); int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain); int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa); int dns_resource_key_to_string(const DnsResourceKey *key, char **ret); @@ -255,6 +303,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); @@ -262,21 +312,24 @@ DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr); int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name); int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name); int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b); -int dns_resource_record_to_string(const DnsResourceRecord *rr, char **ret); +const char* dns_resource_record_to_string(DnsResourceRecord *rr); 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); -const char *dns_class_to_string(uint16_t type); -int dns_class_from_string(const char *name, uint16_t *class); - extern const struct hash_ops dns_resource_key_hash_ops; +extern const struct hash_ops dns_resource_record_hash_ops; -const char* dnssec_algorithm_to_string(int i) _const_; +int dnssec_algorithm_to_string_alloc(int i, char **ret); int dnssec_algorithm_from_string(const char *s) _pure_; -const char *dnssec_digest_to_string(int i) _const_; +int dnssec_digest_to_string_alloc(int i, char **ret); int dnssec_digest_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 61bca04b94..8f3be4b991 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -57,6 +57,20 @@ int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int s->family = family; s->resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC; + if (protocol == DNS_PROTOCOL_DNS) { + /* Copy DNSSEC mode from the link if it is set there, + * otherwise take the manager's DNSSEC mode. Note that + * we copy this only at scope creation time, and do + * not update it from the on, even if the setting + * changes. */ + + if (l) + s->dnssec_mode = link_get_dnssec_mode(l); + else + s->dnssec_mode = manager_get_dnssec_mode(m); + } else + s->dnssec_mode = DNSSEC_NO; + LIST_PREPEND(scopes, m->dns_scopes, s); dns_scope_llmnr_membership(s, true); @@ -81,7 +95,8 @@ static void dns_scope_abort_transactions(DnsScope *s) { * freed while we still look at it */ t->block_gc++; - dns_transaction_complete(t, DNS_TRANSACTION_ABORTED); + if (DNS_TRANSACTION_IS_LIVE(t->state)) + dns_transaction_complete(t, DNS_TRANSACTION_ABORTED); t->block_gc--; dns_transaction_free(t); @@ -161,17 +176,15 @@ void dns_scope_packet_lost(DnsScope *s, usec_t usec) { s->resend_timeout = MIN(s->resend_timeout * 2, MULTICAST_RESEND_TIMEOUT_MAX_USEC); } -static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { +static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) { union in_addr_union addr; int ifindex = 0, r; int family; uint32_t mtu; - size_t saved_size = 0; assert(s); assert(p); assert(p->protocol == s->protocol); - assert((s->protocol == DNS_PROTOCOL_DNS) != (fd < 0)); if (s->link) { mtu = s->link->mtu; @@ -180,30 +193,13 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket mtu = manager_find_mtu(s->manager); switch (s->protocol) { + case DNS_PROTOCOL_DNS: - assert(server); + assert(fd >= 0); if (DNS_PACKET_QDCOUNT(p) > 1) return -EOPNOTSUPP; - if (server->possible_features >= DNS_SERVER_FEATURE_LEVEL_EDNS0) { - bool edns_do; - size_t packet_size; - - edns_do = server->possible_features >= DNS_SERVER_FEATURE_LEVEL_DO; - - if (server->possible_features >= DNS_SERVER_FEATURE_LEVEL_LARGE) - packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX; - else - packet_size = server->received_udp_packet_max; - - r = dns_packet_append_opt_rr(p, packet_size, edns_do, &saved_size); - if (r < 0) - return r; - - DNS_PACKET_HEADER(p)->arcount = htobe16(be16toh(DNS_PACKET_HEADER(p)->arcount) + 1); - } - if (p->size > DNS_PACKET_UNICAST_SIZE_MAX) return -EMSGSIZE; @@ -214,15 +210,11 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket if (r < 0) return r; - if (saved_size > 0) { - dns_packet_truncate(p, saved_size); - - DNS_PACKET_HEADER(p)->arcount = htobe16(be16toh(DNS_PACKET_HEADER(p)->arcount) - 1); - } - break; case DNS_PROTOCOL_LLMNR: + assert(fd < 0); + if (DNS_PACKET_QDCOUNT(p) > 1) return -EOPNOTSUPP; @@ -249,6 +241,8 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket break; case DNS_PROTOCOL_MDNS: + assert(fd < 0); + if (!ratelimit_test(&s->ratelimit)) return -EBUSY; @@ -278,13 +272,13 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket return 1; } -int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { +int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p) { int r; assert(s); assert(p); assert(p->protocol == s->protocol); - assert((s->protocol == DNS_PROTOCOL_DNS) != (fd < 0)); + assert((s->protocol == DNS_PROTOCOL_DNS) == (fd >= 0)); do { /* If there are multiple linked packets, set the TC bit in all but the last of them */ @@ -293,18 +287,24 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { dns_packet_set_flags(p, true, true); } - r = dns_scope_emit_one(s, fd, server, p); + r = dns_scope_emit_one(s, fd, p); if (r < 0) return r; p = p->more; - } while(p); + } while (p); return 0; } -static int dns_scope_socket(DnsScope *s, int type, int family, const union in_addr_union *address, uint16_t port, DnsServer **server) { - DnsServer *srv = NULL; +static int dns_scope_socket( + DnsScope *s, + int type, + int family, + const union in_addr_union *address, + DnsServer *server, + uint16_t port) { + _cleanup_close_ int fd = -1; union sockaddr_union sa = {}; socklen_t salen; @@ -312,31 +312,27 @@ static int dns_scope_socket(DnsScope *s, int type, int family, const union in_ad int ret, r; assert(s); - assert((family == AF_UNSPEC) == !address); - - if (family == AF_UNSPEC) { - srv = dns_scope_get_dns_server(s); - if (!srv) - return -ESRCH; - - srv->possible_features = dns_server_possible_features(srv); - if (type == SOCK_DGRAM && srv->possible_features < DNS_SERVER_FEATURE_LEVEL_UDP) - return -EAGAIN; + if (server) { + assert(family == AF_UNSPEC); + assert(!address); - sa.sa.sa_family = srv->family; - if (srv->family == AF_INET) { + sa.sa.sa_family = server->family; + if (server->family == AF_INET) { sa.in.sin_port = htobe16(port); - sa.in.sin_addr = srv->address.in; + sa.in.sin_addr = server->address.in; salen = sizeof(sa.in); - } else if (srv->family == AF_INET6) { + } else if (server->family == AF_INET6) { sa.in6.sin6_port = htobe16(port); - sa.in6.sin6_addr = srv->address.in6; + sa.in6.sin6_addr = server->address.in6; sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0; salen = sizeof(sa.in6); } else return -EAFNOSUPPORT; } else { + assert(family != AF_UNSPEC); + assert(address); + sa.sa.sa_family = family; if (family == AF_INET) { @@ -394,21 +390,18 @@ static int dns_scope_socket(DnsScope *s, int type, int family, const union in_ad if (r < 0 && errno != EINPROGRESS) return -errno; - if (server) - *server = srv; - ret = fd; fd = -1; return ret; } -int dns_scope_udp_dns_socket(DnsScope *s, DnsServer **server) { - return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, 53, server); +int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port) { + return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, server, port); } -int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port, DnsServer **server) { - return dns_scope_socket(s, SOCK_STREAM, family, address, port, server); +int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port) { + return dns_scope_socket(s, SOCK_STREAM, family, address, server, port); } DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) { @@ -496,7 +489,9 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co } } -int dns_scope_good_key(DnsScope *s, DnsResourceKey *key) { +bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key) { + int key_family; + assert(s); assert(key); @@ -504,9 +499,12 @@ int dns_scope_good_key(DnsScope *s, DnsResourceKey *key) { * this scope. Note that this call assumes as fully qualified * name, i.e. the search suffixes already appended. */ + if (key->class != DNS_CLASS_IN) + return false; + if (s->protocol == DNS_PROTOCOL_DNS) { - /* On classic DNS, lookin up non-address RRs is always + /* On classic DNS, looking up non-address RRs is always * fine. (Specifically, we want to permit looking up * DNSKEY and DS records on the root and top-level * domains.) */ @@ -525,13 +523,11 @@ int dns_scope_good_key(DnsScope *s, DnsResourceKey *key) { /* On mDNS and LLMNR, send A and AAAA queries only on the * respective scopes */ - if (s->family == AF_INET && key->class == DNS_CLASS_IN && key->type == DNS_TYPE_AAAA) - return false; + key_family = dns_type_to_af(key->type); + if (key_family < 0) + return true; - if (s->family == AF_INET6 && key->class == DNS_CLASS_IN && key->type == DNS_TYPE_A) - return false; - - return true; + return key_family == s->family; } static int dns_scope_multicast_membership(DnsScope *s, bool b, struct in_addr in, struct in6_addr in6) { @@ -789,7 +785,7 @@ DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, /* Refuse reusing transactions that completed based on cached * data instead of a real packet, if that's requested. */ if (!cache_ok && - IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_FAILURE) && + IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_RCODE_FAILURE) && t->answer_source != DNS_TRANSACTION_NETWORK) return NULL; @@ -867,7 +863,7 @@ static int on_conflict_dispatch(sd_event_source *es, usec_t usec, void *userdata return 0; } - r = dns_scope_emit(scope, -1, NULL, p); + r = dns_scope_emit_udp(scope, -1, p); if (r < 0) log_debug_errno(r, "Failed to send conflict packet: %m"); } @@ -916,6 +912,8 @@ int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr) { if (r < 0) return log_debug_errno(r, "Failed to add conflict dispatch event: %m"); + (void) sd_event_source_set_description(scope->conflict_event_source, "scope-conflict"); + return 0; } @@ -1019,3 +1017,14 @@ bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name) { return dns_name_is_single_label(name); } + +bool dns_scope_network_good(DnsScope *s) { + /* Checks whether the network is in good state for lookups on this scope. For mDNS/LLMNR/Classic DNS scopes + * bound to links this is easy, as they don't even exist if the link isn't in a suitable state. For the global + * DNS scope we check whether there are any links that are up and have an address. */ + + if (s->link) + return true; + + return manager_routable(s->manager, AF_UNSPEC); +} diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index 2fc2e07deb..05b8d66de0 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -82,12 +82,12 @@ DnsScope* dns_scope_free(DnsScope *s); void dns_scope_packet_received(DnsScope *s, usec_t rtt); void dns_scope_packet_lost(DnsScope *s, usec_t usec); -int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p); -int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port, DnsServer **server); -int dns_scope_udp_dns_socket(DnsScope *s, DnsServer **server); +int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p); +int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port); +int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port); DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain); -int dns_scope_good_key(DnsScope *s, DnsResourceKey *key); +bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key); DnsServer *dns_scope_get_dns_server(DnsScope *s); void dns_scope_next_dns_server(DnsScope *s); @@ -107,3 +107,5 @@ void dns_scope_dump(DnsScope *s, FILE *f); DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s); bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name); + +bool dns_scope_network_good(DnsScope *s); diff --git a/src/resolve/resolved-dns-search-domain.c b/src/resolve/resolved-dns-search-domain.c index f9d966abb1..356c05b9a4 100644 --- a/src/resolve/resolved-dns-search-domain.c +++ b/src/resolve/resolved-dns-search-domain.c @@ -42,9 +42,6 @@ int dns_search_domain_new( if (r < 0) return r; - if (dns_name_is_root(normalized)) - return -EINVAL; - if (l) { if (l->n_search_domains >= LINK_SEARCH_DOMAINS_MAX) return -E2BIG; diff --git a/src/resolve/resolved-dns-search-domain.h b/src/resolve/resolved-dns-search-domain.h index 2e0af31dda..c1903b334f 100644 --- a/src/resolve/resolved-dns-search-domain.h +++ b/src/resolve/resolved-dns-search-domain.h @@ -44,6 +44,7 @@ struct DnsSearchDomain { char *name; bool marked:1; + bool route_only:1; bool linked:1; LIST_FIELDS(DnsSearchDomain, domains); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index d565f99c09..43ec92f4f0 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -19,6 +19,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <sd-messages.h> + #include "alloc-util.h" #include "resolved-dns-server.h" #include "resolved-resolv-conf.h" @@ -68,8 +70,8 @@ int dns_server_new( s->n_ref = 1; s->manager = m; - s->verified_features = _DNS_SERVER_FEATURE_LEVEL_INVALID; - s->possible_features = DNS_SERVER_FEATURE_LEVEL_BEST; + s->verified_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; s->features_grace_period_usec = DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC; s->received_udp_packet_max = DNS_PACKET_UNICAST_SIZE_MAX; s->type = type; @@ -135,6 +137,7 @@ DnsServer* dns_server_unref(DnsServer *s) { if (s->n_ref > 0) return NULL; + free(s->server_string); free(s); return NULL; } @@ -224,43 +227,75 @@ void dns_server_move_back_and_unmark(DnsServer *s) { } } -void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, usec_t rtt, size_t size) { +static void dns_server_verified(DnsServer *s, DnsServerFeatureLevel level) { assert(s); - if (features == DNS_SERVER_FEATURE_LEVEL_LARGE) { - /* even if we successfully receive a reply to a request announcing - support for large packets, that does not mean we can necessarily - receive large packets. */ - if (s->verified_features < DNS_SERVER_FEATURE_LEVEL_LARGE - 1) { - s->verified_features = DNS_SERVER_FEATURE_LEVEL_LARGE - 1; - assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0); - } - } else if (s->verified_features < features) { - s->verified_features = features; - assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0); + if (s->verified_feature_level > level) + return; + + if (s->verified_feature_level != level) { + log_debug("Verified we get a response at feature level %s from DNS server %s.", + dns_server_feature_level_to_string(level), + dns_server_string(s)); + s->verified_feature_level = level; } - if (s->possible_features == features) - s->n_failed_attempts = 0; + assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0); +} + +void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t rtt, size_t size) { + assert(s); + + if (protocol == IPPROTO_UDP) { + if (s->possible_feature_level == level) + s->n_failed_udp = 0; + + /* If the RRSIG data is missing, then we can only validate EDNS0 at max */ + if (s->packet_rrsig_missing && level >= DNS_SERVER_FEATURE_LEVEL_DO) + level = DNS_SERVER_FEATURE_LEVEL_DO - 1; + + /* If the OPT RR got lost, then we can only validate UDP at max */ + if (s->packet_bad_opt && level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) + level = DNS_SERVER_FEATURE_LEVEL_EDNS0 - 1; + + /* Even if we successfully receive a reply to a request announcing support for large packets, + that does not mean we can necessarily receive large packets. */ + if (level == DNS_SERVER_FEATURE_LEVEL_LARGE) + level = DNS_SERVER_FEATURE_LEVEL_LARGE - 1; + + } else if (protocol == IPPROTO_TCP) { + + if (s->possible_feature_level == level) + s->n_failed_tcp = 0; + + /* Successful TCP connections are only useful to verify the TCP feature level. */ + level = DNS_SERVER_FEATURE_LEVEL_TCP; + } + + dns_server_verified(s, level); /* Remember the size of the largest UDP packet we received from a server, we know that we can always announce support for packets with at least this size. */ - if (s->received_udp_packet_max < size) + if (protocol == IPPROTO_UDP && s->received_udp_packet_max < size) s->received_udp_packet_max = size; if (s->max_rtt < rtt) { s->max_rtt = rtt; - s->resend_timeout = MIN(MAX(DNS_TIMEOUT_MIN_USEC, s->max_rtt * 2), DNS_TIMEOUT_MAX_USEC); + s->resend_timeout = CLAMP(s->max_rtt * 2, DNS_TIMEOUT_MIN_USEC, DNS_TIMEOUT_MAX_USEC); } } -void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t usec) { +void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec) { assert(s); assert(s->manager); - if (s->possible_features == features) - s->n_failed_attempts ++; + if (s->possible_feature_level == level) { + if (protocol == IPPROTO_UDP) + s->n_failed_udp ++; + else if (protocol == IPPROTO_TCP) + s->n_failed_tcp ++; + } if (s->resend_timeout > usec) return; @@ -268,14 +303,52 @@ void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC); } -void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel features) { +void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level) { assert(s); - assert(s->manager); - if (s->possible_features != features) + /* Invoked whenever we get a FORMERR, SERVFAIL or NOTIMP rcode from a server. */ + + if (s->possible_feature_level != level) + return; + + s->packet_failed = true; +} + +void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level) { + assert(s); + + /* Invoked whenever we get a packet with TC bit set. */ + + if (s->possible_feature_level != level) + return; + + s->packet_truncated = true; +} + +void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level) { + assert(s); + + if (level < DNS_SERVER_FEATURE_LEVEL_DO) + return; + + /* If the RRSIG RRs are missing, we have to downgrade what we previously verified */ + if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) + s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_DO-1; + + s->packet_rrsig_missing = true; +} + +void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level) { + assert(s); + + if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0) return; - s->n_failed_attempts = (unsigned) -1; + /* 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) { @@ -297,35 +370,199 @@ static bool dns_server_grace_period_expired(DnsServer *s) { return true; } -DnsServerFeatureLevel dns_server_possible_features(DnsServer *s) { +static void dns_server_reset_counters(DnsServer *s) { assert(s); - if (s->possible_features != DNS_SERVER_FEATURE_LEVEL_BEST && + s->n_failed_udp = 0; + s->n_failed_tcp = 0; + s->packet_failed = false; + s->packet_truncated = false; + s->verified_usec = 0; + + /* Note that we do not reset s->packet_bad_opt and s->packet_rrsig_missing here. We reset them only when the + * grace period ends, but not when lowering the possible feature level, as a lower level feature level should + * not make RRSIGs appear or OPT appear, but rather make them disappear. If the reappear anyway, then that's + * indication for a differently broken OPT/RRSIG implementation, and we really don't want to support that + * either. + * + * This is particularly important to deal with certain Belkin routers which break OPT for certain lookups (A), + * but pass traffic through for others (AAAA). If we detect the broken behaviour on one lookup we should not + * reenable it for another, because we cannot validate things anyway, given that the RRSIG/OPT data will be + * incomplete. */ +} + +DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { + assert(s); + + if (s->possible_feature_level != DNS_SERVER_FEATURE_LEVEL_BEST && dns_server_grace_period_expired(s)) { - _cleanup_free_ char *ip = NULL; - - s->possible_features = DNS_SERVER_FEATURE_LEVEL_BEST; - s->n_failed_attempts = 0; - s->verified_usec = 0; - - in_addr_to_string(s->family, &s->address, &ip); - log_info("Grace period over, resuming full feature set for DNS server %s", strna(ip)); - } else if (s->possible_features <= s->verified_features) - s->possible_features = s->verified_features; - else if (s->n_failed_attempts >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && - s->possible_features > DNS_SERVER_FEATURE_LEVEL_WORST) { - _cleanup_free_ char *ip = NULL; - - s->possible_features --; - s->n_failed_attempts = 0; - s->verified_usec = 0; - - in_addr_to_string(s->family, &s->address, &ip); - log_warning("Using degraded feature set (%s) for DNS server %s", - dns_server_feature_level_to_string(s->possible_features), strna(ip)); + + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; + + dns_server_reset_counters(s); + + s->packet_bad_opt = false; + s->packet_rrsig_missing = false; + + log_info("Grace period over, resuming full feature set (%s) for DNS server %s.", + dns_server_feature_level_to_string(s->possible_feature_level), + dns_server_string(s)); + + } else if (s->possible_feature_level <= s->verified_feature_level) + s->possible_feature_level = s->verified_feature_level; + else { + DnsServerFeatureLevel p = s->possible_feature_level; + + if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && + s->possible_feature_level == DNS_SERVER_FEATURE_LEVEL_TCP) { + + /* We are at the TCP (lowest) level, and we tried a couple of TCP connections, and it didn't + * work. Upgrade back to UDP again. */ + log_debug("Reached maximum number of failed TCP connection attempts, trying UDP again..."); + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP; + + } else if (s->packet_bad_opt && + s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) { + + /* A reply to one of our EDNS0 queries didn't carry a valid OPT RR, then downgrade to below + * EDNS0 levels. After all, some records generate different responses with and without OPT RR + * in the request. Example: + * https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */ + + log_debug("Server doesn't support EDNS(0) properly, downgrading feature level..."); + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP; + + } else if (s->packet_rrsig_missing && + s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) { + + /* RRSIG data was missing on a EDNS0 packet with DO bit set. This means the server doesn't + * augment responses with DNSSEC RRs. If so, let's better not ask the server for it anymore, + * after all some servers generate different replies depending if an OPT RR is in the query or + * not. */ + + log_debug("Detected server responses lack RRSIG records, downgrading feature level..."); + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0; + + } else if (s->n_failed_udp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && + s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_UDP) { + + /* We lost too many UDP packets in a row, and are on a feature level of UDP or higher. If the + * packets are lost, maybe the server cannot parse them, hence downgrading sounds like a good + * idea. We might downgrade all the way down to TCP this way. */ + + 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.", + dns_server_feature_level_to_string(s->possible_feature_level), + dns_server_string(s)); + } } - return s->possible_features; + return s->possible_feature_level; +} + +int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level) { + size_t packet_size; + bool edns_do; + int r; + + assert(server); + assert(packet); + assert(packet->protocol == DNS_PROTOCOL_DNS); + + /* Fix the OPT field in the packet to match our current feature level. */ + + r = dns_packet_truncate_opt(packet); + if (r < 0) + return r; + + if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0) + return 0; + + edns_do = level >= DNS_SERVER_FEATURE_LEVEL_DO; + + if (level >= DNS_SERVER_FEATURE_LEVEL_LARGE) + packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX; + else + packet_size = server->received_udp_packet_max; + + return dns_packet_append_opt(packet, packet_size, edns_do, NULL); +} + +const char *dns_server_string(DnsServer *server) { + assert(server); + + if (!server->server_string) + (void) in_addr_to_string(server->family, &server->address, &server->server_string); + + return strna(server->server_string); +} + +bool dns_server_dnssec_supported(DnsServer *server) { + assert(server); + + /* Returns whether the server supports DNSSEC according to what we know about it */ + + if (server->possible_feature_level < DNS_SERVER_FEATURE_LEVEL_DO) + return false; + + if (server->packet_bad_opt) + return false; + + if (server->packet_rrsig_missing) + return false; + + /* DNSSEC servers need to support TCP properly (see RFC5966), if they don't, we assume DNSSEC is borked too */ + if (server->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS) + return false; + + return true; +} + +void dns_server_warn_downgrade(DnsServer *server) { + assert(server); + + if (server->warned_downgrade) + return; + + log_struct(LOG_NOTICE, + LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_DOWNGRADE), + LOG_MESSAGE("Server %s does not support DNSSEC, downgrading to non-DNSSEC mode.", dns_server_string(server)), + "DNS_SERVER=%s", dns_server_string(server), + "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(server->possible_feature_level), + NULL); + + server->warned_downgrade = true; } static void dns_server_hash_func(const void *p, struct siphash *state) { @@ -419,12 +656,10 @@ DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) { if (m->current_dns_server == s) return s; - if (s) { - _cleanup_free_ char *ip = NULL; - - in_addr_to_string(s->family, &s->address, &ip); - log_info("Switching to system DNS server %s.", strna(ip)); - } + if (s) + log_info("Switching to %s DNS server %s.", + dns_server_type_to_string(s->type), + dns_server_string(s)); dns_server_unref(m->current_dns_server); m->current_dns_server = dns_server_ref(s); @@ -442,7 +677,7 @@ DnsServer *manager_get_dns_server(Manager *m) { /* Try to read updates resolv.conf */ manager_read_resolv_conf(m); - /* If no DNS server was chose so far, pick the first one */ + /* If no DNS server was chosen so far, pick the first one */ if (!m->current_dns_server) manager_set_dns_server(m, m->dns_servers); @@ -490,6 +725,13 @@ void manager_next_dns_server(Manager *m) { manager_set_dns_server(m, m->dns_servers); } +static const char* const dns_server_type_table[_DNS_SERVER_TYPE_MAX] = { + [DNS_SERVER_SYSTEM] = "system", + [DNS_SERVER_FALLBACK] = "fallback", + [DNS_SERVER_LINK] = "link", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_server_type, DnsServerType); + static const char* const dns_server_feature_level_table[_DNS_SERVER_FEATURE_LEVEL_MAX] = { [DNS_SERVER_FEATURE_LEVEL_TCP] = "TCP", [DNS_SERVER_FEATURE_LEVEL_UDP] = "UDP", diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index b07fc3af3d..7a885655a4 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -30,6 +30,10 @@ typedef enum DnsServerType { DNS_SERVER_FALLBACK, DNS_SERVER_LINK, } DnsServerType; +#define _DNS_SERVER_TYPE_MAX (DNS_SERVER_LINK + 1) + +const char* dns_server_type_to_string(DnsServerType i) _const_; +DnsServerType dns_server_type_from_string(const char *s) _pure_; typedef enum DnsServerFeatureLevel { DNS_SERVER_FEATURE_LEVEL_TCP, @@ -61,18 +65,33 @@ struct DnsServer { int family; union in_addr_union address; - bool marked:1; + char *server_string; usec_t resend_timeout; usec_t max_rtt; - DnsServerFeatureLevel verified_features; - DnsServerFeatureLevel possible_features; + DnsServerFeatureLevel verified_feature_level; + DnsServerFeatureLevel possible_feature_level; + size_t received_udp_packet_max; - unsigned n_failed_attempts; + + unsigned n_failed_udp; + unsigned n_failed_tcp; + + bool packet_failed:1; + bool packet_truncated:1; + bool packet_bad_opt:1; + bool packet_rrsig_missing:1; + usec_t verified_usec; usec_t features_grace_period_usec; + /* Whether we already warned about downgrading to non-DNSSEC mode for this server */ + bool warned_downgrade:1; + + /* Used when GC'ing old DNS servers when configuration changes. */ + bool marked:1; + /* If linked is set, then this server appears in the servers linked list */ bool linked:1; LIST_FIELDS(DnsServer, servers); @@ -92,9 +111,22 @@ DnsServer* dns_server_unref(DnsServer *s); void dns_server_unlink(DnsServer *s); void dns_server_move_back_and_unmark(DnsServer *s); -void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, usec_t rtt, size_t size); -void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t usec); -void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel features); +void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t rtt, size_t size); +void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec); +void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level); +void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level); +void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level); +void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level); + +DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s); + +int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level); + +const char *dns_server_string(DnsServer *server); + +bool dns_server_dnssec_supported(DnsServer *server); + +void dns_server_warn_downgrade(DnsServer *server); DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr); @@ -110,6 +142,4 @@ void manager_next_dns_server(Manager *m); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref); -DnsServerFeatureLevel dns_server_possible_features(DnsServer *s); - extern const struct hash_ops dns_server_hash_ops; diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index 1c501182fb..b72e6cc06f 100644 --- a/src/resolve/resolved-dns-stream.c +++ b/src/resolve/resolved-dns-stream.c @@ -347,7 +347,6 @@ DnsStream *dns_stream_free(DnsStream *s) { DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free); int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { - static const int one = 1; _cleanup_(dns_stream_freep) DnsStream *s = NULL; int r; @@ -364,14 +363,12 @@ int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { s->fd = -1; s->protocol = protocol; - r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); - if (r < 0) - return -errno; - r = sd_event_add_io(m->event, &s->io_event_source, fd, EPOLLIN, on_stream_io, s); if (r < 0) return r; + (void) sd_event_source_set_description(s->io_event_source, "dns-stream-io"); + r = sd_event_add_time( m->event, &s->timeout_event_source, @@ -381,6 +378,8 @@ int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { if (r < 0) return r; + (void) sd_event_source_set_description(s->timeout_event_source, "dns-stream-timeout"); + LIST_PREPEND(streams, m->dns_streams, s); s->manager = m; s->fd = fd; diff --git a/src/resolve/resolved-dns-synthesize.c b/src/resolve/resolved-dns-synthesize.c new file mode 100644 index 0000000000..f4a43dee8c --- /dev/null +++ b/src/resolve/resolved-dns-synthesize.c @@ -0,0 +1,413 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 "hostname-util.h" +#include "local-addresses.h" +#include "resolved-dns-synthesize.h" + +int dns_synthesize_ifindex(int ifindex) { + + /* When the caller asked for resolving on a specific + * interface, we synthesize the answer for that + * interface. However, if nothing specific was claimed and we + * only return localhost RRs, we synthesize the answer for + * localhost. */ + + if (ifindex > 0) + return ifindex; + + return LOOPBACK_IFINDEX; +} + +int dns_synthesize_family(uint64_t flags) { + + /* Picks an address family depending on set flags. This is + * purely for synthesized answers, where the family we return + * for the reply should match what was requested in the + * question, even though we are synthesizing the answer + * here. */ + + if (!(flags & SD_RESOLVED_DNS)) { + if (flags & (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_MDNS_IPV4)) + return AF_INET; + if (flags & (SD_RESOLVED_LLMNR_IPV6|SD_RESOLVED_MDNS_IPV6)) + return AF_INET6; + } + + return AF_UNSPEC; +} + +DnsProtocol dns_synthesize_protocol(uint64_t flags) { + + /* Similar as dns_synthesize_family() but does this for the + * protocol. If resolving via DNS was requested, we claim it + * was DNS. Similar, if nothing specific was + * requested. However, if only resolving via LLMNR was + * requested we return that. */ + + if (flags & SD_RESOLVED_DNS) + return DNS_PROTOCOL_DNS; + if (flags & SD_RESOLVED_LLMNR) + return DNS_PROTOCOL_LLMNR; + if (flags & SD_RESOLVED_MDNS) + return DNS_PROTOCOL_MDNS; + + return DNS_PROTOCOL_DNS; +} + +static int synthesize_localhost_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { + int r; + + assert(m); + assert(key); + assert(answer); + + r = dns_answer_reserve(answer, 2); + if (r < 0) + return r; + + if (IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_ANY)) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, DNS_RESOURCE_KEY_NAME(key)); + if (!rr) + return -ENOMEM; + + rr->a.in_addr.s_addr = htobe32(INADDR_LOOPBACK); + + r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + if (IN_SET(key->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, DNS_RESOURCE_KEY_NAME(key)); + if (!rr) + return -ENOMEM; + + rr->aaaa.in6_addr = in6addr_loopback; + + r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, int ifindex, DnsAnswerFlags flags) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, from); + if (!rr) + return -ENOMEM; + + rr->ptr.name = strdup(to); + if (!rr->ptr.name) + return -ENOMEM; + + return dns_answer_add(*answer, rr, ifindex, flags); +} + +static int synthesize_localhost_ptr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { + int r; + + assert(m); + assert(key); + assert(answer); + + if (IN_SET(key->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) { + r = dns_answer_reserve(answer, 1); + if (r < 0) + return r; + + r = answer_add_ptr(answer, DNS_RESOURCE_KEY_NAME(key), "localhost", dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int answer_add_addresses_rr( + DnsAnswer **answer, + const char *name, + struct local_address *addresses, + unsigned n_addresses) { + + unsigned j; + int r; + + assert(answer); + assert(name); + + r = dns_answer_reserve(answer, n_addresses); + if (r < 0) + return r; + + for (j = 0; j < n_addresses; j++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + r = dns_resource_record_new_address(&rr, addresses[j].family, &addresses[j].address, name); + if (r < 0) + return r; + + r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int answer_add_addresses_ptr( + DnsAnswer **answer, + const char *name, + struct local_address *addresses, + unsigned n_addresses, + int af, const union in_addr_union *match) { + + unsigned j; + int r; + + assert(answer); + assert(name); + + for (j = 0; j < n_addresses; j++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + if (af != AF_UNSPEC) { + + if (addresses[j].family != af) + continue; + + if (match && !in_addr_equal(af, match, &addresses[j].address)) + continue; + } + + r = dns_answer_reserve(answer, 1); + if (r < 0) + return r; + + r = dns_resource_record_new_reverse(&rr, addresses[j].family, &addresses[j].address, name); + if (r < 0) + return r; + + r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int synthesize_system_hostname_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n = 0, af; + + assert(m); + assert(key); + assert(answer); + + af = dns_type_to_af(key->type); + if (af >= 0) { + n = local_addresses(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + + if (n == 0) { + struct local_address buffer[2]; + + /* If we have no local addresses then use ::1 + * and 127.0.0.2 as local ones. */ + + if (af == AF_INET || af == AF_UNSPEC) + buffer[n++] = (struct local_address) { + .family = AF_INET, + .ifindex = dns_synthesize_ifindex(ifindex), + .address.in.s_addr = htobe32(0x7F000002), + }; + + if (af == AF_INET6 || af == AF_UNSPEC) + buffer[n++] = (struct local_address) { + .family = AF_INET6, + .ifindex = dns_synthesize_ifindex(ifindex), + .address.in6 = in6addr_loopback, + }; + + return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), buffer, n); + } + } + + return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n); +} + +static int synthesize_system_hostname_ptr(Manager *m, int af, const union in_addr_union *address, int ifindex, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n, r; + + assert(m); + assert(address); + assert(answer); + + if (af == AF_INET && address->in.s_addr == htobe32(0x7F000002)) { + + /* Always map the IPv4 address 127.0.0.2 to the local + * hostname, in addition to "localhost": */ + + r = dns_answer_reserve(answer, 3); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->llmnr_hostname, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->mdns_hostname, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + return 0; + } + + n = local_addresses(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + + r = answer_add_addresses_ptr(answer, m->llmnr_hostname, addresses, n, af, address); + if (r < 0) + return r; + + return answer_add_addresses_ptr(answer, m->mdns_hostname, addresses, n, af, address); +} + +static int synthesize_gateway_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n = 0, af; + + assert(m); + assert(key); + assert(answer); + + af = dns_type_to_af(key->type); + if (af >= 0) { + n = local_gateways(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + } + + return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n); +} + +static int synthesize_gateway_ptr(Manager *m, int af, const union in_addr_union *address, int ifindex, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n; + + assert(m); + assert(address); + assert(answer); + + n = local_gateways(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + + return answer_add_addresses_ptr(answer, "gateway", addresses, n, af, address); +} + +int dns_synthesize_answer( + Manager *m, + DnsQuestion *q, + int ifindex, + DnsAnswer **ret) { + + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + DnsResourceKey *key; + bool found = false; + int r; + + assert(m); + assert(q); + + DNS_QUESTION_FOREACH(key, q) { + union in_addr_union address; + const char *name; + int af; + + if (key->class != DNS_CLASS_IN && + key->class != DNS_CLASS_ANY) + continue; + + name = DNS_RESOURCE_KEY_NAME(key); + + if (is_localhost(name)) { + + r = synthesize_localhost_rr(m, key, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize localhost RRs: %m"); + + } else if (manager_is_own_hostname(m, name)) { + + r = synthesize_system_hostname_rr(m, key, ifindex, &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(m, key, ifindex, &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(m, key, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize localhost PTR RRs: %m"); + + } else if (dns_name_address(name, &af, &address) > 0) { + + r = synthesize_system_hostname_ptr(m, af, &address, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize system hostname PTR RR: %m"); + + r = synthesize_gateway_ptr(m, af, &address, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize gateway hostname PTR RR: %m"); + } else + continue; + + found = true; + } + + r = found; + + if (ret) { + *ret = answer; + answer = NULL; + } + + return r; +} diff --git a/src/resolve/resolved-dns-synthesize.h b/src/resolve/resolved-dns-synthesize.h new file mode 100644 index 0000000000..5d829bb2e7 --- /dev/null +++ b/src/resolve/resolved-dns-synthesize.h @@ -0,0 +1,30 @@ +#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 "resolved-dns-answer.h" +#include "resolved-dns-question.h" +#include "resolved-manager.h" + +int dns_synthesize_ifindex(int ifindex); +int dns_synthesize_family(uint64_t flags); +DnsProtocol dns_synthesize_protocol(uint64_t flags); + +int dns_synthesize_answer(Manager *m, DnsQuestion *q, int ifindex, DnsAnswer **ret); diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index bcf6d5c810..501f13063e 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -19,9 +19,12 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <sd-messages.h> + #include "af-list.h" #include "alloc-util.h" #include "dns-domain.h" +#include "errno-list.h" #include "fd-util.h" #include "random-util.h" #include "resolved-dns-cache.h" @@ -29,6 +32,46 @@ #include "resolved-llmnr.h" #include "string-table.h" +#define TRANSACTIONS_MAX 4096 + +static void dns_transaction_reset_answer(DnsTransaction *t) { + assert(t); + + t->received = dns_packet_unref(t->received); + t->answer = dns_answer_unref(t->answer); + t->answer_rcode = 0; + t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; + t->answer_authenticated = false; + t->answer_nsec_ttl = (uint32_t) -1; + t->answer_errno = 0; +} + +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); + + t->stream = dns_stream_free(t->stream); + t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source); + t->dns_udp_fd = safe_close(t->dns_udp_fd); +} + +static void dns_transaction_stop_timeout(DnsTransaction *t) { + assert(t); + + t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); +} + DnsTransaction* dns_transaction_free(DnsTransaction *t) { DnsQueryCandidate *c; DnsZoneItem *i; @@ -37,18 +80,15 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { if (!t) return NULL; - sd_event_source_unref(t->timeout_event_source); - - dns_packet_unref(t->sent); - dns_packet_unref(t->received); + log_debug("Freeing transaction %" PRIu16 ".", t->id); - dns_answer_unref(t->answer); + dns_transaction_close_connection(t); + dns_transaction_stop_timeout(t); - sd_event_source_unref(t->dns_udp_event_source); - safe_close(t->dns_udp_fd); + dns_packet_unref(t->sent); + dns_transaction_reset_answer(t); dns_server_unref(t->server); - dns_stream_free(t->stream); if (t->scope) { hashmap_remove_value(t->scope->transactions_by_key, t->key, t); @@ -58,8 +98,6 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id)); } - dns_resource_key_unref(t->key); - while ((c = set_steal_first(t->notify_query_candidates))) set_remove(c->transactions, t); set_free(t->notify_query_candidates); @@ -72,13 +110,12 @@ 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); + dns_resource_key_unref(t->key); + free(t->key_string); free(t); return NULL; @@ -86,16 +123,36 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { DEFINE_TRIVIAL_CLEANUP_FUNC(DnsTransaction*, dns_transaction_free); -void dns_transaction_gc(DnsTransaction *t) { +bool dns_transaction_gc(DnsTransaction *t) { assert(t); if (t->block_gc > 0) - return; + return true; if (set_isempty(t->notify_query_candidates) && set_isempty(t->notify_zone_items) && - set_isempty(t->notify_transactions)) + set_isempty(t->notify_transactions)) { dns_transaction_free(t); + return false; + } + + return true; +} + +static uint16_t pick_new_id(Manager *m) { + uint16_t new_id; + + /* Find a fresh, unused transaction id. Note that this loop is bounded because there's a limit on the number of + * transactions, and it's much lower than the space of IDs. */ + + assert_cc(TRANSACTIONS_MAX < 0xFFFF); + + do + random_bytes(&new_id, sizeof(new_id)); + while (new_id == 0 || + hashmap_get(m->dns_transactions, UINT_TO_PTR(new_id))); + + return new_id; } int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) { @@ -107,13 +164,18 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) assert(key); /* Don't allow looking up invalid or pseudo RRs */ - if (IN_SET(key->type, DNS_TYPE_OPT, 0, DNS_TYPE_TSIG, DNS_TYPE_TKEY)) + if (!dns_type_is_valid_query(key->type)) return -EINVAL; + if (dns_type_is_obsolete(key->type)) + return -EOPNOTSUPP; /* We only support the IN class */ - if (key->class != DNS_CLASS_IN) + 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; @@ -128,14 +190,12 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) t->dns_udp_fd = -1; t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; - t->dnssec_result = _DNSSEC_RESULT_INVALID; + t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + t->answer_nsec_ttl = (uint32_t) -1; t->key = dns_resource_key_ref(key); + t->current_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; - /* Find a fresh, unused transaction id */ - do - random_bytes(&t->id, sizeof(t->id)); - while (t->id == 0 || - hashmap_get(s->manager->dns_transactions, UINT_TO_PTR(t->id))); + t->id = pick_new_id(s->manager); r = hashmap_put(s->manager->dns_transactions, UINT_TO_PTR(t->id), t); if (r < 0) { @@ -152,6 +212,8 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) LIST_PREPEND(transactions_by_scope, s->transactions, t); t->scope = s; + s->manager->n_transactions_total ++; + if (ret) *ret = t; @@ -160,14 +222,20 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) return 0; } -static void dns_transaction_stop(DnsTransaction *t) { +static void dns_transaction_shuffle_id(DnsTransaction *t) { + uint16_t new_id; assert(t); - t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); - t->stream = dns_stream_free(t->stream); + /* 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); - /* Note that we do not drop the UDP socket here, as we want to - * reuse it to repeat the interaction. */ + 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) { @@ -182,7 +250,9 @@ static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { in_addr_to_string(p->family, &p->sender, &pretty); - log_debug("Transaction on scope %s on %s/%s got tentative packet from %s", + log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s got tentative packet from %s.", + t->id, + dns_transaction_key_string(t), dns_protocol_to_string(t->scope->protocol), t->scope->link ? t->scope->link->name : "*", t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family), @@ -217,39 +287,146 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { DnsZoneItem *z; DnsTransaction *d; Iterator i; + const char *st; assert(t); assert(!DNS_TRANSACTION_IS_LIVE(state)); + if (state == DNS_TRANSACTION_DNSSEC_FAILED) + log_struct(LOG_NOTICE, + LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_FAILURE), + LOG_MESSAGE("DNSSEC validation failed for question %s: %s", dns_transaction_key_string(t), dnssec_result_to_string(t->answer_dnssec_result)), + "DNS_TRANSACTION=%" PRIu16, t->id, + "DNS_QUESTION=%s", dns_transaction_key_string(t), + "DNSSEC_RESULT=%s", dnssec_result_to_string(t->answer_dnssec_result), + "DNS_SERVER=%s", dns_server_string(t->server), + "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(t->server->possible_feature_level), + NULL); + /* Note that this call might invalidate the query. Callers * should hence not attempt to access the query or transaction * after calling this function. */ - log_debug("Transaction on scope %s on %s/%s now complete with <%s> from %s", + if (state == DNS_TRANSACTION_ERRNO) + st = errno_to_name(t->answer_errno); + else + st = dns_transaction_state_to_string(state); + + log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s now complete with <%s> from %s (%s).", + t->id, + dns_transaction_key_string(t), dns_protocol_to_string(t->scope->protocol), t->scope->link ? t->scope->link->name : "*", t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family), - dns_transaction_state_to_string(state), - t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source)); + st, + t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source), + t->answer_authenticated ? "authenticated" : "unsigned"); t->state = state; - dns_transaction_stop(t); + dns_transaction_close_connection(t); + dns_transaction_stop_timeout(t); /* Notify all queries that are interested, but make sure the * transaction isn't freed while we are still looking at it */ t->block_gc++; + SET_FOREACH(c, t->notify_query_candidates, i) dns_query_candidate_notify(c); SET_FOREACH(z, t->notify_zone_items, i) dns_zone_item_notify(z); - SET_FOREACH(d, t->notify_transactions, i) - dns_transaction_notify(d, t); - t->block_gc--; + if (!set_isempty(t->notify_transactions)) { + DnsTransaction **nt; + unsigned j, n = 0; + + /* We need to be careful when notifying other + * transactions, as that might destroy other + * transactions in our list. Hence, in order to be + * able to safely iterate through the list of + * transactions, take a GC lock on all of them + * first. Then, in a second loop, notify them, but + * first unlock that specific transaction. */ + + nt = newa(DnsTransaction*, set_size(t->notify_transactions)); + SET_FOREACH(d, t->notify_transactions, i) { + nt[n++] = d; + d->block_gc++; + } + + assert(n == set_size(t->notify_transactions)); + + for (j = 0; j < n; j++) { + if (set_contains(t->notify_transactions, nt[j])) + dns_transaction_notify(nt[j], t); + + nt[j]->block_gc--; + dns_transaction_gc(nt[j]); + } + } + + t->block_gc--; dns_transaction_gc(t); } +static int dns_transaction_pick_server(DnsTransaction *t) { + DnsServer *server; + + assert(t); + assert(t->scope->protocol == DNS_PROTOCOL_DNS); + + server = dns_scope_get_dns_server(t->scope); + if (!server) + return -ESRCH; + + t->current_feature_level = dns_server_possible_feature_level(server); + + if (server == t->server) + return 0; + + dns_server_unref(t->server); + t->server = dns_server_ref(server); + + return 1; +} + +static void dns_transaction_retry(DnsTransaction *t) { + int r; + + assert(t); + + log_debug("Retrying transaction %" PRIu16 ".", t->id); + + /* Before we try again, switch to a new server. */ + dns_scope_next_dns_server(t->scope); + + r = dns_transaction_go(t); + if (r < 0) { + t->answer_errno = -r; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); + } +} + +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; @@ -264,8 +441,26 @@ static int on_stream_complete(DnsStream *s, int error) { t->stream = dns_stream_free(t->stream); + if (ERRNO_IS_DISCONNECT(error)) { + usec_t usec; + + if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { + /* If the LLMNR/TCP connection failed, the host doesn't support LLMNR, and we cannot answer the + * question on this scope. */ + dns_transaction_complete(t, DNS_TRANSACTION_NOT_FOUND); + return 0; + } + + log_debug_errno(error, "Connection failure for DNS TCP stream: %m"); + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &usec) >= 0); + dns_server_packet_lost(t->server, IPPROTO_TCP, t->current_feature_level, usec - t->start_usec); + + dns_transaction_retry(t); + return 0; + } if (error != 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + t->answer_errno = error; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); return 0; } @@ -281,32 +476,46 @@ static int on_stream_complete(DnsStream *s, int error) { dns_transaction_process_reply(t, p); t->block_gc--; - /* If the response wasn't useful, then complete the transition now */ + /* If the response wasn't useful, then complete the transition + * now. After all, we are the worst feature set now with TCP + * sockets, and there's really no point in retrying. */ if (t->state == DNS_TRANSACTION_PENDING) dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + else + dns_transaction_gc(t); return 0; } static int dns_transaction_open_tcp(DnsTransaction *t) { - DnsServer *server = NULL; _cleanup_close_ int fd = -1; int r; assert(t); - if (t->stream) - return 0; + dns_transaction_close_connection(t); switch (t->scope->protocol) { + case DNS_PROTOCOL_DNS: - fd = dns_scope_tcp_socket(t->scope, AF_UNSPEC, NULL, 53, &server); + r = dns_transaction_pick_server(t); + if (r < 0) + return r; + + if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) + return -EOPNOTSUPP; + + r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); + if (r < 0) + return r; + + fd = dns_scope_socket_tcp(t->scope, AF_UNSPEC, NULL, t->server, 53); break; case DNS_PROTOCOL_LLMNR: /* When we already received a reply to this (but it was truncated), send to its sender address */ if (t->received) - fd = dns_scope_tcp_socket(t->scope, t->received->family, &t->received->sender, t->received->sender_port, NULL); + fd = dns_scope_socket_tcp(t->scope, t->received->family, &t->received->sender, NULL, t->received->sender_port); else { union in_addr_union address; int family = AF_UNSPEC; @@ -323,7 +532,7 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { if (family != t->scope->family) return -ESRCH; - fd = dns_scope_tcp_socket(t->scope, family, &address, LLMNR_PORT, NULL); + fd = dns_scope_socket_tcp(t->scope, family, &address, NULL, LLMNR_PORT); } break; @@ -338,7 +547,6 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { r = dns_stream_new(t->scope->manager, &t->stream, t->scope->protocol, fd); if (r < 0) return r; - fd = -1; r = dns_stream_write_packet(t->stream, t->sent); @@ -347,11 +555,6 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { return r; } - dns_server_unref(t->server); - t->server = dns_server_ref(server); - t->received = dns_packet_unref(t->received); - t->answer = dns_answer_unref(t->answer); - t->answer_rcode = 0; t->stream->complete = on_stream_complete; t->stream->transaction = t; @@ -361,22 +564,14 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { if (t->scope->link) t->stream->ifindex = t->scope->link->ifindex; - return 0; -} + dns_transaction_reset_answer(t); -static void dns_transaction_next_dns_server(DnsTransaction *t) { - assert(t); - - t->server = dns_server_unref(t->server); - t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source); - t->dns_udp_fd = safe_close(t->dns_udp_fd); + t->tried_stream = true; - dns_scope_next_dns_server(t->scope); + return 0; } static void dns_transaction_cache_answer(DnsTransaction *t) { - unsigned n_cache; - assert(t); /* For mDNS we cache whenever we get the packet, rather than @@ -391,54 +586,199 @@ static void dns_transaction_cache_answer(DnsTransaction *t) { if (!DNS_PACKET_SHALL_CACHE(t->received)) return; - /* According to RFC 4795, section 2.9. only the RRs from the - * answer section shall be cached. However, if we know the - * message is authenticated, we might as well cache - * everything. */ - if (t->answer_authenticated) - n_cache = dns_answer_size(t->answer); - else - n_cache = DNS_PACKET_ANCOUNT(t->received); - dns_cache_put(&t->scope->cache, t->key, t->answer_rcode, t->answer, - n_cache, t->answer_authenticated, + t->answer_nsec_ttl, 0, t->received->family, &t->received->sender); } +static bool dns_transaction_dnssec_is_live(DnsTransaction *t) { + DnsTransaction *dt; + Iterator i; + + assert(t); + + SET_FOREACH(dt, t->dnssec_transactions, i) + if (DNS_TRANSACTION_IS_LIVE(dt->state)) + return true; + + 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 (!set_isempty(t->dnssec_transactions)) + r = dns_transaction_dnssec_ready(t); + if (r < 0) + goto fail; + 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) + goto fail; + if (r > 0) /* Transaction got restarted... */ return; /* All our auxiliary DNSSEC transactions are complete now. Try * to validate our RRset now. */ r = dns_transaction_validate_dnssec(t); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + if (r == -EBADMSG) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + if (r < 0) + goto fail; + + if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER && + t->scope->dnssec_mode == DNSSEC_YES) { + /* We are not in automatic downgrade mode, and the + * server is bad, refuse operation. */ + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); return; } - if (!IN_SET(t->dnssec_result, _DNSSEC_RESULT_INVALID, DNSSEC_VALIDATED, DNSSEC_NO_SIGNATURE /* FOR NOW! */)) { + if (!IN_SET(t->answer_dnssec_result, + _DNSSEC_RESULT_INVALID, /* No DNSSEC validation enabled */ + DNSSEC_VALIDATED, /* Answer is signed and validated successfully */ + DNSSEC_UNSIGNED, /* Answer is right-fully unsigned */ + DNSSEC_INCOMPATIBLE_SERVER)) { /* Server does not do DNSSEC (Yay, we are downgrade attack vulnerable!) */ dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); return; } + if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER) + dns_server_warn_downgrade(t->server); + dns_transaction_cache_answer(t); if (t->answer_rcode == DNS_RCODE_SUCCESS) dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); else - dns_transaction_complete(t, DNS_TRANSACTION_FAILURE); + dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); + + return; + +fail: + t->answer_errno = -r; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); +} + +static int dns_transaction_has_positive_answer(DnsTransaction *t, DnsAnswerFlags *flags) { + int r; + + assert(t); + + /* Checks whether the answer is positive, i.e. either a direct + * answer to the question, or a CNAME/DNAME for it */ + + r = dns_answer_match_key(t->answer, t->key, flags); + if (r != 0) + return r; + + r = dns_answer_find_cname_or_dname(t->answer, t->key, NULL, flags); + if (r != 0) + return r; + + return false; +} + +static int dns_transaction_fix_rcode(DnsTransaction *t) { + int r; + + assert(t); + + /* Fix up the RCODE to SUCCESS if we get at least one matching RR in a response. Note that this contradicts the + * DNS RFCs a bit. Specifically, RFC 6604 Section 3 clarifies that the RCODE shall say something about a + * CNAME/DNAME chain element coming after the last chain element contained in the message, and not the first + * one included. However, it also indicates that not all DNS servers implement this correctly. Moreover, when + * using DNSSEC we usually only can prove the first element of a CNAME/DNAME chain anyway, hence let's settle + * on always processing the RCODE as referring to the immediate look-up we do, i.e. the first element of a + * CNAME/DNAME chain. This way, we uniformly handle CNAME/DNAME chains, regardless if the DNS server + * incorrectly implements RCODE, whether DNSSEC is in use, or whether the DNS server only supplied us with an + * incomplete CNAME/DNAME chain. + * + * Or in other words: if we get at least one positive reply in a message we patch NXDOMAIN to become SUCCESS, + * and then rely on the CNAME chasing logic to figure out that there's actually a CNAME error with a new + * lookup. */ + + if (t->answer_rcode != DNS_RCODE_NXDOMAIN) + return 0; + + r = dns_transaction_has_positive_answer(t, NULL); + if (r <= 0) + return r; + + t->answer_rcode = DNS_RCODE_SUCCESS; + return 0; } void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { @@ -447,10 +787,12 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { assert(t); assert(p); - assert(t->state == DNS_TRANSACTION_PENDING); assert(t->scope); assert(t->scope->manager); + if (t->state != DNS_TRANSACTION_PENDING) + return; + /* Note that this call might invalidate the query. Callers * should hence not attempt to access the query or transaction * after calling this function. */ @@ -494,6 +836,11 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { break; case DNS_PROTOCOL_DNS: + /* Note that we do not need to verify the + * addresses/port numbers of incoming traffic, as we + * invoked connect() on our UDP socket in which case + * the kernel already does the needed verification for + * us. */ break; default: @@ -533,17 +880,11 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { /* Request failed, immediately try again with reduced features */ log_debug("Server returned error: %s", dns_rcode_to_string(DNS_PACKET_RCODE(p))); - dns_server_packet_failed(t->server, t->current_features); - - r = dns_transaction_go(t); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } - + dns_server_packet_failed(t->server, t->current_feature_level); + dns_transaction_retry(t); return; - } else - dns_server_packet_received(t->server, t->current_features, ts - t->start_usec, p->size); + } else if (DNS_PACKET_TC(p)) + dns_server_packet_truncated(t->server, t->current_feature_level); break; @@ -564,6 +905,8 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { return; } + log_debug("Reply truncated, retrying via TCP."); + /* Response was truncated, let's try again with good old TCP */ r = dns_transaction_open_tcp(t); if (r == -ESRCH) { @@ -571,42 +914,52 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); return; } + if (r == -EOPNOTSUPP) { + /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ + dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); + return; + } if (r < 0) { /* On LLMNR, if we cannot connect to the host, * we immediately give up */ - if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } + if (t->scope->protocol != DNS_PROTOCOL_DNS) + goto fail; /* On DNS, couldn't send? Try immediately again, with a new server */ - dns_transaction_next_dns_server(t); - - r = dns_transaction_go(t); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } - - return; + dns_transaction_retry(t); } + + return; } - /* Parse message, if it isn't parsed yet. */ + /* After the superficial checks, actually parse the message. */ r = dns_packet_extract(p); if (r < 0) { dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); return; } + /* Report that the OPT RR was missing */ + if (t->server) { + if (!p->opt) + dns_server_packet_bad_opt(t->server, t->current_feature_level); + + dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, ts - t->start_usec, p->size); + } + + /* See if we know things we didn't know before that indicate we better restart the lookup immediately. */ + r = dns_transaction_maybe_restart(t); + if (r < 0) + goto fail; + 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 */ r = dns_packet_is_reply_for(p, t->key); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return; - } + if (r < 0) + goto fail; if (r == 0) { dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); return; @@ -616,21 +969,46 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { dns_answer_unref(t->answer); t->answer = dns_answer_ref(p->answer); t->answer_rcode = DNS_PACKET_RCODE(p); - t->answer_authenticated = t->scope->dnssec_mode == DNSSEC_TRUST && DNS_PACKET_AD(p); + t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + t->answer_authenticated = false; + r = dns_transaction_fix_rcode(t); + if (r < 0) + goto fail; + + /* Block GC while starting requests for additional DNSSEC RRs */ + t->block_gc++; r = dns_transaction_request_dnssec_keys(t); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + t->block_gc--; + + /* Maybe the transaction is ready for GC'ing now? If so, free it and return. */ + if (!dns_transaction_gc(t)) return; - } + + /* Requesting additional keys might have resulted in + * this transaction to fail, since the auxiliary + * request failed for some reason. If so, we are not + * in pending state anymore, and we should exit + * quickly. */ + if (t->state != DNS_TRANSACTION_PENDING) + return; + if (r < 0) + goto fail; if (r > 0) { /* There are DNSSEC transactions pending now. Update the state accordingly. */ t->state = DNS_TRANSACTION_VALIDATING; + dns_transaction_close_connection(t); + dns_transaction_stop_timeout(t); return; } } dns_transaction_process_dnssec(t); + return; + +fail: + t->answer_errno = -r; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); } static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { @@ -642,53 +1020,97 @@ static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *use assert(t->scope); r = manager_recv(t->scope->manager, fd, DNS_PROTOCOL_DNS, &p); - if (r <= 0) - return r; + if (ERRNO_IS_DISCONNECT(-r)) { + usec_t usec; - if (dns_packet_validate_reply(p) > 0 && - DNS_PACKET_ID(p) == t->id) - dns_transaction_process_reply(t, p); - else - log_debug("Invalid DNS packet, ignoring."); + /* UDP connection failure get reported via ICMP and then are possible delivered to us on the next + * recvmsg(). Treat this like a lost packet. */ + + log_debug_errno(r, "Connection failure for DNS UDP packet: %m"); + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &usec) >= 0); + dns_server_packet_lost(t->server, IPPROTO_UDP, t->current_feature_level, usec - t->start_usec); + + dns_transaction_retry(t); + return 0; + } + if (r < 0) { + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); + t->answer_errno = -r; + return 0; + } + + r = dns_packet_validate_reply(p); + if (r < 0) { + log_debug_errno(r, "Received invalid DNS packet as response, ignoring: %m"); + return 0; + } + if (r == 0) { + log_debug("Received inappropriate DNS packet as response, ignoring."); + return 0; + } + if (DNS_PACKET_ID(p) != t->id) { + log_debug("Received packet with incorrect transaction ID, ignoring."); + return 0; + } + + dns_transaction_process_reply(t, p); return 0; } -static int dns_transaction_emit(DnsTransaction *t) { +static int dns_transaction_emit_udp(DnsTransaction *t) { int r; assert(t); - if (t->scope->protocol == DNS_PROTOCOL_DNS && !t->server) { - DnsServer *server = NULL; - _cleanup_close_ int fd = -1; - - fd = dns_scope_udp_dns_socket(t->scope, &server); - if (fd < 0) - return fd; + if (t->scope->protocol == DNS_PROTOCOL_DNS) { - r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t); + r = dns_transaction_pick_server(t); if (r < 0) return r; - t->dns_udp_fd = fd; - fd = -1; - t->server = dns_server_ref(server); - } + if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_UDP) + return -EAGAIN; - r = dns_scope_emit(t->scope, t->dns_udp_fd, t->server, t->sent); + if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) + return -EOPNOTSUPP; + + if (r > 0 || t->dns_udp_fd < 0) { /* Server changed, or no connection yet. */ + int fd; + + dns_transaction_close_connection(t); + + fd = dns_scope_socket_udp(t->scope, t->server, 53); + if (fd < 0) + return fd; + + r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t); + if (r < 0) { + safe_close(fd); + return r; + } + + (void) sd_event_source_set_description(t->dns_udp_event_source, "dns-transaction-udp"); + t->dns_udp_fd = fd; + } + + r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); + if (r < 0) + return r; + } else + dns_transaction_close_connection(t); + + r = dns_scope_emit_udp(t->scope, t->dns_udp_fd, t->sent); if (r < 0) return r; - if (t->server) - t->current_features = t->server->possible_features; + dns_transaction_reset_answer(t); return 0; } static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) { DnsTransaction *t = userdata; - int r; assert(s); assert(t); @@ -696,17 +1118,17 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat if (!t->initial_jitter_scheduled || t->initial_jitter_elapsed) { /* Timeout reached? Increase the timeout for the server used */ switch (t->scope->protocol) { + case DNS_PROTOCOL_DNS: assert(t->server); - - dns_server_packet_lost(t->server, t->current_features, usec - t->start_usec); - + dns_server_packet_lost(t->server, t->stream ? IPPROTO_TCP : IPPROTO_UDP, t->current_feature_level, usec - t->start_usec); break; + case DNS_PROTOCOL_LLMNR: case DNS_PROTOCOL_MDNS: dns_scope_packet_lost(t->scope, usec - t->start_usec); - break; + default: assert_not_reached("Invalid DNS protocol."); } @@ -715,13 +1137,9 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat t->initial_jitter_elapsed = true; } - /* ...and try again with a new server */ - dns_transaction_next_dns_server(t); - - r = dns_transaction_go(t); - if (r < 0) - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); + log_debug("Timeout reached on transaction %" PRIu16 ".", t->id); + dns_transaction_retry(t); return 0; } @@ -730,36 +1148,44 @@ static usec_t transaction_get_resend_timeout(DnsTransaction *t) { assert(t->scope); switch (t->scope->protocol) { + case DNS_PROTOCOL_DNS: assert(t->server); - return t->server->resend_timeout; + case DNS_PROTOCOL_MDNS: assert(t->n_attempts > 0); return (1 << (t->n_attempts - 1)) * USEC_PER_SEC; + case DNS_PROTOCOL_LLMNR: return t->scope->resend_timeout; + default: assert_not_reached("Invalid DNS protocol."); } } static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { - bool had_stream; int r; assert(t); - had_stream = !!t->stream; + dns_transaction_stop_timeout(t); - dns_transaction_stop(t); + r = dns_scope_network_good(t->scope); + if (r < 0) + return r; + if (r == 0) { + dns_transaction_complete(t, DNS_TRANSACTION_NETWORK_DOWN); + return 0; + } if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) { dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); return 0; } - if (t->scope->protocol == DNS_PROTOCOL_LLMNR && had_stream) { + if (t->scope->protocol == DNS_PROTOCOL_LLMNR && t->tried_stream) { /* If we already tried via a stream, then we don't * retry on LLMNR. See RFC 4795, Section 2.7. */ dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); @@ -768,14 +1194,13 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { t->n_attempts++; t->start_usec = ts; - t->received = dns_packet_unref(t->received); - t->answer = dns_answer_unref(t->answer); - t->answer_rcode = 0; - t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; + + 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) { - r = dns_trust_anchor_lookup(&t->scope->manager->trust_anchor, t->key, &t->answer); + r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, t->key, &t->answer); if (r < 0) return r; if (r > 0) { @@ -785,6 +1210,41 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); return 0; } + + if (dns_name_is_root(DNS_RESOURCE_KEY_NAME(t->key)) && + t->key->type == DNS_TYPE_DS) { + + /* Hmm, this is a request for the root DS? A + * DS RR doesn't exist in the root zone, and + * if our trust anchor didn't know it either, + * this means we cannot do any DNSSEC logic + * anymore. */ + + if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) { + /* We are in downgrade mode. In this + * case, synthesize an unsigned empty + * response, so that the any lookup + * depending on this one can continue + * assuming there was no DS, and hence + * the root zone was unsigned. */ + + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR; + t->answer_authenticated = false; + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + } else + /* If we are not in downgrade mode, + * then fail the lookup, because we + * cannot reasonably answer it. There + * might be DS RRs, but we don't know + * them, and the DNS server won't tell + * them to us (and even if it would, + * we couldn't validate it and trust + * it). */ + dns_transaction_complete(t, DNS_TRANSACTION_NO_TRUST_ANCHOR); + + return 0; + } } /* Check the zone, but only if this transaction is not used @@ -823,7 +1283,7 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { if (t->answer_rcode == DNS_RCODE_SUCCESS) dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); else - dns_transaction_complete(t, DNS_TRANSACTION_FAILURE); + dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); return 0; } } @@ -843,7 +1303,7 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) { assert(t); assert(t->scope->protocol == DNS_PROTOCOL_MDNS); - /* Discard any previously prepared packet, so we can start over and coaleasce again */ + /* Discard any previously prepared packet, so we can start over and coalesce again */ t->sent = dns_packet_unref(t->sent); r = dns_packet_new_query(&p, t->scope->protocol, 0, false); @@ -909,6 +1369,8 @@ static int dns_transaction_make_packet_mdns(DnsTransaction *t) { if (r < 0) return r; + (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); + other->state = DNS_TRANSACTION_PENDING; other->next_attempt_after = ts; @@ -945,16 +1407,10 @@ static int dns_transaction_make_packet(DnsTransaction *t) { if (t->sent) return 0; - r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode == DNSSEC_YES); + r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode != DNSSEC_NO); if (r < 0) return r; - r = dns_scope_good_key(t->scope, t->key); - if (r < 0) - return r; - if (r == 0) - return -EDOM; - r = dns_packet_append_key(p, t->key, NULL); if (r < 0) return r; @@ -980,17 +1436,12 @@ int dns_transaction_go(DnsTransaction *t) { if (r <= 0) return r; - if (log_get_max_level() >= LOG_DEBUG) { - _cleanup_free_ char *ks = NULL; - - (void) dns_resource_key_to_string(t->key, &ks); - - log_debug("Excercising transaction for <%s> on scope %s on %s/%s", - ks ? strstrip(ks) : "???", - dns_protocol_to_string(t->scope->protocol), - t->scope->link ? t->scope->link->name : "*", - t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family)); - } + log_debug("Excercising transaction %" PRIu16 " for <%s> on scope %s on %s/%s.", + t->id, + dns_transaction_key_string(t), + dns_protocol_to_string(t->scope->protocol), + t->scope->link ? t->scope->link->name : "*", + t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family)); if (!t->initial_jitter_scheduled && (t->scope->protocol == DNS_PROTOCOL_LLMNR || @@ -1005,10 +1456,12 @@ int dns_transaction_go(DnsTransaction *t) { random_bytes(&jitter, sizeof(jitter)); switch (t->scope->protocol) { + case DNS_PROTOCOL_LLMNR: jitter %= LLMNR_JITTER_INTERVAL_USEC; accuracy = LLMNR_JITTER_INTERVAL_USEC; break; + case DNS_PROTOCOL_MDNS: jitter %= MDNS_JITTER_RANGE_USEC; jitter += MDNS_JITTER_MIN_USEC; @@ -1027,6 +1480,8 @@ int dns_transaction_go(DnsTransaction *t) { if (r < 0) return r; + (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); + t->n_attempts = 0; t->next_attempt_after = ts; t->state = DNS_TRANSACTION_PENDING; @@ -1037,13 +1492,6 @@ int dns_transaction_go(DnsTransaction *t) { /* Otherwise, we need to ask the network */ r = dns_transaction_make_packet(t); - if (r == -EDOM) { - /* Not the right request to make on this network? - * (i.e. an A request made on IPv6 or an AAAA request - * made on IPv4, on LLMNR or mDNS.) */ - dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); - return 0; - } if (r < 0) return r; @@ -1057,7 +1505,11 @@ int dns_transaction_go(DnsTransaction *t) { } else { /* Try via UDP, and if that fails due to large size or lack of * support try via TCP */ - r = dns_transaction_emit(t); + r = dns_transaction_emit_udp(t); + if (r == -EMSGSIZE) + log_debug("Sending query via TCP since it is too large."); + if (r == -EAGAIN) + log_debug("Sending query via TCP since server doesn't support UDP."); if (r == -EMSGSIZE || r == -EAGAIN) r = dns_transaction_open_tcp(t); } @@ -1066,14 +1518,24 @@ int dns_transaction_go(DnsTransaction *t) { /* No servers to send this to? */ dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); return 0; - } else if (r < 0) { - if (t->scope->protocol != DNS_PROTOCOL_DNS) { - dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES); - return 0; - } + } + if (r == -EOPNOTSUPP) { + /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ + dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); + return 0; + } + if (t->scope->protocol == DNS_PROTOCOL_LLMNR && ERRNO_IS_DISCONNECT(-r)) { + /* On LLMNR, if we cannot connect to a host via TCP when doing reverse lookups. This means we cannot + * answer this request with this protocol. */ + dns_transaction_complete(t, DNS_TRANSACTION_NOT_FOUND); + return 0; + } + if (r < 0) { + if (t->scope->protocol != DNS_PROTOCOL_DNS) + return r; /* Couldn't send? Try immediately again, with a new server */ - dns_transaction_next_dns_server(t); + dns_scope_next_dns_server(t->scope); return dns_transaction_go(t); } @@ -1089,12 +1551,36 @@ int dns_transaction_go(DnsTransaction *t) { if (r < 0) return r; + (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); + t->state = DNS_TRANSACTION_PENDING; t->next_attempt_after = ts; return 1; } +static int dns_transaction_find_cyclic(DnsTransaction *t, DnsTransaction *aux) { + DnsTransaction *n; + Iterator i; + int r; + + assert(t); + assert(aux); + + /* Try to find cyclic dependencies between transaction objects */ + + if (t == aux) + return 1; + + SET_FOREACH(n, aux->dnssec_transactions, i) { + r = dns_transaction_find_cyclic(t, n); + if (r != 0) + return r; + } + + return 0; +} + static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResourceKey *key, DnsTransaction **ret) { DnsTransaction *aux; int r; @@ -1113,6 +1599,18 @@ static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResource *ret = aux; return 0; } + + r = dns_transaction_find_cyclic(t, aux); + if (r < 0) + return r; + if (r > 0) { + log_debug("Detected potential cyclic dependency, refusing to add transaction %" PRIu16 " (%s) as dependency for %" PRIu16 " (%s).", + aux->id, + strna(dns_transaction_key_string(aux)), + t->id, + strna(dns_transaction_key_string(t))); + return -ELOOP; + } } r = set_ensure_allocated(&t->dnssec_transactions, NULL); @@ -1150,7 +1648,7 @@ static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey * assert(key); /* Try to get the data from the trust anchor */ - r = dns_trust_anchor_lookup(&t->scope->manager->trust_anchor, key, &a); + r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, key, &a); if (r < 0) return r; if (r > 0) { @@ -1163,6 +1661,8 @@ static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey * /* This didn't work, ask for it via the network/cache then. */ r = dns_transaction_add_dnssec_transaction(t, key, &aux); + if (r == -ELOOP) /* This would result in a cyclic dependency */ + return 0; if (r < 0) return r; @@ -1172,20 +1672,158 @@ static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey * return r; } - return 0; + return 1; +} + +static int dns_transaction_negative_trust_anchor_lookup(DnsTransaction *t, const char *name) { + int r; + + assert(t); + + /* Check whether the specified name is in the the NTA + * database, either in the global one, or the link-local + * one. */ + + r = dns_trust_anchor_lookup_negative(&t->scope->manager->trust_anchor, name); + if (r != 0) + return r; + + if (!t->scope->link) + return 0; + + return set_contains(t->scope->link->dnssec_negative_trust_anchors, name); +} + +static int dns_transaction_has_unsigned_negative_answer(DnsTransaction *t) { + int r; + + assert(t); + + /* Checks whether the answer is negative, and lacks NSEC/NSEC3 + * RRs to prove it */ + + r = dns_transaction_has_positive_answer(t, NULL); + if (r < 0) + return r; + if (r > 0) + return false; + + /* Is this key explicitly listed as a negative trust anchor? + * If so, it's nothing we need to care about */ + r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(t->key)); + if (r < 0) + return r; + if (r > 0) + return false; + + /* The answer does not contain any RRs that match to the + * question. If so, let's see if there are any NSEC/NSEC3 RRs + * included. If not, the answer is unsigned. */ + + r = dns_answer_contains_nsec_or_nsec3(t->answer); + if (r < 0) + return r; + if (r > 0) + return false; + + return true; +} + +static int dns_transaction_is_primary_response(DnsTransaction *t, DnsResourceRecord *rr) { + int r; + + assert(t); + assert(rr); + + /* Check if the specified RR is the "primary" response, + * i.e. either matches the question precisely or is a + * CNAME/DNAME for it. */ + + r = dns_resource_key_match_rr(t->key, rr, NULL); + if (r != 0) + return r; + + return dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL); +} + +static bool dns_transaction_dnssec_supported(DnsTransaction *t) { + assert(t); + + /* Checks whether our transaction's DNS server is assumed to be compatible with DNSSEC. Returns false as soon + * as we changed our mind about a server, and now believe it is incompatible with DNSSEC. */ + + if (t->scope->protocol != DNS_PROTOCOL_DNS) + return false; + + /* If we have picked no server, then we are working from the cache or some other source, and DNSSEC might well + * be supported, hence return true. */ + if (!t->server) + return true; + + if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_DO) + return false; + + return dns_server_dnssec_supported(t->server); +} + +static bool dns_transaction_dnssec_supported_full(DnsTransaction *t) { + DnsTransaction *dt; + Iterator i; + + assert(t); + + /* Checks whether our transaction our any of the auxiliary transactions couldn't do DNSSEC. */ + + if (!dns_transaction_dnssec_supported(t)) + return false; + + SET_FOREACH(dt, t->dnssec_transactions, i) + if (!dns_transaction_dnssec_supported(dt)) + return false; + + return true; } int dns_transaction_request_dnssec_keys(DnsTransaction *t) { DnsResourceRecord *rr; + int r; assert(t); - if (t->scope->dnssec_mode != DNSSEC_YES) + /* + * Retrieve all auxiliary RRs for the answer we got, so that + * we can verify signatures or prove that RRs are rightfully + * unsigned. Specifically: + * + * - For RRSIG we get the matching DNSKEY + * - For DNSKEY we get the matching DS + * - For unsigned SOA/NS we get the matching DS + * - For unsigned CNAME/DNAME/DS we get the parent SOA RR + * - For other unsigned RRs we get the matching SOA RR + * - For SOA/NS/DS queries with no matching response RRs, and no NSEC/NSEC3, the parent's SOA RR + * - For other queries with no matching response RRs, and no NSEC/NSEC3, the SOA RR + */ + + if (t->scope->dnssec_mode == DNSSEC_NO) return 0; + if (t->answer_source != DNS_TRANSACTION_NETWORK) + return 0; /* We only need to validate stuff from the network */ + if (!dns_transaction_dnssec_supported(t)) + return 0; /* If we can't do DNSSEC anyway there's no point in geting the auxiliary RRs */ DNS_ANSWER_FOREACH(rr, t->answer) { + if (dns_type_is_pseudo(rr->key->type)) + continue; + + /* If this RR is in the negative trust anchor, we don't need to validate it. */ + r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r > 0) + continue; + switch (rr->key->type) { case DNS_TYPE_RRSIG: { @@ -1204,10 +1842,18 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { continue; } - /* If the signer is not a parent of the owner, - * then the signature is bogus, let's ignore - * it. */ - r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), rr->rrsig.signer); + /* If the signer is not a parent of our + * original query, then this is about an + * auxiliary RRset, but not anything we asked + * for. In this case we aren't interested, + * because we don't want to request additional + * RRs for stuff we didn't really ask for, and + * also to avoid request loops, where + * additional RRs from one transaction result + * in another transaction whose additonal RRs + * point back to the original transaction, and + * we deadlock. */ + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), rr->rrsig.signer); if (r < 0) return r; if (r == 0) @@ -1217,8 +1863,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (!dnskey) return -ENOMEM; - log_debug("Requesting DNSKEY to validate transaction %" PRIu16" (key tag: %" PRIu16 ").", t->id, rr->rrsig.key_tag); - + log_debug("Requesting DNSKEY to validate transaction %" PRIu16" (%s, RRSIG with key tag: %" PRIu16 ").", t->id, DNS_RESOURCE_KEY_NAME(rr->key), rr->rrsig.key_tag); r = dns_transaction_request_dnssec_rr(t, dnskey); if (r < 0) return r; @@ -1229,61 +1874,685 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { /* For each DNSKEY we request the matching DS */ _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; + /* If the DNSKEY we are looking at is not for + * zone we are interested in, nor any of its + * parents, we aren't interested, and don't + * request it. After all, we don't want to end + * up in request loops, and want to keep + * additional traffic down. */ + + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r == 0) + continue; + ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(rr->key)); if (!ds) return -ENOMEM; - log_debug("Requesting DS to validate transaction %" PRIu16" (key tag: %" PRIu16 ").", t->id, dnssec_keytag(rr)); + log_debug("Requesting DS to validate transaction %" PRIu16" (%s, DNSKEY with key tag: %" PRIu16 ").", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dnssec_keytag(rr, false)); + r = dns_transaction_request_dnssec_rr(t, ds); + if (r < 0) + return r; + + break; + } + + case DNS_TYPE_SOA: + case DNS_TYPE_NS: { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; + + /* For an unsigned SOA or NS, try to acquire + * the matching DS RR, as we are at a zone cut + * then, and whether a DS exists tells us + * whether the zone is signed. Do so only if + * this RR matches our original question, + * however. */ + + r = dns_resource_key_match_rr(t->key, rr, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_has_rrsig(t->answer, rr->key); + if (r < 0) + return r; + if (r > 0) + continue; + + ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(rr->key)); + if (!ds) + return -ENOMEM; + log_debug("Requesting DS to validate transaction %" PRIu16 " (%s, unsigned SOA/NS RRset).", t->id, DNS_RESOURCE_KEY_NAME(rr->key)); r = dns_transaction_request_dnssec_rr(t, ds); if (r < 0) return r; break; + } + + case DNS_TYPE_DS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + const char *name; + + /* CNAMEs and DNAMEs cannot be located at a + * zone apex, hence ask for the parent SOA for + * unsigned CNAME/DNAME RRs, maybe that's the + * apex. But do all that only if this is + * actually a response to our original + * question. + * + * Similar for DS RRs, which are signed when + * the parent SOA is signed. */ + + r = dns_transaction_is_primary_response(t, rr); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_has_rrsig(t->answer, rr->key); + if (r < 0) + return r; + 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) + return r; + if (r == 0) + continue; + + soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, name); + if (!soa) + return -ENOMEM; + + log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned CNAME/DNAME/DS RRset).", t->id, DNS_RESOURCE_KEY_NAME(rr->key)); + r = dns_transaction_request_dnssec_rr(t, soa); + if (r < 0) + return r; + + break; + } + + default: { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + + /* For other unsigned RRsets (including + * NSEC/NSEC3!), look for proof the zone is + * unsigned, by requesting the SOA RR of the + * zone. However, do so only if they are + * directly relevant to our original + * question. */ + + r = dns_transaction_is_primary_response(t, rr); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_has_rrsig(t->answer, rr->key); + if (r < 0) + return r; + if (r > 0) + continue; + + soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, DNS_RESOURCE_KEY_NAME(rr->key)); + if (!soa) + return -ENOMEM; + + log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset <%s>).", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dns_resource_record_to_string(rr)); + r = dns_transaction_request_dnssec_rr(t, soa); + if (r < 0) + return r; + break; }} } - return !set_isempty(t->dnssec_transactions); + /* Above, we requested everything necessary to validate what + * we got. Now, let's request what we need to validate what we + * didn't get... */ + + r = dns_transaction_has_unsigned_negative_answer(t); + if (r < 0) + return r; + if (r > 0) { + const char *name; + + name = DNS_RESOURCE_KEY_NAME(t->key); + + /* If this was a SOA or NS request, then this + * indicates that we are not at a zone apex, hence ask + * the parent name instead. If this was a DS request, + * then it's signed when the parent zone is signed, + * hence ask the parent in that case, too. */ + + if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS, DNS_TYPE_DS)) { + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r > 0) + log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned empty SOA/NS/DS response).", t->id, DNS_RESOURCE_KEY_NAME(t->key)); + else + name = NULL; + } else + log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned empty non-SOA/NS/DS response).", t->id, DNS_RESOURCE_KEY_NAME(t->key)); + + if (name) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + + soa = dns_resource_key_new(t->key->class, DNS_TYPE_SOA, name); + if (!soa) + return -ENOMEM; + + r = dns_transaction_request_dnssec_rr(t, soa); + if (r < 0) + return r; + } + } + + return dns_transaction_dnssec_is_live(t); } void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source) { + assert(t); + assert(source); + + /* 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. */ + + if (t->state == DNS_TRANSACTION_VALIDATING) + dns_transaction_process_dnssec(t); +} + +static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) { + DnsResourceRecord *rr; + int ifindex, r; + + assert(t); + + /* Add all DNSKEY RRs from the answer that are validated by DS + * RRs from the list of validated keys to the list of + * validated keys. */ + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) { + + r = dnssec_verify_dnskey_by_ds_search(rr, t->validated_keys); + if (r < 0) + return r; + if (r == 0) + continue; + + /* If so, the DNSKEY is validated too. */ + r = dns_answer_add_extend(&t->validated_keys, rr, ifindex, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *rr) { int r; assert(t); - assert(IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)); - assert(source); + assert(rr); - /* Invoked whenever any of our auxiliary DNSSEC transactions - completed its work. We simply copy the answer from that - transaction over. */ + /* Checks if the RR we are looking for must be signed with an + * RRSIG. This is used for positive responses. */ - if (source->state != DNS_TRANSACTION_SUCCESS) { - log_debug("Auxiliary DNSSEC RR query failed."); - t->dnssec_result = DNSSEC_FAILED_AUXILIARY; - } else { - r = dns_answer_extend(&t->validated_keys, source->answer); - if (r < 0) { - log_error_errno(r, "Failed to merge validated DNSSEC key data: %m"); - t->dnssec_result = DNSSEC_FAILED_AUXILIARY; + if (t->scope->dnssec_mode == DNSSEC_NO) + return false; + + if (dns_type_is_pseudo(rr->key->type)) + return -EINVAL; + + r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r > 0) + return false; + + switch (rr->key->type) { + + case DNS_TYPE_RRSIG: + /* RRSIGs are the signatures themselves, they need no signing. */ + return false; + + case DNS_TYPE_SOA: + case DNS_TYPE_NS: { + DnsTransaction *dt; + Iterator i; + + /* For SOA or NS RRs we look for a matching DS transaction */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != rr->key->class) + continue; + if (dt->key->type != DNS_TYPE_DS) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r == 0) + continue; + + /* We found a DS transactions for the SOA/NS + * RRs we are looking at. If it discovered signed DS + * RRs, then we need to be signed, too. */ + + if (!dt->answer_authenticated) + return false; + + return dns_answer_match_key(dt->answer, dt->key, NULL); + } + + /* We found nothing that proves this is safe to leave + * this unauthenticated, hence ask inist on + * authentication. */ + return true; + } + + case DNS_TYPE_DS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: { + const char *parent = NULL; + DnsTransaction *dt; + Iterator i; + + /* + * CNAME/DNAME RRs cannot be located at a zone apex, hence look directly for the parent SOA. + * + * DS RRs are signed if the parent is signed, hence also look at the parent SOA + */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != rr->key->class) + continue; + if (dt->key->type != DNS_TYPE_SOA) + continue; + + if (!parent) { + parent = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_parent(&parent); + if (r < 0) + return r; + if (r == 0) { + if (rr->key->type == DNS_TYPE_DS) + return true; + + /* A CNAME/DNAME without a parent? That's sooo weird. */ + log_debug("Transaction %" PRIu16 " claims CNAME/DNAME at root. Refusing.", t->id); + return -EBADMSG; + } + } + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), parent); + if (r < 0) + return r; + if (r == 0) + continue; + + return t->answer_authenticated; } + + return true; } - /* Detach us from the DNSSEC transaction. */ - (void) set_remove(t->dnssec_transactions, source); - (void) set_remove(source->notify_transactions, t); + default: { + DnsTransaction *dt; + Iterator i; - /* 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); + /* Any other kind of RR (including DNSKEY/NSEC/NSEC3). Let's see if our SOA lookup was authenticated */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != rr->key->class) + continue; + if (dt->key->type != DNS_TYPE_SOA) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r == 0) + continue; + + /* We found the transaction that was supposed to find + * the SOA RR for us. It was successful, but found no + * RR for us. This means we are not at a zone cut. In + * this case, we require authentication if the SOA + * lookup was authenticated too. */ + return t->answer_authenticated; + } + + return true; + }} +} + +static int dns_transaction_in_private_tld(DnsTransaction *t, const DnsResourceKey *key) { + DnsTransaction *dt; + const char *tld; + Iterator i; + int r; + + /* If DNSSEC downgrade mode is on, checks whether the + * specified RR is one level below a TLD we have proven not to + * exist. In such a case we assume that this is a private + * domain, and permit it. + * + * This detects cases like the Fritz!Box router networks. Each + * Fritz!Box router serves a private "fritz.box" zone, in the + * non-existing TLD "box". Requests for the "fritz.box" domain + * are served by the router itself, while requests for the + * "box" domain will result in NXDOMAIN. + * + * Note that this logic is unable to detect cases where a + * router serves a private DNS zone directly under + * non-existing TLD. In such a case we cannot detect whether + * the TLD is supposed to exist or not, as all requests we + * make for it will be answered by the router's zone, and not + * by the root zone. */ + + assert(t); + + if (t->scope->dnssec_mode != DNSSEC_ALLOW_DOWNGRADE) + return false; /* In strict DNSSEC mode what doesn't exist, doesn't exist */ + + tld = DNS_RESOURCE_KEY_NAME(key); + r = dns_name_parent(&tld); + if (r < 0) + return r; + if (r == 0) + return false; /* Already the root domain */ + + if (!dns_name_is_single_label(tld)) + return false; + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != key->class) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), tld); + if (r < 0) + return r; + if (r == 0) + continue; + + /* We found an auxiliary lookup we did for the TLD. If + * that returned with NXDOMAIN, we know the TLD didn't + * exist, and hence this might be a private zone. */ + + return dt->answer_rcode == DNS_RCODE_NXDOMAIN; + } + + return false; +} + +static int dns_transaction_requires_nsec(DnsTransaction *t) { + DnsTransaction *dt; + const char *name; + Iterator i; + int r; + + assert(t); + + /* Checks if we need to insist on NSEC/NSEC3 RRs for proving + * this negative reply */ + + if (t->scope->dnssec_mode == DNSSEC_NO) + return false; + + if (dns_type_is_pseudo(t->key->type)) + return -EINVAL; + + r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(t->key)); + if (r < 0) + return r; + if (r > 0) + return false; + + r = dns_transaction_in_private_tld(t, t->key); + if (r < 0) + return r; + if (r > 0) { + /* The lookup is from a TLD that is proven not to + * exist, and we are in downgrade mode, hence ignore + * that fact that we didn't get any NSEC RRs.*/ + + log_info("Detected a negative query %s in a private DNS zone, permitting unsigned response.", dns_transaction_key_string(t)); + return false; + } + + name = DNS_RESOURCE_KEY_NAME(t->key); + + if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS, DNS_TYPE_DS)) { + + /* We got a negative reply for this SOA/NS lookup? If + * so, then we are not at a zone apex, and thus should + * look at the result of the parent SOA lookup. + * + * We got a negative reply for this DS lookup? DS RRs + * are signed when their parent zone is signed, hence + * also check the parent SOA in this case. */ + + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r == 0) + return true; + } + + /* For all other RRs we check the SOA on the same level to see + * if it's signed. */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != t->key->class) + continue; + if (dt->key->type != DNS_TYPE_SOA) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), name); + if (r < 0) + return r; + if (r == 0) + continue; + + return dt->answer_authenticated; + } + + /* If in doubt, require NSEC/NSEC3 */ + return true; +} + +static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRecord *rr) { + DnsResourceRecord *rrsig; + bool found = false; + int r; + + /* Checks whether any of the DNSKEYs used for the RRSIGs for + * the specified RRset is authenticated (i.e. has a matching + * DS RR). */ + + r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r > 0) + return false; + + DNS_ANSWER_FOREACH(rrsig, t->answer) { + DnsTransaction *dt; + Iterator i; + + r = dnssec_key_match_rrsig(rr->key, rrsig); + if (r < 0) + return r; + if (r == 0) + continue; + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != rr->key->class) + continue; + + if (dt->key->type == DNS_TYPE_DNSKEY) { + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), rrsig->rrsig.signer); + if (r < 0) + return r; + if (r == 0) + continue; + + /* OK, we found an auxiliary DNSKEY + * lookup. If that lookup is + * authenticated, report this. */ + + if (dt->answer_authenticated) + return true; + + found = true; + + } else if (dt->key->type == DNS_TYPE_DS) { + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), rrsig->rrsig.signer); + if (r < 0) + return r; + if (r == 0) + continue; + + /* OK, we found an auxiliary DS + * lookup. If that lookup is + * authenticated and non-zero, we + * won! */ + + if (!dt->answer_authenticated) + return false; + + return dns_answer_match_key(dt->answer, dt->key, NULL); + } + } + } + + return found ? false : -ENXIO; +} + +static int dns_transaction_known_signed(DnsTransaction *t, DnsResourceRecord *rr) { + assert(t); + assert(rr); + + /* We know that the root domain is signed, hence if it appears + * not to be signed, there's a problem with the DNS server */ + + return rr->key->class == DNS_CLASS_IN && + dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key)); +} + +static int dns_transaction_check_revoked_trust_anchors(DnsTransaction *t) { + DnsResourceRecord *rr; + int r; + + assert(t); + + /* Maybe warn the user that we encountered a revoked DNSKEY + * for a key from our trust anchor. Note that we don't care + * whether the DNSKEY can be authenticated or not. It's + * sufficient if it is self-signed. */ + + DNS_ANSWER_FOREACH(rr, t->answer) { + r = dns_trust_anchor_check_revoked(&t->scope->manager->trust_anchor, rr, t->answer); + if (r < 0) + return r; + } + + return 0; +} + +static int dns_transaction_invalidate_revoked_keys(DnsTransaction *t) { + bool changed; + int r; + + assert(t); + + /* Removes all DNSKEY/DS objects from t->validated_keys that + * our trust anchors database considers revoked. */ + + do { + DnsResourceRecord *rr; + + changed = false; + + DNS_ANSWER_FOREACH(rr, t->validated_keys) { + r = dns_trust_anchor_is_revoked(&t->scope->manager->trust_anchor, rr); + if (r < 0) + return r; + if (r > 0) { + r = dns_answer_remove_by_rr(&t->validated_keys, rr); + if (r < 0) + return r; + + assert(r > 0); + changed = true; + break; + } + } + } while (changed); + + return 0; +} + +static int dns_transaction_copy_validated(DnsTransaction *t) { + DnsTransaction *dt; + Iterator i; + int r; + + assert(t); + + /* Copy all validated RRs from the auxiliary DNSSEC transactions into our set of validated RRs */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (DNS_TRANSACTION_IS_LIVE(dt->state)) + continue; + + if (!dt->answer_authenticated) + continue; + + r = dns_answer_extend(&t->validated_keys, dt->answer); + if (r < 0) + return r; + } + + return 0; } int dns_transaction_validate_dnssec(DnsTransaction *t) { _cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL; + enum { + PHASE_DNSKEY, /* Phase #1, only validate DNSKEYs */ + PHASE_NSEC, /* Phase #2, only validate NSEC+NSEC3 */ + PHASE_ALL, /* Phase #3, validate everything else */ + } phase; DnsResourceRecord *rr; - int ifindex, r; + DnsAnswerFlags flags; + int r; assert(t); @@ -1291,69 +2560,102 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { * t->validated_keys, let's see which RRs we can now * authenticate with that. */ - if (t->scope->dnssec_mode != DNSSEC_YES) + if (t->scope->dnssec_mode == DNSSEC_NO) return 0; /* Already validated */ - if (t->dnssec_result != _DNSSEC_RESULT_INVALID) + if (t->answer_dnssec_result != _DNSSEC_RESULT_INVALID) return 0; + /* Our own stuff needs no validation */ if (IN_SET(t->answer_source, DNS_TRANSACTION_ZONE, DNS_TRANSACTION_TRUST_ANCHOR)) { - t->dnssec_result = DNSSEC_VALIDATED; + t->answer_dnssec_result = DNSSEC_VALIDATED; t->answer_authenticated = true; return 0; } - if (log_get_max_level() >= LOG_DEBUG) { - _cleanup_free_ char *ks = NULL; + /* Cached stuff is not affected by validation. */ + if (t->answer_source != DNS_TRANSACTION_NETWORK) + return 0; - (void) dns_resource_key_to_string(t->key, &ks); - log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, ks ? strstrip(ks) : "???"); + if (!dns_transaction_dnssec_supported_full(t)) { + /* The server does not support DNSSEC, or doesn't augment responses with RRSIGs. */ + t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER; + log_debug("Not validating response for %" PRIu16 ", server lacks DNSSEC support.", t->id); + return 0; } - /* First see if there are DNSKEYs we already known a validated DS for. */ - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) { + log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, dns_transaction_key_string(t)); - r = dnssec_verify_dnskey_search(rr, t->validated_keys); - if (r < 0) - return r; - if (r == 0) - continue; + /* First, see if this response contains any revoked trust + * anchors we care about */ + r = dns_transaction_check_revoked_trust_anchors(t); + if (r < 0) + return r; - /* If so, the DNSKEY is validated too. */ - r = dns_answer_add_extend(&t->validated_keys, rr, ifindex); - if (r < 0) - return r; - } + /* Third, copy all RRs we acquired successfully from auxiliary RRs over. */ + r = dns_transaction_copy_validated(t); + if (r < 0) + return r; + + /* Second, see if there are DNSKEYs we already know a + * validated DS for. */ + r = dns_transaction_validate_dnskey_by_ds(t); + if (r < 0) + return r; + + /* Fourth, remove all DNSKEY and DS RRs again that our trust + * anchor says are revoked. After all we might have marked + * some keys revoked above, but they might still be lingering + * in our validated_keys list. */ + r = dns_transaction_invalidate_revoked_keys(t); + if (r < 0) + return r; + phase = PHASE_DNSKEY; for (;;) { - bool changed = false, missing_key_for_transaction = false; + bool changed = false, have_nsec = false; DNS_ANSWER_FOREACH(rr, t->answer) { + DnsResourceRecord *rrsig = NULL; DnssecResult result; - if (rr->key->type == DNS_TYPE_RRSIG) + switch (rr->key->type) { + + case DNS_TYPE_RRSIG: continue; - r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result); - if (r < 0) - return r; + case DNS_TYPE_DNSKEY: + /* We validate DNSKEYs only in the DNSKEY and ALL phases */ + if (phase == PHASE_NSEC) + continue; + break; - if (log_get_max_level() >= LOG_DEBUG) { - _cleanup_free_ char *rrs = NULL; + case DNS_TYPE_NSEC: + case DNS_TYPE_NSEC3: + have_nsec = true; + + /* We validate NSEC/NSEC3 only in the NSEC and ALL phases */ + if (phase == PHASE_DNSKEY) + continue; + + break; + + default: + /* We validate all other RRs only in the ALL phases */ + if (phase != PHASE_ALL) + continue; - (void) dns_resource_record_to_string(rr, &rrs); - log_debug("Looking at %s: %s", rrs ? strstrip(rrs) : "???", dnssec_result_to_string(result)); + break; } - switch (result) { + r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result, &rrsig); + if (r < 0) + return r; - case DNSSEC_VALIDATED: + log_debug("Looking at %s: %s", strna(dns_resource_record_to_string(rr)), dnssec_result_to_string(result)); - /* Add the validated RRset to the new list of validated RRsets */ - r = dns_answer_copy_by_key(&validated, t->answer, rr->key); - if (r < 0) - return r; + if (result == DNSSEC_VALIDATED) { if (rr->key->type == DNS_TYPE_DNSKEY) { /* If we just validated a @@ -1362,81 +2664,226 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { * of validated keys for this * transaction. */ - r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key); + r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + /* some of the DNSKEYs we just + * added might already have + * been revoked, remove them + * again in that case. */ + r = dns_transaction_invalidate_revoked_keys(t); if (r < 0) return r; } - /* Now, remove this RRset from the RRs still to process */ - r = dns_answer_remove_by_key(&t->answer, rr->key); + /* Add the validated RRset to the new + * list of validated RRsets, and + * remove it from the unvalidated + * RRsets. We mark the RRset as + * authenticated and cacheable. */ + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE); if (r < 0) return r; + manager_dnssec_verdict(t->scope->manager, DNSSEC_SECURE, rr->key); + + /* Exit the loop, we dropped something from the answer, start from the beginning */ changed = true; break; + } + + /* If we haven't read all DNSKEYs yet a negative result of the validation is irrelevant, as + * there might be more DNSKEYs coming. Similar, if we haven't read all NSEC/NSEC3 RRs yet, we + * cannot do positive wildcard proofs yet, as those require the NSEC/NSEC3 RRs. */ + if (phase != PHASE_ALL) + continue; - case DNSSEC_INVALID: - case DNSSEC_NO_SIGNATURE: - case DNSSEC_SIGNATURE_EXPIRED: + if (result == DNSSEC_VALIDATED_WILDCARD) { + bool authenticated = false; + const char *source; - /* Is this the RRset that we were looking for? If so, this is fatal for the whole transaction */ - r = dns_resource_key_match_rr(t->key, rr, NULL); + /* This RRset validated, but as a wildcard. This means we need to prove via NSEC/NSEC3 + * that no matching non-wildcard RR exists.*/ + + /* First step, determine the source of synthesis */ + r = dns_resource_record_source(rrsig, &source); if (r < 0) return r; - if (r > 0) { - t->dnssec_result = result; - return 0; + + r = dnssec_test_positive_wildcard( + validated, + DNS_RESOURCE_KEY_NAME(rr->key), + source, + rrsig->rrsig.signer, + &authenticated); + + /* Unless the NSEC proof showed that the key really doesn't exist something is off. */ + if (r == 0) + result = DNSSEC_INVALID; + else { + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, authenticated ? (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE) : 0); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, rr->key); + + /* Exit the loop, we dropped something from the answer, start from the beginning */ + changed = true; + break; + } + } + + if (result == DNSSEC_NO_SIGNATURE) { + r = dns_transaction_requires_rrsig(t, rr); + if (r < 0) + return r; + if (r == 0) { + /* Data does not require signing. In that case, just copy it over, + * but remember that this is by no means authenticated.*/ + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); + changed = true; + break; } - /* Is this a CNAME for a record we were looking for? If so, it's also fatal for the whole transaction */ - r = dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL); + r = dns_transaction_known_signed(t, rr); if (r < 0) return r; if (r > 0) { - t->dnssec_result = result; + /* 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, t->current_feature_level); + + if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) { + + /* Downgrading is OK? If so, just consider the information unsigned */ + + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); + changed = true; + break; + } + + /* Otherwise, fail */ + t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER; return 0; } - /* This is just something auxiliary. Just remove the RRset and continue. */ - r = dns_answer_remove_by_key(&t->answer, rr->key); + r = dns_transaction_in_private_tld(t, rr->key); if (r < 0) return r; + if (r > 0) { + _cleanup_free_ char *s = NULL; - changed = true; - break; + /* The data is from a TLD that is proven not to exist, and we are in downgrade + * mode, hence ignore the fact that this was not signed. */ + + (void) dns_resource_key_to_string(rr->key, &s); + log_info("Detected RRset %s is in a private DNS zone, permitting unsigned RRs.", strna(s ? strstrip(s) : NULL)); + + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); + changed = true; + break; + } + } - case DNSSEC_MISSING_KEY: - /* They key is missing? Let's continue - * with the next iteration, maybe - * we'll find it in an DNSKEY RRset - * later on. */ + if (IN_SET(result, + DNSSEC_MISSING_KEY, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_UNSUPPORTED_ALGORITHM)) { - r = dns_resource_key_equal(rr->key, t->key); + r = dns_transaction_dnskey_authenticated(t, rr); + if (r < 0 && r != -ENXIO) + return r; + if (r == 0) { + /* The DNSKEY transaction was not authenticated, this means there's + * no DS for this, which means it's OK if no keys are found for this signature. */ + + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); + changed = true; + break; + } + } + + r = dns_transaction_is_primary_response(t, rr); + if (r < 0) + return r; + if (r > 0) { + + /* Look for a matching DNAME for this CNAME */ + r = dns_answer_has_dname_for_cname(t->answer, rr); if (r < 0) return r; - if (r > 0) - missing_key_for_transaction = true; + 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; + } - break; + if (r == 0) { + if (IN_SET(result, + DNSSEC_INVALID, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_NO_SIGNATURE)) + manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, rr->key); + else /* DNSSEC_MISSING_KEY or DNSSEC_UNSUPPORTED_ALGORITHM */ + manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, rr->key); + + /* This is a primary response to our question, and it failed validation. That's + * fatal. */ + t->answer_dnssec_result = result; + return 0; + } - default: - assert_not_reached("Unexpected DNSSEC result"); + /* 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. */ } - if (changed) - break; + /* This is just some auxiliary data. Just remove the RRset and continue. */ + r = dns_answer_remove_by_key(&t->answer, rr->key); + if (r < 0) + return r; + + /* Exit the loop, we dropped something from the answer, start from the beginning */ + changed = true; + break; } + /* Restart the inner loop as long as we managed to achieve something */ if (changed) continue; - /* This didn't work either, there's no point in - * continuing. */ - if (missing_key_for_transaction) { - t->dnssec_result = DNSSEC_MISSING_KEY; - return 0; + if (phase == PHASE_DNSKEY && have_nsec) { + /* OK, we processed all DNSKEYs, and there are NSEC/NSEC3 RRs, look at those now. */ + phase = PHASE_NSEC; + continue; + } + + if (phase != PHASE_ALL) { + /* OK, we processed all DNSKEYs and NSEC/NSEC3 RRs, look at all the rest now. Note that in this + * third phase we start to remove RRs we couldn't validate. */ + phase = PHASE_ALL; + continue; } + /* We're done */ break; } @@ -1444,24 +2891,131 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { t->answer = validated; validated = NULL; - t->answer_authenticated = true; - t->dnssec_result = DNSSEC_VALIDATED; + /* At this point the answer only contains validated + * RRsets. Now, let's see if it actually answers the question + * we asked. If so, great! If it doesn't, then see if + * NSEC/NSEC3 can prove this. */ + r = dns_transaction_has_positive_answer(t, &flags); + if (r > 0) { + /* Yes, it answers the question! */ + + if (flags & DNS_ANSWER_AUTHENTICATED) { + /* The answer is fully authenticated, yay. */ + t->answer_dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_authenticated = true; + } else { + /* The answer is not fully authenticated. */ + t->answer_dnssec_result = DNSSEC_UNSIGNED; + t->answer_authenticated = false; + } + + } else if (r == 0) { + DnssecNsecResult nr; + bool authenticated = false; + + /* Bummer! Let's check NSEC/NSEC3 */ + r = dnssec_nsec_test(t->answer, t->key, &nr, &authenticated, &t->answer_nsec_ttl); + if (r < 0) + return r; + + switch (nr) { + + case DNSSEC_NSEC_NXDOMAIN: + /* NSEC proves the domain doesn't exist. Very good. */ + log_debug("Proved NXDOMAIN via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t)); + t->answer_dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_NXDOMAIN; + t->answer_authenticated = authenticated; + + manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, t->key); + break; + + case DNSSEC_NSEC_NODATA: + /* NSEC proves that there's no data here, very good. */ + log_debug("Proved NODATA via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t)); + t->answer_dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_authenticated = authenticated; + + manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, t->key); + break; + + case DNSSEC_NSEC_OPTOUT: + /* NSEC3 says the data might not be signed */ + log_debug("Data is NSEC3 opt-out via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t)); + t->answer_dnssec_result = DNSSEC_UNSIGNED; + t->answer_authenticated = false; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, t->key); + break; + + case DNSSEC_NSEC_NO_RR: + /* No NSEC data? Bummer! */ + + r = dns_transaction_requires_nsec(t); + if (r < 0) + return r; + if (r > 0) { + t->answer_dnssec_result = DNSSEC_NO_SIGNATURE; + manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, t->key); + } else { + t->answer_dnssec_result = DNSSEC_UNSIGNED; + t->answer_authenticated = false; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, t->key); + } + + break; + + case DNSSEC_NSEC_UNSUPPORTED_ALGORITHM: + /* We don't know the NSEC3 algorithm used? */ + t->answer_dnssec_result = DNSSEC_UNSUPPORTED_ALGORITHM; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, t->key); + break; + + case DNSSEC_NSEC_FOUND: + case DNSSEC_NSEC_CNAME: + /* NSEC says it needs to be there, but we couldn't find it? Bummer! */ + t->answer_dnssec_result = DNSSEC_NSEC_MISMATCH; + manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, t->key); + break; + + default: + assert_not_reached("Unexpected NSEC result."); + } + } + return 1; } +const char *dns_transaction_key_string(DnsTransaction *t) { + assert(t); + + if (!t->key_string) { + if (dns_resource_key_to_string(t->key, &t->key_string) < 0) + return "n/a"; + } + + return strstrip(t->key_string); +} + static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] = { [DNS_TRANSACTION_NULL] = "null", [DNS_TRANSACTION_PENDING] = "pending", [DNS_TRANSACTION_VALIDATING] = "validating", - [DNS_TRANSACTION_FAILURE] = "failure", + [DNS_TRANSACTION_RCODE_FAILURE] = "rcode-failure", [DNS_TRANSACTION_SUCCESS] = "success", [DNS_TRANSACTION_NO_SERVERS] = "no-servers", [DNS_TRANSACTION_TIMEOUT] = "timeout", [DNS_TRANSACTION_ATTEMPTS_MAX_REACHED] = "attempts-max-reached", [DNS_TRANSACTION_INVALID_REPLY] = "invalid-reply", - [DNS_TRANSACTION_RESOURCES] = "resources", + [DNS_TRANSACTION_ERRNO] = "errno", [DNS_TRANSACTION_ABORTED] = "aborted", [DNS_TRANSACTION_DNSSEC_FAILED] = "dnssec-failed", + [DNS_TRANSACTION_NO_TRUST_ANCHOR] = "no-trust-anchor", + [DNS_TRANSACTION_RR_TYPE_UNSUPPORTED] = "rr-type-unsupported", + [DNS_TRANSACTION_NETWORK_DOWN] = "network-down", + [DNS_TRANSACTION_NOT_FOUND] = "not-found", }; DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState); diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h index 2328e7937c..b6c5b2861c 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -29,15 +29,19 @@ enum DnsTransactionState { DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING, - DNS_TRANSACTION_FAILURE, + DNS_TRANSACTION_RCODE_FAILURE, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_NO_SERVERS, DNS_TRANSACTION_TIMEOUT, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, DNS_TRANSACTION_INVALID_REPLY, - DNS_TRANSACTION_RESOURCES, + DNS_TRANSACTION_ERRNO, DNS_TRANSACTION_ABORTED, DNS_TRANSACTION_DNSSEC_FAILED, + DNS_TRANSACTION_NO_TRUST_ANCHOR, + DNS_TRANSACTION_RR_TYPE_UNSUPPORTED, + DNS_TRANSACTION_NETWORK_DOWN, + DNS_TRANSACTION_NOT_FOUND, /* like NXDOMAIN, but when LLMNR/TCP connections fail */ _DNS_TRANSACTION_STATE_MAX, _DNS_TRANSACTION_STATE_INVALID = -1 }; @@ -62,23 +66,38 @@ struct DnsTransaction { DnsScope *scope; DnsResourceKey *key; + char *key_string; DnsTransactionState state; - DnssecResult dnssec_result; uint16_t id; - bool initial_jitter_scheduled; - bool initial_jitter_elapsed; + bool tried_stream:1; + + bool initial_jitter_scheduled:1; + bool initial_jitter_elapsed:1; DnsPacket *sent, *received; DnsAnswer *answer; int answer_rcode; + DnssecResult answer_dnssec_result; DnsTransactionSource answer_source; + uint32_t answer_nsec_ttl; + int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ + + /* Indicates whether the primary answer is authenticated, + * i.e. whether the RRs from answer which directly match the + * question are authenticated, or, if there are none, whether + * the NODATA or NXDOMAIN case is. It says nothing about + * additional RRs listed in the answer, however they have + * their own DNS_ANSWER_AUTHORIZED FLAGS. Note that this bit + * is defined different than the AD bit in DNS packets, as + * that covers more than just the actual primary answer. */ bool answer_authenticated; - /* Contains DS and DNSKEY RRs we already verified and need to authenticate this reply */ + /* Contains DNSKEY, DS, SOA RRs we already verified and need + * to authenticate this reply */ DnsAnswer *validated_keys; usec_t start_usec; @@ -86,17 +105,18 @@ struct DnsTransaction { sd_event_source *timeout_event_source; unsigned n_attempts; + /* UDP connection logic, if we need it */ int dns_udp_fd; sd_event_source *dns_udp_event_source; + /* TCP connection logic, if we need it */ + DnsStream *stream; + /* The active server */ DnsServer *server; /* The features of the DNS server at time of transaction start */ - DnsServerFeatureLevel current_features; - - /* TCP connection logic, if we need it */ - DnsStream *stream; + DnsServerFeatureLevel current_feature_level; /* Query candidates this transaction is referenced by and that * shall be notified about this specific transaction @@ -125,7 +145,7 @@ struct DnsTransaction { int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key); DnsTransaction* dns_transaction_free(DnsTransaction *t); -void dns_transaction_gc(DnsTransaction *t); +bool dns_transaction_gc(DnsTransaction *t); int dns_transaction_go(DnsTransaction *t); void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p); @@ -135,6 +155,8 @@ void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source); int dns_transaction_validate_dnssec(DnsTransaction *t); int dns_transaction_request_dnssec_keys(DnsTransaction *t); +const char *dns_transaction_key_string(DnsTransaction *t); + const char* dns_transaction_state_to_string(DnsTransactionState p) _const_; DnsTransactionState dns_transaction_state_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c index e55bdaa1ed..02d7ac91e1 100644 --- a/src/resolve/resolved-dns-trust-anchor.c +++ b/src/resolve/resolved-dns-trust-anchor.c @@ -19,26 +19,56 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <sd-messages.h> + #include "alloc-util.h" +#include "conf-files.h" +#include "def.h" +#include "dns-domain.h" +#include "fd-util.h" +#include "fileio.h" +#include "hexdecoct.h" +#include "parse-util.h" #include "resolved-dns-trust-anchor.h" +#include "resolved-dns-dnssec.h" +#include "set.h" +#include "string-util.h" +#include "strv.h" + +static const char trust_anchor_dirs[] = CONF_PATHS_NULSTR("dnssec-trust-anchors.d"); -/* The DS RR from https://data.iana.org/root-anchors/root-anchors.xml */ +/* The DS RR from https://data.iana.org/root-anchors/root-anchors.xml, retrieved December 2015 */ static const uint8_t root_digest[] = { 0x49, 0xAA, 0xC1, 0x1D, 0x7B, 0x6F, 0x64, 0x46, 0x70, 0x2E, 0x54, 0xA1, 0x60, 0x73, 0x71, 0x60, 0x7A, 0x1A, 0x41, 0x85, 0x52, 0x00, 0xFD, 0x2C, 0xE1, 0xCD, 0xDE, 0x32, 0xF2, 0x4E, 0x8F, 0xB5 }; -int dns_trust_anchor_load(DnsTrustAnchor *d) { +static bool dns_trust_anchor_knows_domain_positive(DnsTrustAnchor *d, const char *name) { + assert(d); + + /* Returns true if there's an entry for the specified domain + * name in our trust anchor */ + + return + hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DNSKEY, name)) || + hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, name)); +} + +static int dns_trust_anchor_add_builtin_positive(DnsTrustAnchor *d) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; int r; assert(d); - r = hashmap_ensure_allocated(&d->by_key, &dns_resource_key_hash_ops); + r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops); if (r < 0) return r; - if (hashmap_get(d->by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, "."))) + /* Only add the built-in trust anchor if there's neither a DS + * nor a DNSKEY defined for the root domain. That way users + * have an easy way to override the root domain DS/DNSKEY + * data. */ + if (dns_trust_anchor_knows_domain_positive(d, ".")) return 0; /* Add the RR from https://data.iana.org/root-anchors/root-anchors.xml */ @@ -58,30 +88,445 @@ int dns_trust_anchor_load(DnsTrustAnchor *d) { if (!answer) return -ENOMEM; - r = dns_answer_add(answer, rr, 0); + r = dns_answer_add(answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + r = hashmap_put(d->positive_by_key, rr->key, answer); if (r < 0) return r; - r = hashmap_put(d->by_key, rr->key, answer); + answer = NULL; + return 0; +} + +static int dns_trust_anchor_add_builtin_negative(DnsTrustAnchor *d) { + + static const char private_domains[] = + /* RFC 6761 says that .test is a special domain for + * testing and not to be installed in the root zone */ + "test\0" + + /* RFC 6761 says that these reverse IP lookup ranges + * are for private addresses, and hence should not + * show up in the root zone */ + "10.in-addr.arpa\0" + "16.172.in-addr.arpa\0" + "17.172.in-addr.arpa\0" + "18.172.in-addr.arpa\0" + "19.172.in-addr.arpa\0" + "20.172.in-addr.arpa\0" + "21.172.in-addr.arpa\0" + "22.172.in-addr.arpa\0" + "23.172.in-addr.arpa\0" + "24.172.in-addr.arpa\0" + "25.172.in-addr.arpa\0" + "26.172.in-addr.arpa\0" + "27.172.in-addr.arpa\0" + "28.172.in-addr.arpa\0" + "29.172.in-addr.arpa\0" + "30.172.in-addr.arpa\0" + "31.172.in-addr.arpa\0" + "168.192.in-addr.arpa\0" + + /* RFC 6762 reserves the .local domain for Multicast + * DNS, it hence cannot appear in the root zone. (Note + * that we by default do not route .local traffic to + * DNS anyway, except when a configured search domain + * suggests so.) */ + "local\0" + + /* These two are well known, popular private zone + * TLDs, that are blocked from delegation, according + * to: + * http://icannwiki.com/Name_Collision#NGPC_Resolution + * + * There's also ongoing work on making this official + * in an RRC: + * https://www.ietf.org/archive/id/draft-chapin-additional-reserved-tlds-02.txt */ + "home\0" + "corp\0" + + /* The following four TLDs are suggested for private + * zones in RFC 6762, Appendix G, and are hence very + * unlikely to be made official TLDs any day soon */ + "lan\0" + "intranet\0" + "internal\0" + "private\0"; + + const char *name; + int r; + + assert(d); + + /* Only add the built-in trust anchor if there's no negative + * trust anchor defined at all. This enables easy overriding + * of negative trust anchors. */ + + if (set_size(d->negative_by_name) > 0) + return 0; + + r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops); if (r < 0) return r; + /* We add a couple of domains as default negative trust + * anchors, where it's very unlikely they will be installed in + * the root zone. If they exist they must be private, and thus + * unsigned. */ + + NULSTR_FOREACH(name, private_domains) { + + if (dns_trust_anchor_knows_domain_positive(d, name)) + continue; + + r = set_put_strdup(d->negative_by_name, name); + if (r < 0) + return r; + } + + return 0; +} + +static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ char *domain = NULL, *class = NULL, *type = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + DnsAnswer *old_answer = NULL; + const char *p = s; + int r; + + assert(d); + assert(line); + + r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES); + if (r < 0) + return log_warning_errno(r, "Unable to parse domain in line %s:%u: %m", path, line); + + if (!dns_name_is_valid(domain)) { + log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line); + return -EINVAL; + } + + r = extract_many_words(&p, NULL, 0, &class, &type, NULL); + if (r < 0) + return log_warning_errno(r, "Unable to parse class and type in line %s:%u: %m", path, line); + if (r != 2) { + log_warning("Missing class or type in line %s:%u", path, line); + return -EINVAL; + } + + if (!strcaseeq(class, "IN")) { + log_warning("RR class %s is not supported, ignoring line %s:%u.", class, path, line); + return -EINVAL; + } + + if (strcaseeq(type, "DS")) { + _cleanup_free_ char *key_tag = NULL, *algorithm = NULL, *digest_type = NULL, *digest = NULL; + _cleanup_free_ void *dd = NULL; + uint16_t kt; + int a, dt; + size_t l; + + r = extract_many_words(&p, NULL, 0, &key_tag, &algorithm, &digest_type, &digest, NULL); + if (r < 0) { + log_warning_errno(r, "Failed to parse DS parameters on line %s:%u: %m", path, line); + return -EINVAL; + } + if (r != 4) { + log_warning("Missing DS parameters on line %s:%u", path, line); + return -EINVAL; + } + + r = safe_atou16(key_tag, &kt); + if (r < 0) + return log_warning_errno(r, "Failed to parse DS key tag %s on line %s:%u: %m", key_tag, path, line); + + a = dnssec_algorithm_from_string(algorithm); + if (a < 0) { + log_warning("Failed to parse DS algorithm %s on line %s:%u", algorithm, path, line); + return -EINVAL; + } + + dt = dnssec_digest_from_string(digest_type); + if (dt < 0) { + log_warning("Failed to parse DS digest type %s on line %s:%u", digest_type, path, line); + return -EINVAL; + } + + r = unhexmem(digest, strlen(digest), &dd, &l); + if (r < 0) { + log_warning("Failed to parse DS digest %s on line %s:%u", digest, path, line); + return -EINVAL; + } + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, domain); + if (!rr) + return log_oom(); + + rr->ds.key_tag = kt; + rr->ds.algorithm = a; + rr->ds.digest_type = dt; + rr->ds.digest_size = l; + rr->ds.digest = dd; + dd = NULL; + + } else if (strcaseeq(type, "DNSKEY")) { + _cleanup_free_ char *flags = NULL, *protocol = NULL, *algorithm = NULL, *key = NULL; + _cleanup_free_ void *k = NULL; + uint16_t f; + size_t l; + int a; + + r = extract_many_words(&p, NULL, 0, &flags, &protocol, &algorithm, &key, NULL); + if (r < 0) + return log_warning_errno(r, "Failed to parse DNSKEY parameters on line %s:%u: %m", path, line); + if (r != 4) { + log_warning("Missing DNSKEY parameters on line %s:%u", path, line); + return -EINVAL; + } + + if (!streq(protocol, "3")) { + log_warning("DNSKEY Protocol is not 3 on line %s:%u", path, line); + return -EINVAL; + } + + r = safe_atou16(flags, &f); + if (r < 0) + return log_warning_errno(r, "Failed to parse DNSKEY flags field %s on line %s:%u", flags, path, line); + if ((f & DNSKEY_FLAG_ZONE_KEY) == 0) { + log_warning("DNSKEY lacks zone key bit set on line %s:%u", path, line); + return -EINVAL; + } + if ((f & DNSKEY_FLAG_REVOKE)) { + log_warning("DNSKEY is already revoked on line %s:%u", path, line); + return -EINVAL; + } + + a = dnssec_algorithm_from_string(algorithm); + if (a < 0) { + log_warning("Failed to parse DNSKEY algorithm %s on line %s:%u", algorithm, path, line); + return -EINVAL; + } + + r = unbase64mem(key, strlen(key), &k, &l); + if (r < 0) + return log_warning_errno(r, "Failed to parse DNSKEY key data %s on line %s:%u", key, path, line); + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, domain); + if (!rr) + return log_oom(); + + rr->dnskey.flags = f; + rr->dnskey.protocol = 3; + rr->dnskey.algorithm = a; + rr->dnskey.key_size = l; + rr->dnskey.key = k; + k = NULL; + + } else { + log_warning("RR type %s is not supported, ignoring line %s:%u.", type, path, line); + return -EINVAL; + } + + if (!isempty(p)) { + log_warning("Trailing garbage on line %s:%u, ignoring line.", path, line); + return -EINVAL; + } + + r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops); + if (r < 0) + return log_oom(); + + old_answer = hashmap_get(d->positive_by_key, rr->key); + answer = dns_answer_ref(old_answer); + + r = dns_answer_add_extend(&answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return log_error_errno(r, "Failed to add trust anchor RR: %m"); + + r = hashmap_replace(d->positive_by_key, rr->key, answer); + if (r < 0) + return log_error_errno(r, "Failed to add answer to trust anchor: %m"); + + old_answer = dns_answer_unref(old_answer); answer = NULL; + + return 0; +} + +static int dns_trust_anchor_load_negative(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) { + _cleanup_free_ char *domain = NULL; + const char *p = s; + int r; + + assert(d); + assert(line); + + r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES); + if (r < 0) + return log_warning_errno(r, "Unable to parse line %s:%u: %m", path, line); + + if (!dns_name_is_valid(domain)) { + log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line); + return -EINVAL; + } + + if (!isempty(p)) { + log_warning("Trailing garbage at line %s:%u, ignoring line.", path, line); + return -EINVAL; + } + + r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops); + if (r < 0) + return log_oom(); + + r = set_put(d->negative_by_name, domain); + if (r < 0) + return log_oom(); + if (r > 0) + domain = NULL; + + return 0; +} + +static int dns_trust_anchor_load_files( + DnsTrustAnchor *d, + const char *suffix, + int (*loader)(DnsTrustAnchor *d, const char *path, unsigned n, const char *line)) { + + _cleanup_strv_free_ char **files = NULL; + char **f; + int r; + + assert(d); + assert(suffix); + assert(loader); + + r = conf_files_list_nulstr(&files, suffix, NULL, trust_anchor_dirs); + if (r < 0) + return log_error_errno(r, "Failed to enumerate %s trust anchor files: %m", suffix); + + STRV_FOREACH(f, files) { + _cleanup_fclose_ FILE *g = NULL; + char line[LINE_MAX]; + unsigned n = 0; + + g = fopen(*f, "r"); + if (!g) { + if (errno == ENOENT) + continue; + + log_warning_errno(errno, "Failed to open %s: %m", *f); + continue; + } + + FOREACH_LINE(line, g, log_warning_errno(errno, "Failed to read %s, ignoring: %m", *f)) { + char *l; + + n++; + + l = strstrip(line); + if (isempty(l)) + continue; + + if (*l == ';') + continue; + + (void) loader(d, *f, n, l); + } + } + + return 0; +} + +static int domain_name_cmp(const void *a, const void *b) { + char **x = (char**) a, **y = (char**) b; + + return dns_name_compare_func(*x, *y); +} + +static int dns_trust_anchor_dump(DnsTrustAnchor *d) { + DnsAnswer *a; + Iterator i; + + assert(d); + + if (hashmap_isempty(d->positive_by_key)) + log_info("No positive trust anchors defined."); + else { + log_info("Positive Trust Anchors:"); + HASHMAP_FOREACH(a, d->positive_by_key, i) { + DnsResourceRecord *rr; + + DNS_ANSWER_FOREACH(rr, a) + log_info("%s", dns_resource_record_to_string(rr)); + } + } + + if (set_isempty(d->negative_by_name)) + log_info("No negative trust anchors defined."); + else { + _cleanup_free_ char **l = NULL, *j = NULL; + + l = set_get_strv(d->negative_by_name); + if (!l) + return log_oom(); + + qsort_safe(l, set_size(d->negative_by_name), sizeof(char*), domain_name_cmp); + + j = strv_join(l, " "); + if (!j) + return log_oom(); + + log_info("Negative trust anchors: %s", j); + } + + return 0; +} + +int dns_trust_anchor_load(DnsTrustAnchor *d) { + int r; + + assert(d); + + /* If loading things from disk fails, we don't consider this fatal */ + (void) dns_trust_anchor_load_files(d, ".positive", dns_trust_anchor_load_positive); + (void) dns_trust_anchor_load_files(d, ".negative", dns_trust_anchor_load_negative); + + /* However, if the built-in DS fails, then we have a problem. */ + r = dns_trust_anchor_add_builtin_positive(d); + if (r < 0) + return log_error_errno(r, "Failed to add built-in positive trust anchor: %m"); + + r = dns_trust_anchor_add_builtin_negative(d); + if (r < 0) + return log_error_errno(r, "Failed to add built-in negative trust anchor: %m"); + + dns_trust_anchor_dump(d); + return 0; } void dns_trust_anchor_flush(DnsTrustAnchor *d) { DnsAnswer *a; + DnsResourceRecord *rr; assert(d); - while ((a = hashmap_steal_first(d->by_key))) + while ((a = hashmap_steal_first(d->positive_by_key))) dns_answer_unref(a); + d->positive_by_key = hashmap_free(d->positive_by_key); + + while ((rr = set_steal_first(d->revoked_by_rr))) + dns_resource_record_unref(rr); + d->revoked_by_rr = set_free(d->revoked_by_rr); - d->by_key = hashmap_free(d->by_key); + d->negative_by_name = set_free_free(d->negative_by_name); } -int dns_trust_anchor_lookup(DnsTrustAnchor *d, DnsResourceKey *key, DnsAnswer **ret) { +int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey *key, DnsAnswer **ret) { DnsAnswer *a; assert(d); @@ -92,10 +537,209 @@ int dns_trust_anchor_lookup(DnsTrustAnchor *d, DnsResourceKey *key, DnsAnswer ** if (!IN_SET(key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY)) return 0; - a = hashmap_get(d->by_key, key); + a = hashmap_get(d->positive_by_key, key); if (!a) return 0; *ret = dns_answer_ref(a); return 1; } + +int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name) { + assert(d); + assert(name); + + return set_contains(d->negative_by_name, name); +} + +static int dns_trust_anchor_revoked_put(DnsTrustAnchor *d, DnsResourceRecord *rr) { + int r; + + assert(d); + + r = set_ensure_allocated(&d->revoked_by_rr, &dns_resource_record_hash_ops); + if (r < 0) + return r; + + r = set_put(d->revoked_by_rr, rr); + if (r < 0) + return r; + if (r > 0) + dns_resource_record_ref(rr); + + return r; +} + +static int dns_trust_anchor_remove_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) { + _cleanup_(dns_answer_unrefp) DnsAnswer *new_answer = NULL; + DnsAnswer *old_answer; + int r; + + /* Remember that this is a revoked trust anchor RR */ + r = dns_trust_anchor_revoked_put(d, rr); + if (r < 0) + return r; + + /* Remove this from the positive trust anchor */ + old_answer = hashmap_get(d->positive_by_key, rr->key); + if (!old_answer) + return 0; + + new_answer = dns_answer_ref(old_answer); + + r = dns_answer_remove_by_rr(&new_answer, rr); + if (r <= 0) + return r; + + /* We found the key! Warn the user */ + log_struct(LOG_WARNING, + LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED), + LOG_MESSAGE("DNSSEC Trust anchor %s has been revoked. Please update the trust anchor, or upgrade your operating system."), strna(dns_resource_record_to_string(rr)), + "TRUST_ANCHOR=%s", dns_resource_record_to_string(rr), + NULL); + + if (dns_answer_size(new_answer) <= 0) { + assert_se(hashmap_remove(d->positive_by_key, rr->key) == old_answer); + dns_answer_unref(old_answer); + return 1; + } + + r = hashmap_replace(d->positive_by_key, new_answer->items[0].rr->key, new_answer); + if (r < 0) + return r; + + new_answer = NULL; + dns_answer_unref(old_answer); + return 1; +} + +static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor *d, DnsResourceRecord *revoked_dnskey) { + DnsAnswer *a; + int r; + + assert(d); + assert(revoked_dnskey); + assert(revoked_dnskey->key->type == DNS_TYPE_DNSKEY); + assert(revoked_dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE); + + a = hashmap_get(d->positive_by_key, revoked_dnskey->key); + if (a) { + DnsResourceRecord *anchor; + + /* First, look for the precise DNSKEY in our trust anchor database */ + + DNS_ANSWER_FOREACH(anchor, a) { + + if (anchor->dnskey.protocol != revoked_dnskey->dnskey.protocol) + continue; + + if (anchor->dnskey.algorithm != revoked_dnskey->dnskey.algorithm) + continue; + + if (anchor->dnskey.key_size != revoked_dnskey->dnskey.key_size) + continue; + + /* Note that we allow the REVOKE bit to be + * different! It will be set in the revoked + * key, but unset in our version of it */ + if (((anchor->dnskey.flags ^ revoked_dnskey->dnskey.flags) | DNSKEY_FLAG_REVOKE) != DNSKEY_FLAG_REVOKE) + continue; + + if (memcmp(anchor->dnskey.key, revoked_dnskey->dnskey.key, anchor->dnskey.key_size) != 0) + continue; + + dns_trust_anchor_remove_revoked(d, anchor); + break; + } + } + + a = hashmap_get(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(revoked_dnskey->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(revoked_dnskey->key))); + if (a) { + DnsResourceRecord *anchor; + + /* Second, look for DS RRs matching this DNSKEY in our trust anchor database */ + + DNS_ANSWER_FOREACH(anchor, a) { + + /* We set mask_revoke to true here, since our + * DS fingerprint will be the one of the + * unrevoked DNSKEY, but the one we got passed + * here has the bit set. */ + r = dnssec_verify_dnskey_by_ds(revoked_dnskey, anchor, true); + if (r < 0) + return r; + if (r == 0) + continue; + + dns_trust_anchor_remove_revoked(d, anchor); + break; + } + } + + return 0; +} + +int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsResourceRecord *dnskey, DnsAnswer *rrs) { + DnsResourceRecord *rrsig; + int r; + + assert(d); + assert(dnskey); + + /* Looks if "dnskey" is a self-signed RR that has been revoked + * and matches one of our trust anchor entries. If so, removes + * it from the trust anchor and returns > 0. */ + + if (dnskey->key->type != DNS_TYPE_DNSKEY) + return 0; + + /* Is this DNSKEY revoked? */ + if ((dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE) == 0) + return 0; + + /* Could this be interesting to us at all? If not, + * there's no point in looking for and verifying a + * self-signed RRSIG. */ + if (!dns_trust_anchor_knows_domain_positive(d, DNS_RESOURCE_KEY_NAME(dnskey->key))) + return 0; + + /* Look for a self-signed RRSIG in the other rrs belonging to this DNSKEY */ + DNS_ANSWER_FOREACH(rrsig, rrs) { + DnssecResult result; + + if (rrsig->key->type != DNS_TYPE_RRSIG) + continue; + + r = dnssec_rrsig_match_dnskey(rrsig, dnskey, true); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_verify_rrset(rrs, dnskey->key, rrsig, dnskey, USEC_INFINITY, &result); + if (r < 0) + return r; + if (result != DNSSEC_VALIDATED) + continue; + + /* Bingo! This is a revoked self-signed DNSKEY. Let's + * see if this precise one exists in our trust anchor + * database, too. */ + r = dns_trust_anchor_check_revoked_one(d, dnskey); + if (r < 0) + return r; + + return 1; + } + + return 0; +} + +int dns_trust_anchor_is_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) { + assert(d); + + if (!IN_SET(rr->key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY)) + return 0; + + return set_contains(d->revoked_by_rr, rr); +} diff --git a/src/resolve/resolved-dns-trust-anchor.h b/src/resolve/resolved-dns-trust-anchor.h index 06f3723914..5d137faae1 100644 --- a/src/resolve/resolved-dns-trust-anchor.h +++ b/src/resolve/resolved-dns-trust-anchor.h @@ -30,10 +30,16 @@ typedef struct DnsTrustAnchor DnsTrustAnchor; /* This contains a fixed database mapping domain names to DS or DNSKEY records. */ struct DnsTrustAnchor { - Hashmap *by_key; + Hashmap *positive_by_key; + Set *negative_by_name; + Set *revoked_by_rr; }; int dns_trust_anchor_load(DnsTrustAnchor *d); void dns_trust_anchor_flush(DnsTrustAnchor *d); -int dns_trust_anchor_lookup(DnsTrustAnchor *d, DnsResourceKey* key, DnsAnswer **answer); +int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey* key, DnsAnswer **answer); +int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name); + +int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsResourceRecord *dnskey, DnsAnswer *rrs); +int dns_trust_anchor_is_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr); diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index 8046e2ed34..f60b0bddc1 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -223,9 +223,9 @@ int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) { assert(s); assert(rr); - if (rr->key->class == DNS_CLASS_ANY) + if (dns_class_is_pseudo(rr->key->class)) return -EINVAL; - if (rr->key->type == DNS_TYPE_ANY) + if (dns_type_is_pseudo(rr->key->type)) return -EINVAL; existing = dns_zone_get(z, rr); @@ -386,7 +386,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns if (k < 0) return k; if (k > 0) { - r = dns_answer_add(answer, j->rr, 0); + r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; @@ -412,7 +412,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns if (j->state != DNS_ZONE_ITEM_PROBING) tentative = false; - r = dns_answer_add(answer, j->rr, 0); + r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED); if (r < 0) return r; } @@ -471,15 +471,12 @@ return_empty: } void dns_zone_item_conflict(DnsZoneItem *i) { - _cleanup_free_ char *pretty = NULL; - assert(i); if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED)) return; - dns_resource_record_to_string(i->rr, &pretty); - log_info("Detected conflict on %s", strna(pretty)); + log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr))); dns_zone_item_probe_stop(i); @@ -492,8 +489,6 @@ void dns_zone_item_conflict(DnsZoneItem *i) { } void dns_zone_item_notify(DnsZoneItem *i) { - _cleanup_free_ char *pretty = NULL; - assert(i); assert(i->probe_transaction); @@ -530,15 +525,13 @@ void dns_zone_item_notify(DnsZoneItem *i) { log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost."); } - dns_resource_record_to_string(i->rr, &pretty); - log_debug("Record %s successfully probed.", strna(pretty)); + log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr))); dns_zone_item_probe_stop(i); i->state = DNS_ZONE_ITEM_ESTABLISHED; } static int dns_zone_item_verify(DnsZoneItem *i) { - _cleanup_free_ char *pretty = NULL; int r; assert(i); @@ -546,8 +539,7 @@ static int dns_zone_item_verify(DnsZoneItem *i) { if (i->state != DNS_ZONE_ITEM_ESTABLISHED) return 0; - dns_resource_record_to_string(i->rr, &pretty); - log_debug("Verifying RR %s", strna(pretty)); + log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr))); i->state = DNS_ZONE_ITEM_VERIFYING; r = dns_zone_item_probe_start(i); @@ -632,7 +624,6 @@ void dns_zone_verify_all(DnsZone *zone) { void dns_zone_dump(DnsZone *zone, FILE *f) { Iterator iterator; DnsZoneItem *i; - int r; if (!zone) return; @@ -644,10 +635,10 @@ void dns_zone_dump(DnsZone *zone, FILE *f) { DnsZoneItem *j; LIST_FOREACH(by_key, j, i) { - _cleanup_free_ char *t = NULL; + const char *t; - r = dns_resource_record_to_string(j->rr, &t); - if (r < 0) { + t = dns_resource_record_to_string(j->rr); + if (!t) { log_oom(); continue; } diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c new file mode 100644 index 0000000000..ee82c96822 --- /dev/null +++ b/src/resolve/resolved-etc-hosts.c @@ -0,0 +1,448 @@ +/*** + 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 "fd-util.h" +#include "fileio.h" +#include "hostname-util.h" +#include "resolved-etc-hosts.h" +#include "resolved-dns-synthesize.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" + +/* Recheck /etc/hosts at most once every 2s */ +#define ETC_HOSTS_RECHECK_USEC (2*USEC_PER_SEC) + +typedef struct EtcHostsItem { + int family; + union in_addr_union address; + + char **names; +} EtcHostsItem; + +typedef struct EtcHostsItemByName { + char *name; + + EtcHostsItem **items; + size_t n_items, n_allocated; +} EtcHostsItemByName; + +void manager_etc_hosts_flush(Manager *m) { + EtcHostsItem *item; + EtcHostsItemByName *bn; + + while ((item = set_steal_first(m->etc_hosts_by_address))) { + strv_free(item->names); + free(item); + } + + while ((bn = hashmap_steal_first(m->etc_hosts_by_name))) { + free(bn->name); + free(bn->items); + free(bn); + } + + m->etc_hosts_by_address = set_free(m->etc_hosts_by_address); + m->etc_hosts_by_name = hashmap_free(m->etc_hosts_by_name); + + m->etc_hosts_mtime = USEC_INFINITY; +} + +static void etc_hosts_item_hash_func(const void *p, struct siphash *state) { + const EtcHostsItem *item = p; + + siphash24_compress(&item->family, sizeof(item->family), state); + + if (item->family == AF_INET) + siphash24_compress(&item->address.in, sizeof(item->address.in), state); + else if (item->family == AF_INET6) + siphash24_compress(&item->address.in6, sizeof(item->address.in6), state); +} + +static int etc_hosts_item_compare_func(const void *a, const void *b) { + const EtcHostsItem *x = a, *y = b; + + if (x->family != y->family) + return x->family - y->family; + + if (x->family == AF_INET) + return memcmp(&x->address.in.s_addr, &y->address.in.s_addr, sizeof(struct in_addr)); + + if (x->family == AF_INET6) + return memcmp(&x->address.in6.s6_addr, &y->address.in6.s6_addr, sizeof(struct in6_addr)); + + return trivial_compare_func(a, b); +} + +static const struct hash_ops etc_hosts_item_ops = { + .hash = etc_hosts_item_hash_func, + .compare = etc_hosts_item_compare_func, +}; + +static int add_item(Manager *m, int family, const union in_addr_union *address, char **names) { + + EtcHostsItem key = { + .family = family, + .address = *address, + }; + EtcHostsItem *item; + char **n; + int r; + + assert(m); + assert(address); + + r = in_addr_is_null(family, address); + if (r < 0) + return r; + if (r > 0) + /* This is an 0.0.0.0 or :: item, which we assume means that we shall map the specified hostname to + * nothing. */ + item = NULL; + else { + /* If this is a normal address, then, simply add entry mapping it to the specified names */ + + item = set_get(m->etc_hosts_by_address, &key); + if (item) { + r = strv_extend_strv(&item->names, names, true); + if (r < 0) + return log_oom(); + } else { + + r = set_ensure_allocated(&m->etc_hosts_by_address, &etc_hosts_item_ops); + if (r < 0) + return log_oom(); + + item = new0(EtcHostsItem, 1); + if (!item) + return log_oom(); + + item->family = family; + item->address = *address; + item->names = names; + + r = set_put(m->etc_hosts_by_address, item); + if (r < 0) { + free(item); + return log_oom(); + } + } + } + + STRV_FOREACH(n, names) { + EtcHostsItemByName *bn; + + bn = hashmap_get(m->etc_hosts_by_name, *n); + if (!bn) { + r = hashmap_ensure_allocated(&m->etc_hosts_by_name, &dns_name_hash_ops); + if (r < 0) + return log_oom(); + + bn = new0(EtcHostsItemByName, 1); + if (!bn) + return log_oom(); + + bn->name = strdup(*n); + if (!bn->name) { + free(bn); + return log_oom(); + } + + r = hashmap_put(m->etc_hosts_by_name, bn->name, bn); + if (r < 0) { + free(bn->name); + free(bn); + return log_oom(); + } + } + + if (item) { + if (!GREEDY_REALLOC(bn->items, bn->n_allocated, bn->n_items+1)) + return log_oom(); + + bn->items[bn->n_items++] = item; + } + } + + return 0; +} + +static int parse_line(Manager *m, unsigned nr, const char *line) { + _cleanup_free_ char *address = NULL; + _cleanup_strv_free_ char **names = NULL; + union in_addr_union in; + bool suppressed = false; + int family, r; + + assert(m); + assert(line); + + r = extract_first_word(&line, &address, NULL, EXTRACT_RELAX); + if (r < 0) + return log_error_errno(r, "Couldn't extract address, in line /etc/hosts:%u.", nr); + if (r == 0) { + log_error("Premature end of line, in line /etc/hosts:%u.", nr); + return -EINVAL; + } + + r = in_addr_from_string_auto(address, &family, &in); + if (r < 0) + return log_error_errno(r, "Address '%s' is invalid, in line /etc/hosts:%u.", address, nr); + + for (;;) { + _cleanup_free_ char *name = NULL; + + r = extract_first_word(&line, &name, NULL, EXTRACT_RELAX); + if (r < 0) + return log_error_errno(r, "Couldn't extract host name, in line /etc/hosts:%u.", nr); + if (r == 0) + break; + + r = dns_name_is_valid(name); + if (r <= 0) + return log_error_errno(r, "Hostname %s is not valid, ignoring, in line /etc/hosts:%u.", name, nr); + + if (is_localhost(name)) { + /* Suppress the "localhost" line that is often seen */ + suppressed = true; + continue; + } + + r = strv_push(&names, name); + if (r < 0) + return log_oom(); + + name = NULL; + } + + if (strv_isempty(names)) { + + if (suppressed) + return 0; + + log_error("Line is missing any host names, in line /etc/hosts:%u.", nr); + return -EINVAL; + } + + /* Takes possession of the names strv */ + r = add_item(m, family, &in, names); + if (r < 0) + return r; + + names = NULL; + return r; +} + +int manager_etc_hosts_read(Manager *m) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + struct stat st; + usec_t ts; + unsigned nr = 0; + int r; + + assert_se(sd_event_now(m->event, clock_boottime_or_monotonic(), &ts) >= 0); + + /* See if we checked /etc/hosts recently already */ + if (m->etc_hosts_last != USEC_INFINITY && m->etc_hosts_last + ETC_HOSTS_RECHECK_USEC > ts) + return 0; + + m->etc_hosts_last = ts; + + if (m->etc_hosts_mtime != USEC_INFINITY) { + if (stat("/etc/hosts", &st) < 0) { + if (errno == ENOENT) { + r = 0; + goto clear; + } + + return log_error_errno(errno, "Failed to stat /etc/hosts: %m"); + } + + /* Did the mtime change? If not, there's no point in re-reading the file. */ + if (timespec_load(&st.st_mtim) == m->etc_hosts_mtime) + return 0; + } + + f = fopen("/etc/hosts", "re"); + if (!f) { + if (errno == ENOENT) { + r = 0; + goto clear; + } + + return log_error_errno(errno, "Failed to open /etc/hosts: %m"); + } + + /* Take the timestamp at the beginning of processing, so that any changes made later are read on the next + * invocation */ + r = fstat(fileno(f), &st); + if (r < 0) + return log_error_errno(errno, "Failed to fstat() /etc/hosts: %m"); + + manager_etc_hosts_flush(m); + + FOREACH_LINE(line, f, return log_error_errno(errno, "Failed to read /etc/hosts: %m")) { + char *l; + + nr ++; + + l = strstrip(line); + if (isempty(l)) + continue; + if (l[0] == '#') + continue; + + r = parse_line(m, nr, l); + if (r == -ENOMEM) /* On OOM we abandon the half-built-up structure. All other errors we ignore and proceed */ + goto clear; + } + + m->etc_hosts_mtime = timespec_load(&st.st_mtim); + m->etc_hosts_last = ts; + + return 1; + +clear: + manager_etc_hosts_flush(m); + return r; +} + +int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer) { + bool found_a = false, found_aaaa = false; + EtcHostsItemByName *bn; + EtcHostsItem k = {}; + DnsResourceKey *t; + const char *name; + unsigned i; + int r; + + assert(m); + assert(q); + assert(answer); + + r = manager_etc_hosts_read(m); + if (r < 0) + return r; + + name = dns_question_first_name(q); + if (!name) + return 0; + + r = dns_name_address(name, &k.family, &k.address); + if (r > 0) { + EtcHostsItem *item; + DnsResourceKey *found_ptr = NULL; + + item = set_get(m->etc_hosts_by_address, &k); + if (!item) + return 0; + + /* We have an address in /etc/hosts that matches the queried name. Let's return successful. Actual data + * we'll only return if the request was for PTR. */ + + DNS_QUESTION_FOREACH(t, q) { + if (!IN_SET(t->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) + continue; + if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY)) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(t), name); + if (r < 0) + return r; + if (r > 0) { + found_ptr = t; + break; + } + } + + if (found_ptr) { + char **n; + + r = dns_answer_reserve(answer, strv_length(item->names)); + if (r < 0) + return r; + + STRV_FOREACH(n, item->names) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new(found_ptr); + if (!rr) + return -ENOMEM; + + rr->ptr.name = strdup(*n); + if (!rr->ptr.name) + return -ENOMEM; + + r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + } + + return 1; + } + + bn = hashmap_get(m->etc_hosts_by_name, name); + if (!bn) + return 0; + + r = dns_answer_reserve(answer, bn->n_items); + if (r < 0) + return r; + + DNS_QUESTION_FOREACH(t, q) { + if (!IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_TYPE_ANY)) + continue; + if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY)) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(t), name); + if (r < 0) + return r; + if (r == 0) + continue; + + if (IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_ANY)) + found_a = true; + if (IN_SET(t->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) + found_aaaa = true; + + if (found_a && found_aaaa) + break; + } + + for (i = 0; i < bn->n_items; i++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + if ((found_a && bn->items[i]->family != AF_INET) && + (found_aaaa && bn->items[i]->family != AF_INET6)) + continue; + + r = dns_resource_record_new_address(&rr, bn->items[i]->family, &bn->items[i]->address, bn->name); + if (r < 0) + return r; + + r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 1; +} diff --git a/src/resolve/resolved-etc-hosts.h b/src/resolve/resolved-etc-hosts.h new file mode 100644 index 0000000000..9d5a175f18 --- /dev/null +++ b/src/resolve/resolved-etc-hosts.h @@ -0,0 +1,28 @@ +#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 "resolved-manager.h" +#include "resolved-dns-question.h" +#include "resolved-dns-answer.h" + +void manager_etc_hosts_flush(Manager *m); +int manager_etc_hosts_read(Manager *m); +int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer); diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf index c815eae850..82f26215df 100644 --- a/src/resolve/resolved-gperf.gperf +++ b/src/resolve/resolved-gperf.gperf @@ -14,8 +14,8 @@ struct ConfigPerfItem; %struct-type %includes %% -Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0 -Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0 -Resolve.Domains, config_parse_search_domains, 0, 0 -Resolve.LLMNR, config_parse_support, 0, offsetof(Manager, llmnr_support) -Resolve.DNSSEC, config_parse_dnssec, 0, 0 +Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0 +Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0 +Resolve.Domains, config_parse_search_domains, 0, 0 +Resolve.LLMNR, config_parse_resolve_support, 0, offsetof(Manager, llmnr_support) +Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode) diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c new file mode 100644 index 0000000000..e6b087f412 --- /dev/null +++ b/src/resolve/resolved-link-bus.c @@ -0,0 +1,552 @@ +/*-*- 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', "(sb)"); + if (r < 0) + return r; + + LIST_FOREACH(domains, d, l->search_domains) { + r = sd_bus_message_append(reply, "(sb)", d->name, d->route_only); + 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) { + Link *l = userdata; + int r; + + assert(message); + assert(l); + + r = sd_bus_message_enter_container(message, 'a', "(sb)"); + if (r < 0) + return r; + + for (;;) { + const char *name; + int route_only; + + r = sd_bus_message_read(message, "(sb)", &name, &route_only); + if (r < 0) + return r; + if (r == 0) + break; + + r = dns_name_is_valid(name); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid search domain %s", name); + if (!route_only && dns_name_is_root(name)) + 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); + + r = sd_bus_message_rewind(message, false); + if (r < 0) + return r; + + for (;;) { + DnsSearchDomain *d; + const char *name; + int route_only; + + r = sd_bus_message_read(message, "(sb)", &name, &route_only); + if (r < 0) + goto clear; + if (r == 0) + break; + + r = dns_search_domain_find(l->search_domains, name, &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, &d, DNS_SEARCH_DOMAIN_LINK, l, name); + if (r < 0) + goto clear; + } + + d->route_only = route_only; + } + + r = sd_bus_message_exit_container(message); + 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 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", "a(sb)", 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", "a(sb)", 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 84100bd988..7412e64622 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -46,7 +46,10 @@ int link_new(Manager *m, Link **ret, int ifindex) { return -ENOMEM; l->ifindex = ifindex; - l->llmnr_support = SUPPORT_YES; + l->llmnr_support = RESOLVE_SUPPORT_YES; + l->mdns_support = RESOLVE_SUPPORT_NO; + l->dnssec_mode = _DNSSEC_MODE_INVALID; + l->operstate = IF_OPER_UNKNOWN; r = hashmap_put(m->links, INT_TO_PTR(ifindex), l); if (r < 0) @@ -61,15 +64,27 @@ int link_new(Manager *m, Link **ret, int ifindex) { return 0; } +void link_flush_settings(Link *l) { + assert(l); + + l->llmnr_support = RESOLVE_SUPPORT_YES; + l->mdns_support = RESOLVE_SUPPORT_NO; + l->dnssec_mode = _DNSSEC_MODE_INVALID; + + dns_server_unlink_all(l->dns_servers); + dns_search_domain_unlink_all(l->search_domains); + + l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors); +} + Link *link_free(Link *l) { if (!l) return NULL; - dns_server_unlink_marked(l->dns_servers); - dns_search_domain_unlink_all(l->search_domains); + link_flush_settings(l); while (l->addresses) - link_address_free(l->addresses); + (void) link_address_free(l->addresses); if (l->manager) hashmap_remove(l->manager->links, INT_TO_PTR(l->ifindex)); @@ -84,12 +99,13 @@ Link *link_free(Link *l) { return NULL; } -static void link_allocate_scopes(Link *l) { +void link_allocate_scopes(Link *l) { int r; assert(l); - if (l->dns_servers) { + if (link_relevant(l, AF_UNSPEC, false) && + l->dns_servers) { if (!l->unicast_scope) { r = dns_scope_new(l->manager, &l->unicast_scope, l, DNS_PROTOCOL_DNS, AF_UNSPEC); if (r < 0) @@ -98,9 +114,9 @@ static void link_allocate_scopes(Link *l) { } else l->unicast_scope = dns_scope_free(l->unicast_scope); - if (link_relevant(l, AF_INET) && - l->llmnr_support != SUPPORT_NO && - l->manager->llmnr_support != SUPPORT_NO) { + if (link_relevant(l, AF_INET, true) && + l->llmnr_support != RESOLVE_SUPPORT_NO && + l->manager->llmnr_support != RESOLVE_SUPPORT_NO) { if (!l->llmnr_ipv4_scope) { r = dns_scope_new(l->manager, &l->llmnr_ipv4_scope, l, DNS_PROTOCOL_LLMNR, AF_INET); if (r < 0) @@ -109,9 +125,9 @@ static void link_allocate_scopes(Link *l) { } else l->llmnr_ipv4_scope = dns_scope_free(l->llmnr_ipv4_scope); - if (link_relevant(l, AF_INET6) && - l->llmnr_support != SUPPORT_NO && - l->manager->llmnr_support != SUPPORT_NO && + if (link_relevant(l, AF_INET6, true) && + l->llmnr_support != RESOLVE_SUPPORT_NO && + l->manager->llmnr_support != RESOLVE_SUPPORT_NO && socket_ipv6_is_supported()) { if (!l->llmnr_ipv6_scope) { r = dns_scope_new(l->manager, &l->llmnr_ipv6_scope, l, DNS_PROTOCOL_LLMNR, AF_INET6); @@ -121,9 +137,9 @@ static void link_allocate_scopes(Link *l) { } else l->llmnr_ipv6_scope = dns_scope_free(l->llmnr_ipv6_scope); - if (link_relevant(l, AF_INET) && - l->mdns_support != SUPPORT_NO && - l->manager->mdns_support != SUPPORT_NO) { + if (link_relevant(l, AF_INET, true) && + l->mdns_support != RESOLVE_SUPPORT_NO && + l->manager->mdns_support != RESOLVE_SUPPORT_NO) { if (!l->mdns_ipv4_scope) { r = dns_scope_new(l->manager, &l->mdns_ipv4_scope, l, DNS_PROTOCOL_MDNS, AF_INET); if (r < 0) @@ -132,9 +148,9 @@ static void link_allocate_scopes(Link *l) { } else l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope); - if (link_relevant(l, AF_INET6) && - l->mdns_support != SUPPORT_NO && - l->manager->mdns_support != SUPPORT_NO) { + if (link_relevant(l, AF_INET6, true) && + l->mdns_support != RESOLVE_SUPPORT_NO && + l->manager->mdns_support != RESOLVE_SUPPORT_NO) { if (!l->mdns_ipv6_scope) { r = dns_scope_new(l->manager, &l->mdns_ipv6_scope, l, DNS_PROTOCOL_MDNS, AF_INET6); if (r < 0) @@ -162,7 +178,8 @@ int link_update_rtnl(Link *l, sd_netlink_message *m) { if (r < 0) return r; - sd_netlink_message_read_u32(m, IFLA_MTU, &l->mtu); + (void) sd_netlink_message_read_u32(m, IFLA_MTU, &l->mtu); + (void) sd_netlink_message_read_u8(m, IFLA_OPERSTATE, &l->operstate); if (sd_netlink_message_read_string(m, IFLA_IFNAME, &n) >= 0) { strncpy(l->name, n, sizeof(l->name)-1); @@ -183,6 +200,10 @@ static int link_update_dns_servers(Link *l) { assert(l); r = sd_network_link_get_dns(l->ifindex, &nameservers); + if (r == -ENODATA) { + r = 0; + goto clear; + } if (r < 0) goto clear; @@ -222,55 +243,194 @@ static int link_update_llmnr_support(Link *l) { assert(l); r = sd_network_link_get_llmnr(l->ifindex, &b); + if (r == -ENODATA) { + r = 0; + goto clear; + } if (r < 0) goto clear; - r = parse_boolean(b); - if (r < 0) { - if (streq(b, "resolve")) - l->llmnr_support = SUPPORT_RESOLVE; - else - goto clear; + l->llmnr_support = resolve_support_from_string(b); + if (l->llmnr_support < 0) { + r = -EINVAL; + goto clear; + } + + return 0; + +clear: + l->llmnr_support = RESOLVE_SUPPORT_YES; + return r; +} - } else if (r > 0) - l->llmnr_support = SUPPORT_YES; - else - l->llmnr_support = SUPPORT_NO; +static int link_update_mdns_support(Link *l) { + _cleanup_free_ char *b = NULL; + int r; + + assert(l); + + r = sd_network_link_get_mdns(l->ifindex, &b); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + l->mdns_support = resolve_support_from_string(b); + if (l->mdns_support < 0) { + r = -EINVAL; + goto clear; + } return 0; clear: - l->llmnr_support = SUPPORT_YES; + l->mdns_support = RESOLVE_SUPPORT_NO; return r; } -static int link_update_search_domains(Link *l) { - _cleanup_strv_free_ char **domains = NULL; +void link_set_dnssec_mode(Link *l, DnssecMode mode) { + + assert(l); + + if (l->dnssec_mode == mode) + return; + + if ((l->dnssec_mode == _DNSSEC_MODE_INVALID) || + (l->dnssec_mode == DNSSEC_NO && mode != DNSSEC_NO) || + (l->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE && mode == DNSSEC_YES)) { + + /* When switching from non-DNSSEC mode to DNSSEC mode, flush the cache. Also when switching from the + * allow-downgrade mode to full DNSSEC mode, flush it too. */ + if (l->unicast_scope) + dns_cache_flush(&l->unicast_scope->cache); + } + + l->dnssec_mode = mode; +} + +static int link_update_dnssec_mode(Link *l) { + _cleanup_free_ char *m = NULL; + DnssecMode mode; + int r; + + assert(l); + + r = sd_network_link_get_dnssec(l->ifindex, &m); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + mode = dnssec_mode_from_string(m); + if (mode < 0) { + r = -EINVAL; + goto clear; + } + + link_set_dnssec_mode(l, mode); + + return 0; + +clear: + l->dnssec_mode = _DNSSEC_MODE_INVALID; + return r; +} + +static int link_update_dnssec_negative_trust_anchors(Link *l) { + _cleanup_strv_free_ char **ntas = NULL; + _cleanup_set_free_free_ Set *ns = NULL; char **i; int r; assert(l); - r = sd_network_link_get_domains(l->ifindex, &domains); + r = sd_network_link_get_dnssec_negative_trust_anchors(l->ifindex, &ntas); + if (r == -ENODATA) { + r = 0; + goto clear; + } if (r < 0) goto clear; - dns_search_domain_mark_all(l->search_domains); + ns = set_new(&dns_name_hash_ops); + if (!ns) + return -ENOMEM; + + STRV_FOREACH(i, ntas) { + r = set_put_strdup(ns, *i); + if (r < 0) + return r; + } + + set_free_free(l->dnssec_negative_trust_anchors); + l->dnssec_negative_trust_anchors = ns; + ns = NULL; + + return 0; + +clear: + l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors); + return r; +} + +static int link_update_search_domain_one(Link *l, const char *name, bool route_only) { + DnsSearchDomain *d; + int r; + + r = dns_search_domain_find(l->search_domains, name, &d); + if (r < 0) + return r; + if (r > 0) + dns_search_domain_move_back_and_unmark(d); + else { + r = dns_search_domain_new(l->manager, &d, DNS_SEARCH_DOMAIN_LINK, l, name); + if (r < 0) + return r; + } - STRV_FOREACH(i, domains) { - DnsSearchDomain *d; + d->route_only = route_only; + return 0; +} - r = dns_search_domain_find(l->search_domains, *i, &d); +static int link_update_search_domains(Link *l) { + _cleanup_strv_free_ char **sdomains = NULL, **rdomains = NULL; + char **i; + int r, q; + + assert(l); + + r = sd_network_link_get_search_domains(l->ifindex, &sdomains); + if (r < 0 && r != -ENODATA) + goto clear; + + q = sd_network_link_get_route_domains(l->ifindex, &rdomains); + if (q < 0 && q != -ENODATA) { + r = q; + goto clear; + } + + if (r == -ENODATA && q == -ENODATA) { + /* networkd knows nothing about this interface, and that's fine. */ + r = 0; + goto clear; + } + + dns_search_domain_mark_all(l->search_domains); + + STRV_FOREACH(i, sdomains) { + r = link_update_search_domain_one(l, *i, false); 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; - } + STRV_FOREACH(i, rdomains) { + r = link_update_search_domain_one(l, *i, true); + if (r < 0) + goto clear; } dns_search_domain_unlink_marked(l->search_domains); @@ -281,46 +441,117 @@ clear: return r; } -int link_update_monitor(Link *l) { +static int link_is_unmanaged(Link *l) { + _cleanup_free_ char *state = NULL; int r; assert(l); - link_update_dns_servers(l); - link_update_llmnr_support(l); - link_allocate_scopes(l); + r = sd_network_link_get_setup_state(l->ifindex, &state); + if (r == -ENODATA) + return 1; + if (r < 0) + return r; + + return STR_IN_SET(state, "pending", "unmanaged"); +} + +static void link_read_settings(Link *l) { + int r; + + assert(l); + + /* Read settings from networkd, except when networkd is not managing this interface. */ + + r = link_is_unmanaged(l); + if (r < 0) { + log_warning_errno(r, "Failed to determine whether interface %s is managed: %m", l->name); + return; + } + if (r > 0) { + + /* If this link used to be managed, but is now unmanaged, flush all our settings -- but only once. */ + if (l->is_managed) + link_flush_settings(l); + + l->is_managed = false; + return; + } + + l->is_managed = true; + + r = link_update_dns_servers(l); + if (r < 0) + log_warning_errno(r, "Failed to read DNS servers for interface %s, ignoring: %m", l->name); + + r = link_update_llmnr_support(l); + if (r < 0) + log_warning_errno(r, "Failed to read LLMNR support for interface %s, ignoring: %m", l->name); + + r = link_update_mdns_support(l); + if (r < 0) + log_warning_errno(r, "Failed to read mDNS support for interface %s, ignoring: %m", l->name); + + r = link_update_dnssec_mode(l); + if (r < 0) + log_warning_errno(r, "Failed to read DNSSEC mode for interface %s, ignoring: %m", l->name); + + r = link_update_dnssec_negative_trust_anchors(l); + if (r < 0) + log_warning_errno(r, "Failed to read DNSSEC negative trust anchors for interface %s, ignoring: %m", l->name); r = link_update_search_domains(l); if (r < 0) log_warning_errno(r, "Failed to read search domains for interface %s, ignoring: %m", l->name); +} +int link_update_monitor(Link *l) { + assert(l); + + link_read_settings(l); + link_allocate_scopes(l); link_add_rrs(l, false); return 0; } -bool link_relevant(Link *l, int family) { +bool link_relevant(Link *l, int family, bool local_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 local multicast traffic if it isn't a loopback or pointopoint device, has a link + * beat, can do multicast and has at least one link-local (or better) IP address. + * + * A link is relevant for non-multicast traffic if it isn't a loopback device, has a link beat, and has at + * least one routable 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; - sd_network_link_get_operational_state(l->ifindex, &state); + if (local_multicast) { + if (l->flags & IFF_POINTOPOINT) + return false; + + if ((l->flags & IFF_MULTICAST) != IFF_MULTICAST) + return false; + } + + /* Check kernel operstate + * https://www.kernel.org/doc/Documentation/networking/operstates.txt */ + if (!IN_SET(l->operstate, IF_OPER_UNKNOWN, IF_OPER_UP)) + return false; + + (void) 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, local_multicast)) return true; return false; @@ -344,12 +575,8 @@ DnsServer* link_set_dns_server(Link *l, DnsServer *s) { if (l->current_dns_server == s) return s; - if (s) { - _cleanup_free_ char *ip = NULL; - - in_addr_to_string(s->family, &s->address, &ip); - log_info("Switching to DNS server %s for interface %s.", strna(ip), l->name); - } + if (s) + log_info("Switching to DNS server %s for interface %s.", dns_server_string(s), l->name); dns_server_unref(l->current_dns_server); l->current_dns_server = dns_server_ref(s); @@ -385,6 +612,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; @@ -444,10 +695,10 @@ void link_address_add_rrs(LinkAddress *a, bool force_remove) { if (a->family == AF_INET) { if (!force_remove && - link_address_relevant(a) && + link_address_relevant(a, true) && a->link->llmnr_ipv4_scope && - a->link->llmnr_support == SUPPORT_YES && - a->link->manager->llmnr_support == SUPPORT_YES) { + a->link->llmnr_support == RESOLVE_SUPPORT_YES && + a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) { if (!a->link->manager->llmnr_host_ipv4_key) { a->link->manager->llmnr_host_ipv4_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, a->link->manager->llmnr_hostname); @@ -501,10 +752,10 @@ void link_address_add_rrs(LinkAddress *a, bool force_remove) { if (a->family == AF_INET6) { if (!force_remove && - link_address_relevant(a) && + link_address_relevant(a, true) && a->link->llmnr_ipv6_scope && - a->link->llmnr_support == SUPPORT_YES && - a->link->manager->llmnr_support == SUPPORT_YES) { + a->link->llmnr_support == RESOLVE_SUPPORT_YES && + a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) { if (!a->link->manager->llmnr_host_ipv6_key) { a->link->manager->llmnr_host_ipv6_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, a->link->manager->llmnr_hostname); @@ -578,13 +829,13 @@ int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m) { return 0; } -bool link_address_relevant(LinkAddress *a) { +bool link_address_relevant(LinkAddress *a, bool local_multicast) { assert(a); if (a->flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE)) return false; - if (IN_SET(a->scope, RT_SCOPE_HOST, RT_SCOPE_NOWHERE)) + if (a->scope >= (local_multicast ? RT_SCOPE_HOST : RT_SCOPE_LINK)) return false; return true; diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h index a3b406bbc2..29e7b72247 100644 --- a/src/resolve/resolved-link.h +++ b/src/resolve/resolved-link.h @@ -25,6 +25,7 @@ #include "in-addr-util.h" #include "ratelimit.h" +#include "resolve-util.h" typedef struct Link Link; typedef struct LinkAddress LinkAddress; @@ -66,8 +67,10 @@ struct Link { LIST_HEAD(DnsSearchDomain, search_domains); unsigned n_search_domains; - Support llmnr_support; - Support mdns_support; + ResolveSupport llmnr_support; + ResolveSupport mdns_support; + DnssecMode dnssec_mode; + Set *dnssec_negative_trust_anchors; DnsScope *unicast_scope; DnsScope *llmnr_ipv4_scope; @@ -75,26 +78,36 @@ struct Link { DnsScope *mdns_ipv4_scope; DnsScope *mdns_ipv6_scope; + bool is_managed; + char name[IF_NAMESIZE]; uint32_t mtu; + uint8_t operstate; }; 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 local_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); -bool link_address_relevant(LinkAddress *l); +bool link_address_relevant(LinkAddress *l, bool local_multicast); void link_address_add_rrs(LinkAddress *a, bool force_remove); DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free); diff --git a/src/resolve/resolved-llmnr.c b/src/resolve/resolved-llmnr.c index ed754c3899..f52ab8f384 100644 --- a/src/resolve/resolved-llmnr.c +++ b/src/resolve/resolved-llmnr.c @@ -47,7 +47,7 @@ int manager_llmnr_start(Manager *m) { assert(m); - if (m->llmnr_support == SUPPORT_NO) + if (m->llmnr_support == RESOLVE_SUPPORT_NO) return 0; r = manager_llmnr_ipv4_udp_fd(m); @@ -80,7 +80,7 @@ int manager_llmnr_start(Manager *m) { eaddrinuse: log_warning("There appears to be another LLMNR responder running. Turning off LLMNR support."); - m->llmnr_support = SUPPORT_NO; + m->llmnr_support = RESOLVE_SUPPORT_NO; manager_llmnr_stop(m); return 0; @@ -117,7 +117,7 @@ static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *u dns_scope_process_query(scope, NULL, p); } else - log_debug("Invalid LLMNR UDP packet."); + log_debug("Invalid LLMNR UDP packet, ignoring."); return 0; } @@ -193,6 +193,8 @@ int manager_llmnr_ipv4_udp_fd(Manager *m) { if (r < 0) goto fail; + (void) sd_event_source_set_description(m->llmnr_ipv4_udp_event_source, "llmnr-ipv4-udp"); + return m->llmnr_ipv4_udp_fd; fail: @@ -267,10 +269,10 @@ int manager_llmnr_ipv6_udp_fd(Manager *m) { } r = sd_event_add_io(m->event, &m->llmnr_ipv6_udp_event_source, m->llmnr_ipv6_udp_fd, EPOLLIN, on_llmnr_packet, m); - if (r < 0) { - r = -errno; + if (r < 0) goto fail; - } + + (void) sd_event_source_set_description(m->llmnr_ipv6_udp_event_source, "llmnr-ipv6-udp"); return m->llmnr_ipv6_udp_fd; @@ -393,6 +395,8 @@ int manager_llmnr_ipv4_tcp_fd(Manager *m) { if (r < 0) goto fail; + (void) sd_event_source_set_description(m->llmnr_ipv4_tcp_event_source, "llmnr-ipv4-tcp"); + return m->llmnr_ipv4_tcp_fd; fail: @@ -464,6 +468,8 @@ int manager_llmnr_ipv6_tcp_fd(Manager *m) { if (r < 0) goto fail; + (void) sd_event_source_set_description(m->llmnr_ipv6_tcp_event_source, "llmnr-ipv6-tcp"); + return m->llmnr_ipv6_tcp_fd; fail: diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index a2677f442a..f1dbda1a6a 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -37,10 +37,11 @@ #include "random-util.h" #include "resolved-bus.h" #include "resolved-conf.h" +#include "resolved-etc-hosts.h" #include "resolved-llmnr.h" #include "resolved-manager.h" -#include "resolved-resolv-conf.h" #include "resolved-mdns.h" +#include "resolved-resolv-conf.h" #include "socket-util.h" #include "string-table.h" #include "string-util.h" @@ -205,7 +206,7 @@ static int manager_rtnl_listen(Manager *m) { if (r < 0) return r; - r = sd_netlink_attach_event(m->rtnl, m->event, 0); + r = sd_netlink_attach_event(m->rtnl, m->event, SD_EVENT_PRIORITY_IMPORTANT); if (r < 0) return r; @@ -287,7 +288,7 @@ static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void * r = manager_write_resolv_conf(m); if (r < 0) - log_warning_errno(r, "Could not update resolv.conf: %m"); + log_warning_errno(r, "Could not update "PRIVATE_RESOLV_CONF": %m"); return 0; } @@ -313,6 +314,12 @@ static int manager_network_monitor_listen(Manager *m) { if (r < 0) return r; + r = sd_event_source_set_priority(m->network_event_source, SD_EVENT_PRIORITY_IMPORTANT+5); + if (r < 0) + return r; + + (void) sd_event_source_set_description(m->network_event_source, "network-monitor"); + return 0; } @@ -420,6 +427,8 @@ static int manager_watch_hostname(Manager *m) { return log_error_errno(r, "Failed to add hostname event source: %m"); } + (void) sd_event_source_set_description(m->hostname_event_source, "hostname"); + r = determine_hostname(&m->llmnr_hostname, &m->mdns_hostname); if (r < 0) { log_info("Defaulting to hostname 'linux'."); @@ -476,14 +485,21 @@ int manager_new(Manager **ret) { m->mdns_ipv4_fd = m->mdns_ipv6_fd = -1; m->hostname_fd = -1; - m->llmnr_support = SUPPORT_YES; + m->llmnr_support = RESOLVE_SUPPORT_YES; + m->mdns_support = RESOLVE_SUPPORT_NO; + m->dnssec_mode = DNSSEC_NO; m->read_resolv_conf = true; m->need_builtin_fallbacks = true; + m->etc_hosts_last = m->etc_hosts_mtime = USEC_INFINITY; r = dns_trust_anchor_load(&m->trust_anchor); if (r < 0) return r; + r = manager_parse_config_file(m); + if (r < 0) + return r; + r = sd_event_default(&m->event); if (r < 0) return r; @@ -584,6 +600,7 @@ Manager *manager_free(Manager *m) { free(m->mdns_hostname); dns_trust_anchor_flush(&m->trust_anchor); + manager_etc_hosts_flush(m); free(m); @@ -772,7 +789,7 @@ static int write_loop(int fd, void *message, size_t length) { int manager_write(Manager *m, int fd, DnsPacket *p) { int r; - log_debug("Sending %s packet with id %u", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p)); + log_debug("Sending %s packet with id %" PRIu16 ".", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p)); r = write_loop(fd, DNS_PACKET_DATA(p), p->size); if (r < 0) @@ -887,7 +904,7 @@ int manager_send(Manager *m, int fd, int ifindex, int family, const union in_add assert(port > 0); assert(p); - log_debug("Sending %s packet with id %u on interface %i/%s", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p), ifindex, af_to_name(family)); + log_debug("Sending %s packet with id %" PRIu16 " on interface %i/%s.", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p), ifindex, af_to_name(family)); if (family == AF_INET) return manager_ipv4_send(m, fd, ifindex, &addr->in, port, p); @@ -1164,9 +1181,63 @@ int manager_compile_search_domains(Manager *m, OrderedSet **domains) { return 0; } -static const char* const support_table[_SUPPORT_MAX] = { - [SUPPORT_NO] = "no", - [SUPPORT_YES] = "yes", - [SUPPORT_RESOLVE] = "resolve", -}; -DEFINE_STRING_TABLE_LOOKUP(support, Support); +DnssecMode manager_get_dnssec_mode(Manager *m) { + assert(m); + + if (m->dnssec_mode != _DNSSEC_MODE_INVALID) + return m->dnssec_mode; + + return DNSSEC_NO; +} + +bool manager_dnssec_supported(Manager *m) { + DnsServer *server; + Iterator i; + Link *l; + + assert(m); + + if (manager_get_dnssec_mode(m) == DNSSEC_NO) + return false; + + server = manager_get_dns_server(m); + if (server && !dns_server_dnssec_supported(server)) + return false; + + HASHMAP_FOREACH(l, m->links, i) + if (!link_dnssec_supported(l)) + return false; + + return true; +} + +void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key) { + + assert(verdict >= 0); + assert(verdict < _DNSSEC_VERDICT_MAX); + + if (log_get_max_level() >= LOG_DEBUG) { + _cleanup_free_ char *s = NULL; + + (void) dns_resource_key_to_string(key, &s); + + log_debug("Found verdict for lookup %s: %s", s ? strstrip(s) : "n/a", dnssec_verdict_to_string(verdict)); + } + + m->n_dnssec_verdict[verdict]++; +} + +bool manager_routable(Manager *m, int family) { + Iterator i; + Link *l; + + assert(m); + + /* Returns true if the host has at least one interface with a routable address of the specified type */ + + HASHMAP_FOREACH(l, m->links, i) + if (link_relevant(l, family, false)) + return true; + + return false; +} diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index b52273403a..e2c539d3d2 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -28,17 +28,9 @@ #include "hashmap.h" #include "list.h" #include "ordered-set.h" +#include "resolve-util.h" typedef struct Manager Manager; -typedef enum Support Support; - -enum Support { - SUPPORT_NO, - SUPPORT_YES, - SUPPORT_RESOLVE, - _SUPPORT_MAX, - _SUPPORT_INVALID = -1 -}; #include "resolved-dns-query.h" #include "resolved-dns-search-domain.h" @@ -53,8 +45,9 @@ enum Support { struct Manager { sd_event *event; - Support llmnr_support; - Support mdns_support; + ResolveSupport llmnr_support; + ResolveSupport mdns_support; + DnssecMode dnssec_mode; /* Network */ Hashmap *links; @@ -81,6 +74,7 @@ struct Manager { LIST_HEAD(DnsSearchDomain, search_domains); unsigned n_search_domains; + bool permit_domain_search; bool need_builtin_fallbacks:1; @@ -128,6 +122,14 @@ struct Manager { sd_bus_slot *prepare_for_sleep_slot; sd_event_source *sigusr1_event_source; + + unsigned n_transactions_total; + unsigned n_dnssec_verdict[_DNSSEC_VERDICT_MAX]; + + /* Data from /etc/hosts */ + Set* etc_hosts_by_address; + Hashmap* etc_hosts_by_name; + usec_t etc_hosts_last, etc_hosts_mtime; }; /* Manager */ @@ -163,5 +165,9 @@ int manager_is_own_hostname(Manager *m, const char *name); int manager_compile_dns_servers(Manager *m, OrderedSet **servers); int manager_compile_search_domains(Manager *m, OrderedSet **domains); -const char* support_to_string(Support p) _const_; -int support_from_string(const char *s) _pure_; +DnssecMode manager_get_dnssec_mode(Manager *m); +bool manager_dnssec_supported(Manager *m); + +void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key); + +bool manager_routable(Manager *m, int family); diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c index d6973a6999..d5b253d4f5 100644 --- a/src/resolve/resolved-mdns.c +++ b/src/resolve/resolved-mdns.c @@ -42,7 +42,7 @@ int manager_mdns_start(Manager *m) { assert(m); - if (m->mdns_support == SUPPORT_NO) + if (m->mdns_support == RESOLVE_SUPPORT_NO) return 0; r = manager_mdns_ipv4_fd(m); @@ -63,7 +63,7 @@ int manager_mdns_start(Manager *m) { eaddrinuse: log_warning("There appears to be another mDNS responder running. Turning off mDNS support."); - m->mdns_support = SUPPORT_NO; + m->mdns_support = RESOLVE_SUPPORT_NO; manager_mdns_stop(m); return 0; @@ -122,8 +122,7 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us dns_transaction_process_reply(t, p); } - dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, - p->answer->n_rrs, false, 0, p->family, &p->sender); + dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender); } else if (dns_packet_validate_query(p) > 0) { log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c index 956f380f3c..c5ce9c4f01 100644 --- a/src/resolve/resolved-resolv-conf.c +++ b/src/resolve/resolved-resolv-conf.c @@ -147,16 +147,14 @@ clear: } static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { - _cleanup_free_ char *t = NULL; - int r; - assert(s); assert(f); assert(count); - r = in_addr_to_string(s->family, &s->address, &t); - if (r < 0) { - log_warning_errno(r, "Invalid DNS address. Ignoring: %m"); + (void) dns_server_string(s); + + if (!s->server_string) { + log_warning("Our of memory, or invalid DNS address. Ignoring server."); return; } @@ -164,7 +162,7 @@ static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f); (*count) ++; - fprintf(f, "nameserver %s\n", t); + fprintf(f, "nameserver %s\n", s->server_string); } static void write_resolv_conf_search( @@ -228,8 +226,6 @@ static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *doma int manager_write_resolv_conf(Manager *m) { - #define PRIVATE_RESOLV_CONF "/run/systemd/resolve/resolv.conf" - _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL; _cleanup_free_ char *temp_path = NULL; _cleanup_fclose_ FILE *f = NULL; diff --git a/src/resolve/resolved-resolv-conf.h b/src/resolve/resolved-resolv-conf.h index a3355e994b..7081563965 100644 --- a/src/resolve/resolved-resolv-conf.h +++ b/src/resolve/resolved-resolv-conf.h @@ -23,5 +23,7 @@ #include "resolved-manager.h" +#define PRIVATE_RESOLV_CONF "/run/systemd/resolve/resolv.conf" + int manager_read_resolv_conf(Manager *m); int manager_write_resolv_conf(Manager *m); diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c index be406b71fe..eee52da882 100644 --- a/src/resolve/resolved.c +++ b/src/resolve/resolved.c @@ -81,12 +81,6 @@ int main(int argc, char *argv[]) { goto finish; } - r = manager_parse_config_file(m); - if (r < 0) { - log_error_errno(r, "Failed to parse configuration file: %m"); - goto finish; - } - r = manager_start(m); if (r < 0) { log_error_errno(r, "Failed to start manager: %m"); @@ -97,7 +91,7 @@ int main(int argc, char *argv[]) { * symlink */ r = manager_write_resolv_conf(m); if (r < 0) - log_warning_errno(r, "Could not create resolv.conf: %m"); + log_warning_errno(r, "Could not create "PRIVATE_RESOLV_CONF": %m"); sd_notify(false, "READY=1\n" diff --git a/src/resolve/test-dnssec-complex.c b/src/resolve/test-dnssec-complex.c new file mode 100644 index 0000000000..cde9741866 --- /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); + +#ifdef 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 a2118513f1..45fe1997e2 100644 --- a/src/resolve/test-dnssec.c +++ b/src/resolve/test-dnssec.c @@ -27,6 +27,99 @@ #include "resolved-dns-dnssec.h" #include "resolved-dns-rr.h" #include "string-util.h" +#include "hexdecoct.h" + +static void test_dnssec_verify_rrset2(void) { + + static const uint8_t signature_blob[] = { + 0x48, 0x45, 0xc8, 0x8b, 0xc0, 0x14, 0x92, 0xf5, 0x15, 0xc6, 0x84, 0x9d, 0x2f, 0xe3, 0x32, 0x11, + 0x7d, 0xf1, 0xe6, 0x87, 0xb9, 0x42, 0xd3, 0x8b, 0x9e, 0xaf, 0x92, 0x31, 0x0a, 0x53, 0xad, 0x8b, + 0xa7, 0x5c, 0x83, 0x39, 0x8c, 0x28, 0xac, 0xce, 0x6e, 0x9c, 0x18, 0xe3, 0x31, 0x16, 0x6e, 0xca, + 0x38, 0x31, 0xaf, 0xd9, 0x94, 0xf1, 0x84, 0xb1, 0xdf, 0x5a, 0xc2, 0x73, 0x22, 0xf6, 0xcb, 0xa2, + 0xe7, 0x8c, 0x77, 0x0c, 0x74, 0x2f, 0xc2, 0x13, 0xb0, 0x93, 0x51, 0xa9, 0x4f, 0xae, 0x0a, 0xda, + 0x45, 0xcc, 0xfd, 0x43, 0x99, 0x36, 0x9a, 0x0d, 0x21, 0xe0, 0xeb, 0x30, 0x65, 0xd4, 0xa0, 0x27, + 0x37, 0x3b, 0xe4, 0xc1, 0xc5, 0xa1, 0x2a, 0xd1, 0x76, 0xc4, 0x7e, 0x64, 0x0e, 0x5a, 0xa6, 0x50, + 0x24, 0xd5, 0x2c, 0xcc, 0x6d, 0xe5, 0x37, 0xea, 0xbd, 0x09, 0x34, 0xed, 0x24, 0x06, 0xa1, 0x22, + }; + + static const uint8_t dnskey_blob[] = { + 0x03, 0x01, 0x00, 0x01, 0xc3, 0x7f, 0x1d, 0xd1, 0x1c, 0x97, 0xb1, 0x13, 0x34, 0x3a, 0x9a, 0xea, + 0xee, 0xd9, 0x5a, 0x11, 0x1b, 0x17, 0xc7, 0xe3, 0xd4, 0xda, 0x20, 0xbc, 0x5d, 0xba, 0x74, 0xe3, + 0x37, 0x99, 0xec, 0x25, 0xce, 0x93, 0x7f, 0xbd, 0x22, 0x73, 0x7e, 0x14, 0x71, 0xe0, 0x60, 0x07, + 0xd4, 0x39, 0x8b, 0x5e, 0xe9, 0xba, 0x25, 0xe8, 0x49, 0xe9, 0x34, 0xef, 0xfe, 0x04, 0x5c, 0xa5, + 0x27, 0xcd, 0xa9, 0xda, 0x70, 0x05, 0x21, 0xab, 0x15, 0x82, 0x24, 0xc3, 0x94, 0xf5, 0xd7, 0xb7, + 0xc4, 0x66, 0xcb, 0x32, 0x6e, 0x60, 0x2b, 0x55, 0x59, 0x28, 0x89, 0x8a, 0x72, 0xde, 0x88, 0x56, + 0x27, 0x95, 0xd9, 0xac, 0x88, 0x4f, 0x65, 0x2b, 0x68, 0xfc, 0xe6, 0x41, 0xc1, 0x1b, 0xef, 0x4e, + 0xd6, 0xc2, 0x0f, 0x64, 0x88, 0x95, 0x5e, 0xdd, 0x3a, 0x02, 0x07, 0x50, 0xa9, 0xda, 0xa4, 0x49, + 0x74, 0x62, 0xfe, 0xd7, + }; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *nsec = NULL, *rrsig = NULL, *dnskey = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + DnssecResult result; + + nsec = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC, "nasa.gov"); + assert_se(nsec); + + nsec->nsec.next_domain_name = strdup("3D-Printing.nasa.gov"); + assert_se(nsec->nsec.next_domain_name); + + nsec->nsec.types = bitmap_new(); + assert_se(nsec->nsec.types); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_A) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NS) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_SOA) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_MX) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_TXT) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_RRSIG) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NSEC) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_DNSKEY) >= 0); + assert_se(bitmap_set(nsec->nsec.types, 65534) >= 0); + + log_info("NSEC: %s", strna(dns_resource_record_to_string(nsec))); + + rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV."); + assert_se(rrsig); + + rrsig->rrsig.type_covered = DNS_TYPE_NSEC; + rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; + rrsig->rrsig.labels = 2; + rrsig->rrsig.original_ttl = 300; + rrsig->rrsig.expiration = 0x5689002f; + rrsig->rrsig.inception = 0x56617230; + rrsig->rrsig.key_tag = 30390; + rrsig->rrsig.signer = strdup("Nasa.Gov."); + assert_se(rrsig->rrsig.signer); + rrsig->rrsig.signature_size = sizeof(signature_blob); + rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size); + assert_se(rrsig->rrsig.signature); + + log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig))); + + dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV"); + assert_se(dnskey); + + dnskey->dnskey.flags = 256; + dnskey->dnskey.protocol = 3; + dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; + dnskey->dnskey.key_size = sizeof(dnskey_blob); + dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); + assert_se(dnskey->dnskey.key); + + log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); + + assert_se(dnssec_key_match_rrsig(nsec->key, rrsig) > 0); + assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0); + + answer = dns_answer_new(1); + assert_se(answer); + assert_se(dns_answer_add(answer, nsec, 0, DNS_ANSWER_AUTHENTICATED) >= 0); + + /* Validate the RR as it if was 2015-12-11 today */ + assert_se(dnssec_verify_rrset(answer, nsec->key, rrsig, dnskey, 1449849318*USEC_PER_SEC, &result) >= 0); + assert_se(result == DNSSEC_VALIDATED); +} static void test_dnssec_verify_rrset(void) { @@ -55,7 +148,6 @@ static void test_dnssec_verify_rrset(void) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *a = NULL, *rrsig = NULL, *dnskey = NULL; _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL; DnssecResult result; a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "nAsA.gov"); @@ -63,8 +155,7 @@ static void test_dnssec_verify_rrset(void) { a->a.in_addr.s_addr = inet_addr("52.0.14.116"); - assert_se(dns_resource_record_to_string(a, &x) >= 0); - log_info("A: %s", x); + log_info("A: %s", strna(dns_resource_record_to_string(a))); rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV."); assert_se(rrsig); @@ -82,8 +173,7 @@ static void test_dnssec_verify_rrset(void) { rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size); assert_se(rrsig->rrsig.signature); - assert_se(dns_resource_record_to_string(rrsig, &y) >= 0); - log_info("RRSIG: %s", y); + log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig))); dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV"); assert_se(dnskey); @@ -95,16 +185,15 @@ static void test_dnssec_verify_rrset(void) { dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); assert_se(dnskey->dnskey.key); - assert_se(dns_resource_record_to_string(dnskey, &z) >= 0); - log_info("DNSKEY: %s", z); - log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey)); + log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); assert_se(dnssec_key_match_rrsig(a->key, rrsig) > 0); - assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey) > 0); + assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0); answer = dns_answer_new(1); assert_se(answer); - assert_se(dns_answer_add(answer, a, 0) >= 0); + assert_se(dns_answer_add(answer, a, 0, DNS_ANSWER_AUTHENTICATED) >= 0); /* Validate the RR as it if was 2015-12-2 today */ assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, 1449092754*USEC_PER_SEC, &result) >= 0); @@ -142,7 +231,6 @@ static void test_dnssec_verify_dns_key(void) { }; _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds1 = NULL, *ds2 = NULL; - _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL; /* The two DS RRs in effect for nasa.gov on 2015-12-01. */ ds1 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "nasa.gov"); @@ -155,8 +243,7 @@ static void test_dnssec_verify_dns_key(void) { ds1->ds.digest = memdup(ds1_fprint, ds1->ds.digest_size); assert_se(ds1->ds.digest); - assert_se(dns_resource_record_to_string(ds1, &a) >= 0); - log_info("DS1: %s", a); + log_info("DS1: %s", strna(dns_resource_record_to_string(ds1))); ds2 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "NASA.GOV"); assert_se(ds2); @@ -168,8 +255,7 @@ static void test_dnssec_verify_dns_key(void) { ds2->ds.digest = memdup(ds2_fprint, ds2->ds.digest_size); assert_se(ds2->ds.digest); - assert_se(dns_resource_record_to_string(ds2, &b) >= 0); - log_info("DS2: %s", b); + log_info("DS2: %s", strna(dns_resource_record_to_string(ds2))); dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nasa.GOV"); assert_se(dnskey); @@ -181,12 +267,11 @@ static void test_dnssec_verify_dns_key(void) { dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); assert_se(dnskey->dnskey.key); - assert_se(dns_resource_record_to_string(dnskey, &c) >= 0); - log_info("DNSKEY: %s", c); - log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey)); + 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) > 0); - assert_se(dnssec_verify_dnskey(dnskey, ds2) > 0); + assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds1, false) > 0); + assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds2, false) > 0); } static void test_dnssec_canonicalize_one(const char *original, const char *canonical, int r) { @@ -209,11 +294,45 @@ static void test_dnssec_canonicalize(void) { test_dnssec_canonicalize_one("FOO..bar.", NULL, -EINVAL); } +static void test_dnssec_nsec3_hash(void) { + static const uint8_t salt[] = { 0xB0, 0x1D, 0xFA, 0xCE }; + static const uint8_t next_hashed_name[] = { 0x84, 0x10, 0x26, 0x53, 0xc9, 0xfa, 0x4d, 0x85, 0x6c, 0x97, 0x82, 0xe2, 0x8f, 0xdf, 0x2d, 0x5e, 0x87, 0x69, 0xc4, 0x52 }; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + uint8_t h[DNSSEC_HASH_SIZE_MAX]; + _cleanup_free_ char *b = NULL; + int k; + + /* The NSEC3 RR for eurid.eu on 2015-12-14. */ + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC3, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM.eurid.eu."); + assert_se(rr); + + rr->nsec3.algorithm = DNSSEC_DIGEST_SHA1; + rr->nsec3.flags = 1; + rr->nsec3.iterations = 1; + rr->nsec3.salt = memdup(salt, sizeof(salt)); + assert_se(rr->nsec3.salt); + rr->nsec3.salt_size = sizeof(salt); + rr->nsec3.next_hashed_name = memdup(next_hashed_name, sizeof(next_hashed_name)); + assert_se(rr->nsec3.next_hashed_name); + rr->nsec3.next_hashed_name_size = sizeof(next_hashed_name); + + log_info("NSEC3: %s", strna(dns_resource_record_to_string(rr))); + + k = dnssec_nsec3_hash(rr, "eurid.eu", &h); + assert_se(k >= 0); + + b = base32hexmem(h, k, false); + assert_se(b); + assert_se(strcasecmp(b, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM") == 0); +} + int main(int argc, char*argv[]) { test_dnssec_canonicalize(); test_dnssec_verify_dns_key(); test_dnssec_verify_rrset(); + test_dnssec_verify_rrset2(); + test_dnssec_nsec3_hash(); return 0; } diff --git a/src/resolve/test-resolve-tables.c b/src/resolve/test-resolve-tables.c new file mode 100644 index 0000000000..63660afc87 --- /dev/null +++ b/src/resolve/test-resolve-tables.c @@ -0,0 +1,27 @@ +/*** + This file is part of systemd + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + 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 "dns-type.h" +#include "test-tables.h" + +int main(int argc, char **argv) { + test_table_sparse(dns_type, DNS_TYPE); + + return EXIT_SUCCESS; +} diff --git a/src/run/run.c b/src/run/run.c index 92a1d5373c..080c15466c 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -422,17 +422,9 @@ static int transient_unit_set_properties(sd_bus_message *m, char **properties) { return r; STRV_FOREACH(i, properties) { - r = sd_bus_message_open_container(m, 'r', "sv"); - if (r < 0) - return r; - r = bus_append_unit_property_assignment(m, *i); if (r < 0) return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; } return 0; diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c index bc12f89f02..716899f659 100644 --- a/src/shared/ask-password-api.c +++ b/src/shared/ask-password-api.c @@ -59,6 +59,7 @@ #include "terminal-util.h" #include "time-util.h" #include "umask-util.h" +#include "utf8.h" #include "util.h" #define KEYRING_TIMEOUT_USEC ((5 * USEC_PER_MINUTE) / 2) @@ -71,7 +72,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; @@ -213,8 +214,8 @@ int ask_password_tty( char **ret) { struct termios old_termios, new_termios; - char passphrase[LINE_MAX], *x; - size_t p = 0; + char passphrase[LINE_MAX + 1] = {}, *x; + size_t p = 0, codepoint = 0; int r; _cleanup_close_ int ttyfd = -1, notify = -1; struct pollfd pollfd[2]; @@ -378,8 +379,13 @@ int ask_password_tty( passphrase[p++] = c; - if (!(flags & ASK_PASSWORD_SILENT) && ttyfd >= 0) - loop_write(ttyfd, (flags & ASK_PASSWORD_ECHO) ? &c : "*", 1, false); + if (!(flags & ASK_PASSWORD_SILENT) && ttyfd >= 0) { + n = utf8_encoded_valid_unichar(passphrase + codepoint); + if (n >= 0) { + codepoint = p; + loop_write(ttyfd, (flags & ASK_PASSWORD_ECHO) ? &c : "*", 1, false); + } + } dirty = true; } diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c index 5c6dc34700..6df73c560a 100644 --- a/src/shared/bus-util.c +++ b/src/shared/bus-util.c @@ -1398,7 +1398,7 @@ int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u) { int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment) { const char *eq, *field; - int r; + int r, rl; assert(m); assert(assignment); @@ -1409,20 +1409,18 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen return -EINVAL; } + r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv"); + if (r < 0) + return bus_log_create_error(r); + field = strndupa(assignment, eq - assignment); eq ++; if (streq(field, "CPUQuota")) { - if (isempty(eq)) { - - r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, "CPUQuotaPerSecUSec"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "v", "t", USEC_INFINITY); - - } else if (endswith(eq, "%")) { + if (isempty(eq)) + r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", USEC_INFINITY); + else if (endswith(eq, "%")) { double percent; if (sscanf(eq, "%lf%%", &percent) != 1 || percent <= 0) { @@ -1430,58 +1428,69 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen return -EINVAL; } - r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, "CPUQuotaPerSecUSec"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "v", "t", (usec_t) percent * USEC_PER_SEC / 100); + r = sd_bus_message_append(m, "sv", "CPUQuotaPerSecUSec", "t", (usec_t) percent * USEC_PER_SEC / 100); } else { log_error("CPU quota needs to be in percent."); return -EINVAL; } - if (r < 0) - return bus_log_create_error(r); - - return 0; + goto finish; } else if (streq(field, "EnvironmentFile")) { - r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, "EnvironmentFiles"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "v", "a(sb)", 1, + r = sd_bus_message_append(m, "sv", "EnvironmentFiles", "a(sb)", 1, eq[0] == '-' ? eq + 1 : eq, eq[0] == '-'); + goto finish; + + } else if (STR_IN_SET(field, "AccuracySec", "RandomizedDelaySec", "RuntimeMaxSec")) { + char *n; + usec_t t; + size_t l; + r = parse_sec(eq, &t); if (r < 0) - return bus_log_create_error(r); + return log_error_errno(r, "Failed to parse %s= parameter: %s", field, eq); - return 0; + l = strlen(field); + n = newa(char, l + 2); + if (!n) + return log_oom(); - } else if (streq(field, "RandomizedDelaySec")) { - usec_t t; + /* Change suffix Sec → USec */ + strcpy(mempcpy(n, field, l - 3), "USec"); + r = sd_bus_message_append(m, "sv", n, "t", t); + goto finish; + } - r = parse_sec(eq, &t); + r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field); + if (r < 0) + return bus_log_create_error(r); + + rl = rlimit_from_string(field); + if (rl >= 0) { + const char *sn; + struct rlimit l; + + r = rlimit_parse(rl, eq, &l); if (r < 0) - return log_error_errno(r, "Failed to parse RandomizedDelaySec= parameter: %s", eq); + return log_error_errno(r, "Failed to parse resource limit: %s", eq); - r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, "RandomizedDelayUSec"); + r = sd_bus_message_append(m, "v", "t", l.rlim_max); if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_append(m, "v", "t", t); + r = sd_bus_message_close_container(m); if (r < 0) return bus_log_create_error(r); - return 0; - } + r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv"); + if (r < 0) + return bus_log_create_error(r); - r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field); - if (r < 0) - return bus_log_create_error(r); + sn = strjoina(field, "Soft"); + r = sd_bus_message_append(m, "sv", sn, "t", l.rlim_cur); - if (STR_IN_SET(field, + } else if (STR_IN_SET(field, "CPUAccounting", "MemoryAccounting", "BlockIOAccounting", "TasksAccounting", "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies", "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit", @@ -1662,21 +1671,6 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen r = sd_bus_message_append(m, "v", "a(st)", path, u); } - } else if (rlimit_from_string(field) >= 0) { - uint64_t rl; - - if (streq(eq, "infinity")) - rl = (uint64_t) -1; - else { - r = safe_atou64(eq, &rl); - if (r < 0) { - log_error("Invalid resource limit: %s", eq); - return -EINVAL; - } - } - - r = sd_bus_message_append(m, "v", "t", rl); - } else if (streq(field, "Nice")) { int32_t i; @@ -1746,16 +1740,6 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen r = sd_bus_message_append(m, "v", "i", sig); - } else if (streq(field, "AccuracySec")) { - usec_t u; - - r = parse_sec(eq, &u); - if (r < 0) { - log_error("Failed to parse %s value %s", field, eq); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "t", u); } else if (streq(field, "TimerSlackNSec")) { nsec_t n; @@ -1869,6 +1853,11 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen return -EINVAL; } +finish: + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); if (r < 0) return bus_log_create_error(r); @@ -2041,13 +2030,21 @@ static const struct { { "start-limit", "start of the service was attempted too often" } }; -static void log_job_error_with_service_result(const char* service, const char *result) { - _cleanup_free_ char *service_shell_quoted = NULL; +static void log_job_error_with_service_result(const char* service, const char *result, const char *extra_args) { + _cleanup_free_ char *service_shell_quoted = NULL, *systemctl_extra_args = NULL; assert(service); service_shell_quoted = shell_maybe_quote(service); + systemctl_extra_args = strjoin("systemctl ", extra_args, " ", NULL); + if (!systemctl_extra_args) { + log_oom(); + return; + } + + systemctl_extra_args = strstrip(systemctl_extra_args); + if (!isempty(result)) { unsigned i; @@ -2056,27 +2053,30 @@ static void log_job_error_with_service_result(const char* service, const char *r break; if (i < ELEMENTSOF(explanations)) { - log_error("Job for %s failed because %s. See \"systemctl status %s\" and \"journalctl -xe\" for details.\n", + log_error("Job for %s failed because %s. See \"%s status %s\" and \"journalctl -xe\" for details.\n", service, explanations[i].explanation, + systemctl_extra_args, strna(service_shell_quoted)); goto finish; } } - log_error("Job for %s failed. See \"systemctl status %s\" and \"journalctl -xe\" for details.\n", + log_error("Job for %s failed. See \"%s status %s\" and \"journalctl -xe\" for details.\n", service, + systemctl_extra_args, strna(service_shell_quoted)); finish: /* For some results maybe additional explanation is required */ if (streq_ptr(result, "start-limit")) - log_info("To force a start use \"systemctl reset-failed %1$s\" followed by \"systemctl start %1$s\" again.", + log_info("To force a start use \"%1$s reset-failed %2$s\" followed by \"%1$s start %2$s\" again.", + systemctl_extra_args, strna(service_shell_quoted)); } -static int check_wait_response(BusWaitForJobs *d, bool quiet) { +static int check_wait_response(BusWaitForJobs *d, bool quiet, const char *extra_args) { int r = 0; assert(d->result); @@ -2089,7 +2089,7 @@ static int check_wait_response(BusWaitForJobs *d, bool quiet) { else if (streq(d->result, "dependency")) log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d->name)); else if (streq(d->result, "invalid")) - log_error("Job for %s invalid.", strna(d->name)); + log_error("%s is not active, cannot reload.", strna(d->name)); else if (streq(d->result, "assert")) log_error("Assertion failed on job for %s.", strna(d->name)); else if (streq(d->result, "unsupported")) @@ -2103,7 +2103,7 @@ static int check_wait_response(BusWaitForJobs *d, bool quiet) { if (q < 0) log_debug_errno(q, "Failed to get Result property of service %s: %m", d->name); - log_job_error_with_service_result(d->name, result); + log_job_error_with_service_result(d->name, result, extra_args); } else log_error("Job failed. See \"journalctl -xe\" for details."); } @@ -2127,7 +2127,7 @@ static int check_wait_response(BusWaitForJobs *d, bool quiet) { return r; } -int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet) { +int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char *extra_args) { int r = 0; assert(d); @@ -2140,7 +2140,7 @@ int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet) { return log_error_errno(q, "Failed to wait for response: %m"); if (d->result) { - q = check_wait_response(d, quiet); + q = check_wait_response(d, quiet, extra_args); /* Return the first error as it is most likely to be * meaningful. */ if (q < 0 && r == 0) @@ -2175,7 +2175,7 @@ int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet) { if (r < 0) return log_oom(); - return bus_wait_for_jobs(d, quiet); + return bus_wait_for_jobs(d, quiet, NULL); } int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, UnitFileChange **changes, unsigned *n_changes) { diff --git a/src/shared/bus-util.h b/src/shared/bus-util.h index a5e3b6a0b5..18fc827754 100644 --- a/src/shared/bus-util.h +++ b/src/shared/bus-util.h @@ -182,7 +182,7 @@ typedef struct BusWaitForJobs BusWaitForJobs; int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret); void bus_wait_for_jobs_free(BusWaitForJobs *d); int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path); -int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet); +int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char *extra_args); int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet); DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForJobs*, bus_wait_for_jobs_free); diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index 0466857042..7ef4ad3cf8 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -48,7 +48,6 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) { assert(name); assert(*name); - assert(dest); n = *name; d = dest; @@ -79,9 +78,12 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) { else if (*n == '\\' || *n == '.') { /* Escaped backslash or dot */ - *(d++) = *(n++); + + if (d) + *(d++) = *n; sz--; r++; + n++; } else if (n[0] >= '0' && n[0] <= '9') { unsigned k; @@ -96,11 +98,17 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) { ((unsigned) (n[1] - '0') * 10) + ((unsigned) (n[2] - '0')); - /* Don't allow CC characters or anything that doesn't fit in 8bit */ - if (k < ' ' || k > 255 || k == 127) + /* Don't allow anything that doesn't + * fit in 8bit. Note that we do allow + * control characters, as some servers + * (e.g. cloudflare) are happy to + * generate labels with them + * inside. */ + if (k > 255) return -EINVAL; - *(d++) = (char) k; + if (d) + *(d++) = (char) k; sz--; r++; @@ -111,9 +119,12 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) { } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) { /* Normal character */ - *(d++) = *(n++); + + if (d) + *(d++) = *n; sz--; r++; + n++; } else return -EINVAL; } @@ -122,7 +133,7 @@ int dns_label_unescape(const char **name, char *dest, size_t sz) { if (r == 0 && *n) return -EINVAL; - if (sz >= 1) + if (sz >= 1 && d) *d = 0; *name = n; @@ -148,20 +159,24 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha return 0; } - assert(**label_terminal == '.' || **label_terminal == 0); + terminal = *label_terminal; + assert(*terminal == '.' || *terminal == 0); - /* skip current terminal character */ - terminal = *label_terminal - 1; + /* Skip current terminal character (and accept domain names ending it ".") */ + if (*terminal == 0) + terminal--; + if (terminal >= name && *terminal == '.') + terminal--; - /* point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */ + /* Point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */ for (;;) { if (terminal < name) { - /* reached the first label, so indicate that there are no more */ + /* Reached the first label, so indicate that there are no more */ terminal = NULL; break; } - /* find the start of the last label */ + /* Find the start of the last label */ if (*terminal == '.') { const char *y; unsigned slashes = 0; @@ -170,7 +185,7 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha slashes ++; if (slashes % 2 == 0) { - /* the '.' was not escaped */ + /* The '.' was not escaped */ name = terminal + 1; break; } else { @@ -235,7 +250,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) { *(q++) = *p; sz -= 1; - } else if ((uint8_t) *p >= (uint8_t) ' ' && *p != 127) { + } else { /* Everything else */ @@ -248,9 +263,7 @@ int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) { *(q++) = '0' + (char) ((uint8_t) *p % 10); sz -= 4; - - } else - return -EINVAL; + } p++; l--; @@ -392,15 +405,20 @@ int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, int dns_name_concat(const char *a, const char *b, char **_ret) { _cleanup_free_ char *ret = NULL; size_t n = 0, allocated = 0; - const char *p = a; + const char *p; bool first = true; int r; - assert(a); + if (a) + p = a; + else if (b) { + p = b; + b = NULL; + } else + goto finish; for (;;) { char label[DNS_LABEL_MAX]; - int k; r = dns_label_unescape(&p, label, sizeof(label)); if (r < 0) @@ -419,12 +437,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; @@ -451,12 +463,21 @@ int dns_name_concat(const char *a, const char *b, char **_ret) { n += r; } +finish: if (n > DNS_HOSTNAME_MAX) return -EINVAL; if (_ret) { - if (!GREEDY_REALLOC(ret, allocated, n + 1)) - return -ENOMEM; + if (n == 0) { + /* Nothing appended? If so, generate at least a single dot, to indicate the DNS root domain */ + if (!GREEDY_REALLOC(ret, allocated, 2)) + return -ENOMEM; + + ret[n++] = '.'; + } else { + if (!GREEDY_REALLOC(ret, allocated, n + 1)) + return -ENOMEM; + } ret[n] = 0; *_ret = ret; @@ -472,27 +493,18 @@ void dns_name_hash_func(const void *s, struct siphash *state) { assert(p); - while (*p) { + for (;;) { char label[DNS_LABEL_MAX+1]; - int k; r = dns_label_unescape(&p, label, sizeof(label)); if (r < 0) break; - - k = dns_label_undo_idna(label, r, label, sizeof(label)); - if (k < 0) - break; - if (k > 0) - r = k; - if (r == 0) break; - label[r] = 0; - ascii_strlower(label); - - string_hash_func(label, state); + ascii_strlower_n(label, r); + siphash24_compress(label, r, state); + siphash24_compress_byte(0, state); /* make sure foobar and foo.bar result in different hashes */ } /* enforce that all names are terminated by the empty label */ @@ -501,7 +513,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); @@ -510,7 +522,7 @@ int dns_name_compare_func(const void *a, const void *b) { y = (const char *) b + strlen(b); for (;;) { - char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; + char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; if (x == NULL && y == NULL) return 0; @@ -520,17 +532,7 @@ int dns_name_compare_func(const void *a, const void *b) { if (r < 0 || q < 0) return r - q; - k = dns_label_undo_idna(la, r, la, sizeof(la)); - w = dns_label_undo_idna(lb, q, lb, sizeof(lb)); - if (k < 0 || w < 0) - return k - w; - if (k > 0) - r = k; - if (w > 0) - r = w; - - la[r] = lb[q] = 0; - r = strcasecmp(la, lb); + r = ascii_strcasecmp_nn(la, r, lb, q); if (r != 0) return r; } @@ -542,53 +544,35 @@ const struct hash_ops dns_name_hash_ops = { }; int dns_name_equal(const char *x, const char *y) { - int r, q, k, w; + int r, q; assert(x); assert(y); for (;;) { - char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; - - if (*x == 0 && *y == 0) - return true; + char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; r = dns_label_unescape(&x, la, sizeof(la)); if (r < 0) return r; - if (r > 0) { - k = dns_label_undo_idna(la, r, la, sizeof(la)); - if (k < 0) - return k; - if (k > 0) - r = k; - } q = dns_label_unescape(&y, lb, sizeof(lb)); if (q < 0) return q; - if (q > 0) { - w = dns_label_undo_idna(lb, q, lb, sizeof(lb)); - if (w < 0) - return w; - if (w > 0) - q = w; - } - /* If one name had fewer labels than the other, this - * will show up as empty label here, which the - * strcasecmp() below will properly consider different - * from a non-empty label. */ + if (r != q) + return false; + if (r == 0) + return true; - la[r] = lb[q] = 0; - if (strcasecmp(la, lb) != 0) + if (ascii_strcasecmp_n(la, lb, r) != 0) return false; } } int dns_name_endswith(const char *name, const char *suffix) { const char *n, *s, *saved_n = NULL; - int r, q, k, w; + int r, q; assert(name); assert(suffix); @@ -597,18 +581,11 @@ int dns_name_endswith(const char *name, const char *suffix) { s = suffix; for (;;) { - char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1]; + char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; r = dns_label_unescape(&n, ln, sizeof(ln)); if (r < 0) return r; - if (r > 0) { - k = dns_label_undo_idna(ln, r, ln, sizeof(ln)); - if (k < 0) - return k; - if (k > 0) - r = k; - } if (!saved_n) saved_n = n; @@ -616,22 +593,13 @@ int dns_name_endswith(const char *name, const char *suffix) { q = dns_label_unescape(&s, ls, sizeof(ls)); if (q < 0) return q; - if (q > 0) { - w = dns_label_undo_idna(ls, q, ls, sizeof(ls)); - if (w < 0) - return w; - if (w > 0) - q = w; - } if (r == 0 && q == 0) return true; if (r == 0 && saved_n == n) return false; - ln[r] = ls[q] = 0; - - if (r != q || strcasecmp(ln, ls)) { + if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) { /* Not the same, let's jump back, and try with the next label again */ s = suffix; @@ -641,9 +609,39 @@ int dns_name_endswith(const char *name, const char *suffix) { } } +int dns_name_startswith(const char *name, const char *prefix) { + const char *n, *p; + int r, q; + + assert(name); + assert(prefix); + + n = name; + p = prefix; + + for (;;) { + char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX]; + + r = dns_label_unescape(&p, lp, sizeof(lp)); + if (r < 0) + return r; + if (r == 0) + return true; + + q = dns_label_unescape(&n, ln, sizeof(ln)); + if (q < 0) + return q; + + if (r != q) + return false; + if (ascii_strcasecmp_n(ln, lp, r) != 0) + return false; + } +} + int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret) { const char *n, *s, *saved_before = NULL, *saved_after = NULL, *prefix; - int r, q, k, w; + int r, q; assert(name); assert(old_suffix); @@ -654,7 +652,7 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char s = old_suffix; for (;;) { - char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1]; + char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; if (!saved_before) saved_before = n; @@ -662,13 +660,6 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char r = dns_label_unescape(&n, ln, sizeof(ln)); if (r < 0) return r; - if (r > 0) { - k = dns_label_undo_idna(ln, r, ln, sizeof(ln)); - if (k < 0) - return k; - if (k > 0) - r = k; - } if (!saved_after) saved_after = n; @@ -676,13 +667,6 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char q = dns_label_unescape(&s, ls, sizeof(ls)); if (q < 0) return q; - if (q > 0) { - w = dns_label_undo_idna(ls, q, ls, sizeof(ls)); - if (w < 0) - return w; - if (w > 0) - q = w; - } if (r == 0 && q == 0) break; @@ -691,9 +675,7 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char return 0; } - ln[r] = ls[q] = 0; - - if (r != q || strcasecmp(ln, ls)) { + if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) { /* Not the same, let's jump back, and try with the next label again */ s = old_suffix; @@ -861,12 +843,11 @@ bool dns_name_is_root(const char *name) { } bool dns_name_is_single_label(const char *name) { - char label[DNS_LABEL_MAX+1]; int r; assert(name); - r = dns_label_unescape(&name, label, sizeof(label)); + r = dns_name_parent(&name); if (r <= 0) return false; @@ -899,19 +880,11 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, boo if (r < 0) return r; - if (canonical) { - size_t i; - - /* Optionally, output the name in DNSSEC - * canonical format, as described in RFC 4034, - * section 6.2. Or in other words: in - * lower-case. */ - - for (i = 0; i < (size_t) r; i++) { - if (out[i] >= 'A' && out[i] <= 'Z') - out[i] = out[i] - 'A' + 'a'; - } - } + /* Optionally, output the name in DNSSEC canonical + * format, as described in RFC 4034, section 6.2. Or + * in other words: in lower-case. */ + if (canonical) + ascii_strlower_n((char*) out, (size_t) r); /* Fill label length, move forward */ *label_length = r; @@ -1095,17 +1068,15 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do if (x >= 3 && srv_type_label_is_valid(c, cn)) { if (dns_service_name_label_is_valid(a, an)) { - /* OK, got <name> . <type> . <type2> . <domain> */ name = strndup(a, an); if (!name) return -ENOMEM; - type = new(char, bn+1+cn+1); + type = strjoin(b, ".", c, NULL); if (!type) return -ENOMEM; - strcpy(stpcpy(stpcpy(type, b), "."), c); d = p; goto finish; @@ -1117,10 +1088,9 @@ int dns_service_split(const char *joined, char **_name, char **_type, char **_do name = NULL; - type = new(char, an+1+bn+1); + type = strjoin(a, ".", b, NULL); if (!type) return -ENOMEM; - strcpy(stpcpy(stpcpy(type, a), "."), b); d = q; goto finish; @@ -1153,3 +1123,202 @@ finish: return 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(table); + + p = name; + for (;;) { + if (n > DNS_N_LABELS_MAX) + return -EINVAL; + + table[n] = p; + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + break; + + n++; + } + + 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; + int r; + + assert(name); + + p = name; + for (;;) { + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + break; + + if (n >= DNS_N_LABELS_MAX) + return -EINVAL; + + n++; + } + + return (int) n; +} + +int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) { + int r; + + assert(a); + assert(b); + + r = dns_name_skip(a, n_labels, &a); + if (r <= 0) + return r; + + return dns_name_equal(a, b); +} + +int dns_name_common_suffix(const char *a, const char *b, const char **ret) { + const char *a_labels[DNS_N_LABELS_MAX+1], *b_labels[DNS_N_LABELS_MAX+1]; + int n = 0, m = 0, k = 0, r, q; + + assert(a); + assert(b); + assert(ret); + + /* Determines the common suffix of domain names a and b */ + + n = dns_name_build_suffix_table(a, a_labels); + if (n < 0) + return n; + + m = dns_name_build_suffix_table(b, b_labels); + if (m < 0) + return m; + + for (;;) { + char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + const char *x, *y; + + if (k >= n || k >= m) { + *ret = a_labels[n - k]; + return 0; + } + + x = a_labels[n - 1 - k]; + r = dns_label_unescape(&x, la, sizeof(la)); + if (r < 0) + return r; + + y = b_labels[m - 1 - k]; + q = dns_label_unescape(&y, lb, sizeof(lb)); + if (q < 0) + return q; + + if (r != q || ascii_strcasecmp_n(la, lb, r) != 0) { + *ret = a_labels[n - k]; + return 0; + } + + k++; + } +} + +int dns_name_apply_idna(const char *name, char **ret) { + _cleanup_free_ char *buf = NULL; + size_t n = 0, allocated = 0; + bool first = true; + int r, q; + + assert(name); + assert(ret); + + for (;;) { + char label[DNS_LABEL_MAX]; + + r = dns_label_unescape(&name, label, sizeof(label)); + if (r < 0) + return r; + if (r == 0) + break; + + q = dns_label_apply_idna(label, r, label, sizeof(label)); + if (q < 0) + return q; + if (q > 0) + r = q; + + if (!GREEDY_REALLOC(buf, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + r = dns_label_escape(label, r, buf + n + !first, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + 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 3f8f621802..40c9ee5f27 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -42,11 +42,18 @@ /* Maximum length of a full hostname, on the wire, including the final NUL byte */ #define DNS_WIRE_FOMAT_HOSTNAME_MAX 255 +/* Maximum number of labels per valid hostname */ +#define DNS_N_LABELS_MAX 127 + int dns_label_unescape(const char **name, char *dest, size_t sz); int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz); int dns_label_escape(const char *p, size_t l, char *dest, size_t sz); int dns_label_escape_new(const char *p, size_t l, char **ret); +static inline int dns_name_parent(const char **name) { + return dns_label_unescape(name, NULL, DNS_LABEL_MAX); +} + int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); @@ -76,6 +83,7 @@ extern const struct hash_ops dns_name_hash_ops; int dns_name_between(const char *a, const char *b, const char *c); int dns_name_equal(const char *x, const char *y); int dns_name_endswith(const char *name, const char *suffix); +int dns_name_startswith(const char *name, const char *prefix); int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret); @@ -92,3 +100,13 @@ bool dns_service_name_is_valid(const char *name); int dns_service_join(const char *name, const char *type, const char *domain, char **ret); int dns_service_split(const char *joined, char **name, char **type, char **domain); + +int dns_name_suffix(const char *name, unsigned n_labels, const char **ret); +int dns_name_count_labels(const char *name); + +int dns_name_skip(const char *a, unsigned n_labels, const char **ret); +int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b); + +int dns_name_common_suffix(const char *a, const char *b, const char **ret); + +int dns_name_apply_idna(const char *name, char **ret); diff --git a/src/shared/dropin.c b/src/shared/dropin.c index 692e8b8338..073a8396c5 100644 --- a/src/shared/dropin.c +++ b/src/shared/dropin.c @@ -156,7 +156,7 @@ static int iterate_dir( errno = 0; de = readdir(d); - if (!de && errno != 0) + if (!de && errno > 0) return log_error_errno(errno, "Failed to read directory %s: %m", path); if (!de) diff --git a/src/shared/generator.c b/src/shared/generator.c index 37de3f7cb1..76808cbdd5 100644 --- a/src/shared/generator.c +++ b/src/shared/generator.c @@ -190,7 +190,6 @@ int generator_write_timeouts( return write_drop_in_format(dir, unit, 50, "device-timeout", "# Automatically generated by %s\n\n" - "[Unit]\nJobTimeoutSec=" USEC_FMT, - program_invocation_short_name, - u / USEC_PER_SEC); + "[Unit]\nJobTimeoutSec=%s", + program_invocation_short_name, timeout); } diff --git a/src/shared/gpt.h b/src/shared/gpt.h index add1df420f..5f4c00ba83 100644 --- a/src/shared/gpt.h +++ b/src/shared/gpt.h @@ -25,7 +25,7 @@ #include "sd-id128.h" -/* We only support root disk discovery for x86, x86-64 and ARM for +/* We only support root disk discovery for x86, x86-64, Itanium and ARM for * now, since EFI for anything else doesn't really exist, and we only * care for root partitions on the same disk as the EFI ESP. */ @@ -33,6 +33,7 @@ #define GPT_ROOT_X86_64 SD_ID128_MAKE(4f,68,bc,e3,e8,cd,4d,b1,96,e7,fb,ca,f9,84,b7,09) #define GPT_ROOT_ARM SD_ID128_MAKE(69,da,d7,10,2c,e4,4e,3c,b1,6c,21,a1,d4,9a,be,d3) #define GPT_ROOT_ARM_64 SD_ID128_MAKE(b9,21,b0,45,1d,f0,41,c3,af,44,4c,6f,28,0d,3f,ae) +#define GPT_ROOT_IA64 SD_ID128_MAKE(99,3d,8d,3d,f8,0e,42,25,85,5a,9d,af,8e,d7,ea,97) #define GPT_ESP SD_ID128_MAKE(c1,2a,73,28,f8,1f,11,d2,ba,4b,00,a0,c9,3e,c9,3b) #define GPT_SWAP SD_ID128_MAKE(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f) @@ -46,6 +47,10 @@ # define GPT_ROOT_NATIVE GPT_ROOT_X86 #endif +#if defined(__ia64__) +# define GPT_ROOT_NATIVE GPT_ROOT_IA64 +#endif + #if defined(__aarch64__) && (__BYTE_ORDER != __BIG_ENDIAN) # define GPT_ROOT_NATIVE GPT_ROOT_ARM_64 # define GPT_ROOT_SECONDARY GPT_ROOT_ARM diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 193dad1943..111f0225d9 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -82,12 +82,11 @@ static int print_catalog(FILE *f, sd_journal *j) { static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) { size_t fl, nl; - void *buf; + char *buf; assert(data); assert(field); assert(target); - assert(target_size); fl = strlen(field); if (length < fl) @@ -97,16 +96,18 @@ static int parse_field(const void *data, size_t length, const char *field, char return 0; nl = length - fl; - buf = malloc(nl+1); + buf = new(char, nl+1); if (!buf) return log_oom(); memcpy(buf, (const char*) data + fl, nl); - ((char*)buf)[nl] = 0; + buf[nl] = 0; free(*target); *target = buf; - *target_size = nl; + + if (target_size) + *target_size = nl; return 1; } @@ -415,7 +416,7 @@ static int output_verbose( const void *data; size_t length; _cleanup_free_ char *cursor = NULL; - uint64_t realtime; + uint64_t realtime = 0; char ts[FORMAT_TIMESTAMP_MAX + 7]; int r; @@ -431,16 +432,15 @@ static int output_verbose( return log_full_errno(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR, r, "Failed to get source realtime timestamp: %m"); else { _cleanup_free_ char *value = NULL; - size_t size; - r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &value, &size); + r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &value, NULL); if (r < 0) - log_debug_errno(r, "_SOURCE_REALTIME_TIMESTAMP invalid: %m"); - else { - r = safe_atou64(value, &realtime); - if (r < 0) - log_debug_errno(r, "Failed to parse realtime timestamp: %m"); - } + return r; + assert(r > 0); + + r = safe_atou64(value, &realtime); + if (r < 0) + log_debug_errno(r, "Failed to parse realtime timestamp: %m"); } if (r < 0) { diff --git a/src/shared/resolve-util.c b/src/shared/resolve-util.c new file mode 100644 index 0000000000..bf6fc26841 --- /dev/null +++ b/src/shared/resolve-util.c @@ -0,0 +1,41 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "conf-parser.h" +#include "resolve-util.h" +#include "string-table.h" + +DEFINE_CONFIG_PARSE_ENUM(config_parse_resolve_support, resolve_support, ResolveSupport, "Failed to parse resolve support setting"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_dnssec_mode, dnssec_mode, DnssecMode, "Failed to parse DNSSEC mode setting"); + +static const char* const resolve_support_table[_RESOLVE_SUPPORT_MAX] = { + [RESOLVE_SUPPORT_NO] = "no", + [RESOLVE_SUPPORT_YES] = "yes", + [RESOLVE_SUPPORT_RESOLVE] = "resolve", +}; +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(resolve_support, ResolveSupport, RESOLVE_SUPPORT_YES); + +static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = { + [DNSSEC_NO] = "no", + [DNSSEC_ALLOW_DOWNGRADE] = "allow-downgrade", + [DNSSEC_YES] = "yes", +}; +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dnssec_mode, DnssecMode, DNSSEC_YES); diff --git a/src/shared/resolve-util.h b/src/shared/resolve-util.h new file mode 100644 index 0000000000..fd93a13f73 --- /dev/null +++ b/src/shared/resolve-util.h @@ -0,0 +1,62 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "macro.h" + +typedef enum ResolveSupport ResolveSupport; +typedef enum DnssecMode DnssecMode; + +enum ResolveSupport { + RESOLVE_SUPPORT_NO, + RESOLVE_SUPPORT_YES, + RESOLVE_SUPPORT_RESOLVE, + _RESOLVE_SUPPORT_MAX, + _RESOLVE_SUPPORT_INVALID = -1 +}; + +enum DnssecMode { + /* No DNSSEC validation is done */ + DNSSEC_NO, + + /* Validate locally, if the server knows DO, but if not, + * don't. Don't trust the AD bit. If the server doesn't do + * DNSSEC properly, downgrade to non-DNSSEC operation. Of + * course, we then are vulnerable to a downgrade attack, but + * that's life and what is configured. */ + DNSSEC_ALLOW_DOWNGRADE, + + /* Insist on DNSSEC server support, and rather fail than downgrading. */ + DNSSEC_YES, + + _DNSSEC_MODE_MAX, + _DNSSEC_MODE_INVALID = -1 +}; + +int config_parse_resolve_support(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_dnssec_mode(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); + +const char* resolve_support_to_string(ResolveSupport p) _const_; +ResolveSupport resolve_support_from_string(const char *s) _pure_; + +const char* dnssec_mode_to_string(DnssecMode p) _const_; +DnssecMode dnssec_mode_from_string(const char *s) _pure_; diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index b1bbbdaadd..bf0739e5fa 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -35,6 +35,7 @@ #include "mkdir.h" #include "path-util.h" #include "rm-rf.h" +#include "stdio-util.h" #include "string-util.h" #include "switch-root.h" #include "user-util.h" @@ -77,7 +78,7 @@ int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, char new_mount[PATH_MAX]; struct stat sb; - snprintf(new_mount, sizeof(new_mount), "%s%s", new_root, i); + xsprintf(new_mount, "%s%s", new_root, i); mkdir_p_label(new_mount, 0755); diff --git a/src/shared/test-tables.h b/src/shared/test-tables.h index 74f1716fe0..228e510104 100644 --- a/src/shared/test-tables.h +++ b/src/shared/test-tables.h @@ -28,18 +28,25 @@ static inline void _test_table(const char *name, reverse_t reverse, int size, bool sparse) { - int i; + int i, boring = 0; for (i = -1; i < size + 1; i++) { const char* val = lookup(i); int rev; - if (val) + if (val) { rev = reverse(val); - else + boring = 0; + } else { rev = reverse("--no-such--value----"); + boring += i >= 0; + } + + if (boring < 1 || i == size) + printf("%s: %d → %s → %d\n", name, i, val, rev); + else if (boring == 1) + printf("%*s ...\n", (int) strlen(name), ""); - printf("%s: %d → %s → %d\n", name, i, val, rev); assert_se(!(i >= 0 && i < size ? sparse ? rev != i && rev != -1 : val == NULL || rev != i : val != NULL || rev != -1)); diff --git a/src/socket-proxy/socket-proxyd.c b/src/socket-proxy/socket-proxyd.c index ba82adadb4..600f772e19 100644 --- a/src/socket-proxy/socket-proxyd.c +++ b/src/socket-proxy/socket-proxyd.c @@ -505,7 +505,7 @@ static int accept_cb(sd_event_source *s, int fd, uint32_t revents, void *userdat if (errno != -EAGAIN) log_warning_errno(errno, "Failed to accept() socket: %m"); } else { - getpeername_pretty(nfd, &peer); + getpeername_pretty(nfd, true, &peer); log_debug("New connection from %s", strna(peer)); r = add_connection_socket(context, nfd); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 1448d974bd..536beb28ce 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -247,7 +247,7 @@ static OutputFlags get_output_flags(void) { arg_all * OUTPUT_SHOW_ALL | arg_full * OUTPUT_FULL_WIDTH | (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | - on_tty() * OUTPUT_COLOR | + colors_enabled() * OUTPUT_COLOR | !arg_quiet * OUTPUT_WARN_CUTOFF; } @@ -1979,7 +1979,7 @@ static void dump_unit_file_changes(const UnitFileChange *changes, unsigned n_cha for (i = 0; i < n_changes; i++) { if (changes[i].type == UNIT_FILE_SYMLINK) - log_info("Created symlink from %s to %s.", changes[i].path, changes[i].source); + log_info("Created symlink %s, pointing to %s.", changes[i].path, changes[i].source); else log_info("Removed symlink %s.", changes[i].path); } @@ -2325,42 +2325,12 @@ static int unit_find_paths( if (!install_client_side() && !unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *unit_load_error = NULL; _cleanup_free_ char *unit = NULL; - char *unit_load_error_name, *unit_load_error_message; unit = unit_dbus_path_from_name(unit_name); if (!unit) return log_oom(); - if (need_daemon_reload(bus, unit_name) > 0) - warn_unit_file_changed(unit_name); - - r = sd_bus_get_property( - bus, - "org.freedesktop.systemd1", - unit, - "org.freedesktop.systemd1.Unit", - "LoadError", - &error, - &unit_load_error, - "(ss)"); - if (r < 0) - return log_error_errno(r, "Failed to get LoadError: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read( - unit_load_error, - "(ss)", - &unit_load_error_name, - &unit_load_error_message); - if (r < 0) - return bus_log_parse_error(r); - - if (!isempty(unit_load_error_name)) { - log_error("Unit %s is not loaded: %s", unit_name, unit_load_error_message); - return 0; - } - r = sd_bus_get_property_string( bus, "org.freedesktop.systemd1", @@ -2440,19 +2410,16 @@ static int unit_find_paths( } static int check_one_unit(sd_bus *bus, const char *name, const char *good_states, bool quiet) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *n = NULL, *state = NULL; - const char *path; + _cleanup_free_ char *buf = NULL; + const char *path, *state; int r; assert(name); - r = unit_name_mangle(name, UNIT_NAME_NOGLOB, &n); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - - /* We don't use unit_dbus_path_from_name() directly since we - * don't want to load the unit if it isn't loaded. */ + /* We don't use unit_dbus_path_from_name() directly since we don't want to load the unit unnecessarily, if it + * isn't loaded. */ r = sd_bus_call_method( bus, @@ -2460,31 +2427,34 @@ static int check_one_unit(sd_bus *bus, const char *name, const char *good_states "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "GetUnit", - NULL, + &error, &reply, - "s", n); + "s", name); if (r < 0) { - if (!quiet) - puts("unknown"); - return 0; - } + if (!sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT)) + return log_error_errno(r, "Failed to retrieve unit: %s", bus_error_message(&error, r)); - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); + /* The unit is currently not loaded, hence say it's "inactive", since all units that aren't loaded are + * considered inactive. */ + state = "inactive"; - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "ActiveState", - NULL, - &state); - if (r < 0) { - if (!quiet) - puts("unknown"); - return 0; + } else { + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "ActiveState", + &error, + &buf); + if (r < 0) + return log_error_errno(r, "Failed to retrieve unit state: %s", bus_error_message(&error, r)); + + state = buf; } if (!quiet) @@ -2568,6 +2538,7 @@ static const struct { { "try-restart", "TryRestartUnit" }, { "condrestart", "TryRestartUnit" }, { "reload-or-restart", "ReloadOrRestartUnit" }, + { "try-reload-or-restart", "ReloadOrTryRestartUnit" }, { "reload-or-try-restart", "ReloadOrTryRestartUnit" }, { "condreload", "ReloadOrTryRestartUnit" }, { "force-reload", "ReloadOrTryRestartUnit" } @@ -2631,7 +2602,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); @@ -2682,14 +2659,25 @@ static int expand_names(sd_bus *bus, char **names, const char* suffix, char ***r if (!strv_isempty(globs)) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_free_ UnitInfo *unit_infos = NULL; + size_t allocated, n; r = get_unit_list(bus, NULL, globs, &unit_infos, 0, &reply); if (r < 0) return r; - for (i = 0; i < r; i++) - if (strv_extend(&mangled, unit_infos[i].id) < 0) + n = strv_length(mangled); + allocated = n + 1; + + for (i = 0; i < r; i++) { + if (!GREEDY_REALLOC(mangled, allocated, n+2)) return log_oom(); + + mangled[n] = strdup(unit_infos[i].id); + if (!mangled[n]) + return log_oom(); + + mangled[++n] = NULL; + } } *ret = mangled; @@ -2794,7 +2782,7 @@ static int start_unit(int argc, char *argv[], void *userdata) { if (!arg_no_block) { int q; - q = bus_wait_for_jobs(w, arg_quiet); + q = bus_wait_for_jobs(w, arg_quiet, arg_scope != UNIT_FILE_SYSTEM ? "--user" : NULL); if (q < 0) return q; @@ -3177,6 +3165,7 @@ static int check_unit_generic(int code, const char *good_states, char **args) { sd_bus *bus; char **name; int r; + bool found = false; r = acquire_bus(BUS_MANAGER, &bus); if (r < 0) @@ -3192,11 +3181,13 @@ static int check_unit_generic(int code, const char *good_states, char **args) { state = check_one_unit(bus, *name, good_states, arg_quiet); if (state < 0) return state; - if (state == 0) - r = code; + if (state > 0) + found = true; } - return r; + /* use the given return code for the case that we won't find + * any unit which matches the list */ + return found ? 0 : code; } static int check_unit_active(int argc, char *argv[], void *userdata) { @@ -4882,17 +4873,9 @@ static int set_property(int argc, char *argv[], void *userdata) { return bus_log_create_error(r); STRV_FOREACH(i, strv_skip(argv, 2)) { - r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv"); - if (r < 0) - return bus_log_create_error(r); - r = bus_append_unit_property_assignment(m, *i); if (r < 0) return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); } r = sd_bus_message_close_container(m); @@ -5801,7 +5784,7 @@ static int is_system_running(int argc, char *argv[], void *userdata) { sd_bus *bus; int r; - if (arg_transport == BUS_TRANSPORT_LOCAL && !sd_booted()) { + if (running_in_chroot() > 0 || (arg_transport == BUS_TRANSPORT_LOCAL && !sd_booted())) { if (!arg_quiet) puts("offline"); return EXIT_FAILURE; @@ -6244,8 +6227,8 @@ static void systemctl_help(void) { " try-restart NAME... Restart one or more units if active\n" " reload-or-restart NAME... Reload one or more units if possible,\n" " otherwise start or restart\n" - " reload-or-try-restart NAME... Reload one or more units if possible,\n" - " otherwise restart if active\n" + " try-reload-or-restart NAME... If active, reload one or more units,\n" + " if supported, otherwise restart\n" " isolate NAME Start one unit and stop all others\n" " kill NAME... Send signal to processes of a unit\n" " is-active PATTERN... Check whether units are active\n" @@ -7334,70 +7317,71 @@ static int talk_initctl(void) { static int systemctl_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "list-units", VERB_ANY, VERB_ANY, VERB_DEFAULT, list_units }, - { "list-unit-files", VERB_ANY, VERB_ANY, 0, list_unit_files }, - { "list-sockets", VERB_ANY, VERB_ANY, 0, list_sockets }, - { "list-timers", VERB_ANY, VERB_ANY, 0, list_timers }, - { "list-jobs", VERB_ANY, VERB_ANY, 0, list_jobs }, - { "list-machines", VERB_ANY, VERB_ANY, 0, list_machines }, - { "clear-jobs", VERB_ANY, 1, 0, daemon_reload }, - { "cancel", VERB_ANY, VERB_ANY, 0, cancel_job }, - { "start", 2, VERB_ANY, 0, start_unit }, - { "stop", 2, VERB_ANY, 0, start_unit }, - { "condstop", 2, VERB_ANY, 0, start_unit }, /* For compatibility with ALTLinux */ - { "reload", 2, VERB_ANY, 0, start_unit }, - { "restart", 2, VERB_ANY, 0, start_unit }, - { "try-restart", 2, VERB_ANY, 0, start_unit }, - { "reload-or-restart", 2, VERB_ANY, 0, start_unit }, - { "reload-or-try-restart", 2, VERB_ANY, 0, start_unit }, - { "force-reload", 2, VERB_ANY, 0, start_unit }, /* For compatibility with SysV */ - { "condreload", 2, VERB_ANY, 0, start_unit }, /* For compatibility with ALTLinux */ - { "condrestart", 2, VERB_ANY, 0, start_unit }, /* For compatibility with RH */ - { "isolate", 2, 2, 0, start_unit }, - { "kill", 2, VERB_ANY, 0, kill_unit }, - { "is-active", 2, VERB_ANY, 0, check_unit_active }, - { "check", 2, VERB_ANY, 0, check_unit_active }, - { "is-failed", 2, VERB_ANY, 0, check_unit_failed }, - { "show", VERB_ANY, VERB_ANY, 0, show }, - { "cat", 2, VERB_ANY, 0, cat }, - { "status", VERB_ANY, VERB_ANY, 0, show }, - { "help", VERB_ANY, VERB_ANY, 0, show }, - { "daemon-reload", VERB_ANY, 1, 0, daemon_reload }, - { "daemon-reexec", VERB_ANY, 1, 0, daemon_reload }, - { "show-environment", VERB_ANY, 1, 0, show_environment }, - { "set-environment", 2, VERB_ANY, 0, set_environment }, - { "unset-environment", 2, VERB_ANY, 0, set_environment }, - { "import-environment", VERB_ANY, VERB_ANY, 0, import_environment}, - { "halt", VERB_ANY, 1, 0, start_special }, - { "poweroff", VERB_ANY, 1, 0, start_special }, - { "reboot", VERB_ANY, 2, 0, start_special }, - { "kexec", VERB_ANY, 1, 0, start_special }, - { "suspend", VERB_ANY, 1, 0, start_special }, - { "hibernate", VERB_ANY, 1, 0, start_special }, - { "hybrid-sleep", VERB_ANY, 1, 0, start_special }, - { "default", VERB_ANY, 1, 0, start_special }, - { "rescue", VERB_ANY, 1, 0, start_special }, - { "emergency", VERB_ANY, 1, 0, start_special }, - { "exit", VERB_ANY, 2, 0, start_special }, - { "reset-failed", VERB_ANY, VERB_ANY, 0, reset_failed }, - { "enable", 2, VERB_ANY, 0, enable_unit }, - { "disable", 2, VERB_ANY, 0, enable_unit }, - { "is-enabled", 2, VERB_ANY, 0, unit_is_enabled }, - { "reenable", 2, VERB_ANY, 0, enable_unit }, - { "preset", 2, VERB_ANY, 0, enable_unit }, - { "preset-all", VERB_ANY, 1, 0, preset_all }, - { "mask", 2, VERB_ANY, 0, enable_unit }, - { "unmask", 2, VERB_ANY, 0, enable_unit }, - { "link", 2, VERB_ANY, 0, enable_unit }, - { "switch-root", 2, VERB_ANY, 0, switch_root }, - { "list-dependencies", VERB_ANY, 2, 0, list_dependencies }, - { "set-default", 2, 2, 0, set_default }, - { "get-default", VERB_ANY, 1, 0, get_default, }, - { "set-property", 3, VERB_ANY, 0, set_property }, - { "is-system-running", VERB_ANY, 1, 0, is_system_running }, - { "add-wants", 3, VERB_ANY, 0, add_dependency }, - { "add-requires", 3, VERB_ANY, 0, add_dependency }, - { "edit", 2, VERB_ANY, 0, edit }, + { "list-units", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_NOCHROOT, list_units }, + { "list-unit-files", VERB_ANY, VERB_ANY, 0, list_unit_files }, + { "list-sockets", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_sockets }, + { "list-timers", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_timers }, + { "list-jobs", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_jobs }, + { "list-machines", VERB_ANY, VERB_ANY, VERB_NOCHROOT, list_machines }, + { "clear-jobs", VERB_ANY, 1, VERB_NOCHROOT, daemon_reload }, + { "cancel", VERB_ANY, VERB_ANY, VERB_NOCHROOT, cancel_job }, + { "start", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "stop", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "condstop", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with ALTLinux */ + { "reload", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "try-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "reload-or-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "reload-or-try-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatbility with old systemctl <= 228 */ + { "try-reload-or-restart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, + { "force-reload", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with SysV */ + { "condreload", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with ALTLinux */ + { "condrestart", 2, VERB_ANY, VERB_NOCHROOT, start_unit }, /* For compatibility with RH */ + { "isolate", 2, 2, VERB_NOCHROOT, start_unit }, + { "kill", 2, VERB_ANY, VERB_NOCHROOT, kill_unit }, + { "is-active", 2, VERB_ANY, VERB_NOCHROOT, check_unit_active }, + { "check", 2, VERB_ANY, VERB_NOCHROOT, check_unit_active }, + { "is-failed", 2, VERB_ANY, VERB_NOCHROOT, check_unit_failed }, + { "show", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show }, + { "cat", 2, VERB_ANY, VERB_NOCHROOT, cat }, + { "status", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show }, + { "help", VERB_ANY, VERB_ANY, VERB_NOCHROOT, show }, + { "daemon-reload", VERB_ANY, 1, VERB_NOCHROOT, daemon_reload }, + { "daemon-reexec", VERB_ANY, 1, VERB_NOCHROOT, daemon_reload }, + { "show-environment", VERB_ANY, 1, VERB_NOCHROOT, show_environment }, + { "set-environment", 2, VERB_ANY, VERB_NOCHROOT, set_environment }, + { "unset-environment", 2, VERB_ANY, VERB_NOCHROOT, set_environment }, + { "import-environment", VERB_ANY, VERB_ANY, VERB_NOCHROOT, import_environment}, + { "halt", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "poweroff", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "reboot", VERB_ANY, 2, VERB_NOCHROOT, start_special }, + { "kexec", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "suspend", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "hibernate", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "hybrid-sleep", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "default", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "rescue", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "emergency", VERB_ANY, 1, VERB_NOCHROOT, start_special }, + { "exit", VERB_ANY, 2, VERB_NOCHROOT, start_special }, + { "reset-failed", VERB_ANY, VERB_ANY, VERB_NOCHROOT, reset_failed }, + { "enable", 2, VERB_ANY, 0, enable_unit }, + { "disable", 2, VERB_ANY, 0, enable_unit }, + { "is-enabled", 2, VERB_ANY, 0, unit_is_enabled }, + { "reenable", 2, VERB_ANY, 0, enable_unit }, + { "preset", 2, VERB_ANY, 0, enable_unit }, + { "preset-all", VERB_ANY, 1, 0, preset_all }, + { "mask", 2, VERB_ANY, 0, enable_unit }, + { "unmask", 2, VERB_ANY, 0, enable_unit }, + { "link", 2, VERB_ANY, 0, enable_unit }, + { "switch-root", 2, VERB_ANY, VERB_NOCHROOT, switch_root }, + { "list-dependencies", VERB_ANY, 2, VERB_NOCHROOT, list_dependencies }, + { "set-default", 2, 2, 0, set_default }, + { "get-default", VERB_ANY, 1, 0, get_default, }, + { "set-property", 3, VERB_ANY, VERB_NOCHROOT, set_property }, + { "is-system-running", VERB_ANY, 1, 0, is_system_running }, + { "add-wants", 3, VERB_ANY, 0, add_dependency }, + { "add-requires", 3, VERB_ANY, 0, add_dependency }, + { "edit", 2, VERB_ANY, VERB_NOCHROOT, edit }, {} }; @@ -7651,7 +7635,7 @@ int main(int argc, char*argv[]) { if (r <= 0) goto finish; - if (running_in_chroot() > 0 && arg_action != ACTION_SYSTEMCTL) { + if (arg_action != ACTION_SYSTEMCTL && running_in_chroot() > 0) { log_info("Running in chroot, ignoring request."); r = 0; goto finish; diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index 4deaf52e68..bbf220e689 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -42,6 +42,48 @@ enum { SD_DHCP_CLIENT_EVENT_RENEW = 4, }; +enum { + SD_DHCP_OPTION_PAD = 0, + SD_DHCP_OPTION_SUBNET_MASK = 1, + SD_DHCP_OPTION_TIME_OFFSET = 2, + SD_DHCP_OPTION_ROUTER = 3, + SD_DHCP_OPTION_DOMAIN_NAME_SERVER = 6, + SD_DHCP_OPTION_HOST_NAME = 12, + SD_DHCP_OPTION_BOOT_FILE_SIZE = 13, + SD_DHCP_OPTION_DOMAIN_NAME = 15, + SD_DHCP_OPTION_ROOT_PATH = 17, + SD_DHCP_OPTION_ENABLE_IP_FORWARDING = 19, + SD_DHCP_OPTION_ENABLE_IP_FORWARDING_NL = 20, + SD_DHCP_OPTION_POLICY_FILTER = 21, + SD_DHCP_OPTION_INTERFACE_MDR = 22, + SD_DHCP_OPTION_INTERFACE_TTL = 23, + SD_DHCP_OPTION_INTERFACE_MTU_AGING_TIMEOUT = 24, + SD_DHCP_OPTION_INTERFACE_MTU = 26, + SD_DHCP_OPTION_BROADCAST = 28, + SD_DHCP_OPTION_STATIC_ROUTE = 33, + SD_DHCP_OPTION_NTP_SERVER = 42, + SD_DHCP_OPTION_VENDOR_SPECIFIC = 43, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS = 50, + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51, + SD_DHCP_OPTION_OVERLOAD = 52, + SD_DHCP_OPTION_MESSAGE_TYPE = 53, + SD_DHCP_OPTION_SERVER_IDENTIFIER = 54, + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST = 55, + SD_DHCP_OPTION_ERROR_MESSAGE = 56, + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE = 57, + SD_DHCP_OPTION_RENEWAL_T1_TIME = 58, + SD_DHCP_OPTION_REBINDING_T2_TIME = 59, + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER = 60, + SD_DHCP_OPTION_CLIENT_IDENTIFIER = 61, + SD_DHCP_OPTION_FQDN = 81, + SD_DHCP_OPTION_NEW_POSIX_TIMEZONE = 100, + SD_DHCP_OPTION_NEW_TZDB_TIMEZONE = 101, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE = 121, + SD_DHCP_OPTION_PRIVATE_BASE = 224, + SD_DHCP_OPTION_PRIVATE_LAST = 254, + SD_DHCP_OPTION_END = 255, +}; + typedef struct sd_dhcp_client sd_dhcp_client; typedef void (*sd_dhcp_client_cb_t)(sd_dhcp_client *client, int event, diff --git a/src/systemd/sd-dhcp-lease.h b/src/systemd/sd-dhcp-lease.h index cb5c2cf173..a0d24c211c 100644 --- a/src/systemd/sd-dhcp-lease.h +++ b/src/systemd/sd-dhcp-lease.h @@ -33,7 +33,7 @@ _SD_BEGIN_DECLARATIONS; typedef struct sd_dhcp_lease sd_dhcp_lease; -struct sd_dhcp_route; +typedef struct sd_dhcp_route sd_dhcp_route; sd_dhcp_lease *sd_dhcp_lease_ref(sd_dhcp_lease *lease); sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease); @@ -53,11 +53,15 @@ int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu); int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname); int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname); int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path); -int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, struct sd_dhcp_route **routes); +int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes); int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len); int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len); int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **timezone); +int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination); +int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length); +int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway); + _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_lease, sd_dhcp_lease_unref); _SD_END_DECLARATIONS; diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h index 12cc763eb7..fa90f7a670 100644 --- a/src/systemd/sd-dhcp6-client.h +++ b/src/systemd/sd-dhcp6-client.h @@ -41,6 +41,41 @@ enum { SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST = 13, }; +enum { + SD_DHCP6_OPTION_CLIENTID = 1, + SD_DHCP6_OPTION_SERVERID = 2, + SD_DHCP6_OPTION_IA_NA = 3, + SD_DHCP6_OPTION_IA_TA = 4, + SD_DHCP6_OPTION_IAADDR = 5, + SD_DHCP6_OPTION_ORO = 6, + SD_DHCP6_OPTION_PREFERENCE = 7, + SD_DHCP6_OPTION_ELAPSED_TIME = 8, + SD_DHCP6_OPTION_RELAY_MSG = 9, + /* option code 10 is unassigned */ + SD_DHCP6_OPTION_AUTH = 11, + SD_DHCP6_OPTION_UNICAST = 12, + SD_DHCP6_OPTION_STATUS_CODE = 13, + SD_DHCP6_OPTION_RAPID_COMMIT = 14, + SD_DHCP6_OPTION_USER_CLASS = 15, + SD_DHCP6_OPTION_VENDOR_CLASS = 16, + SD_DHCP6_OPTION_VENDOR_OPTS = 17, + SD_DHCP6_OPTION_INTERFACE_ID = 18, + SD_DHCP6_OPTION_RECONF_MSG = 19, + SD_DHCP6_OPTION_RECONF_ACCEPT = 20, + + SD_DHCP6_OPTION_DNS_SERVERS = 23, /* RFC 3646 */ + SD_DHCP6_OPTION_DOMAIN_LIST = 24, /* RFC 3646 */ + + SD_DHCP6_OPTION_SNTP_SERVERS = 31, /* RFC 4075, deprecated */ + + /* option code 35 is unassigned */ + + SD_DHCP6_OPTION_NTP_SERVER = 56, /* RFC 5908 */ + + /* option codes 89-142 are unassigned */ + /* option codes 144-65535 are unassigned */ +}; + typedef struct sd_dhcp6_client sd_dhcp6_client; typedef void (*sd_dhcp6_client_cb_t)(sd_dhcp6_client *client, int event, diff --git a/src/systemd/sd-journal.h b/src/systemd/sd-journal.h index 33e36149e9..caf322f062 100644 --- a/src/systemd/sd-journal.h +++ b/src/systemd/sd-journal.h @@ -129,6 +129,9 @@ int sd_journal_query_unique(sd_journal *j, const char *field); int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l); void sd_journal_restart_unique(sd_journal *j); +int sd_journal_enumerate_fields(sd_journal *j, const char **field); +void sd_journal_restart_fields(sd_journal *j); + int sd_journal_get_fd(sd_journal *j); int sd_journal_get_events(sd_journal *j); int sd_journal_get_timeout(sd_journal *j, uint64_t *timeout_usec); @@ -139,22 +142,31 @@ int sd_journal_reliable_fd(sd_journal *j); int sd_journal_get_catalog(sd_journal *j, char **text); int sd_journal_get_catalog_for_message_id(sd_id128_t id, char **text); -/* the inverse condition avoids ambiguity of danling 'else' after the macro */ +int sd_journal_has_runtime_files(sd_journal *j); +int sd_journal_has_persistent_files(sd_journal *j); + +/* The inverse condition avoids ambiguity of dangling 'else' after the macro */ #define SD_JOURNAL_FOREACH(j) \ if (sd_journal_seek_head(j) < 0) { } \ else while (sd_journal_next(j) > 0) -/* the inverse condition avoids ambiguity of danling 'else' after the macro */ +/* The inverse condition avoids ambiguity of dangling 'else' after the macro */ #define SD_JOURNAL_FOREACH_BACKWARDS(j) \ if (sd_journal_seek_tail(j) < 0) { } \ else while (sd_journal_previous(j) > 0) +/* Iterate through the data fields of the current journal entry */ #define SD_JOURNAL_FOREACH_DATA(j, data, l) \ for (sd_journal_restart_data(j); sd_journal_enumerate_data((j), &(data), &(l)) > 0; ) +/* Iterate through the all known values of a specific field */ #define SD_JOURNAL_FOREACH_UNIQUE(j, data, l) \ for (sd_journal_restart_unique(j); sd_journal_enumerate_unique((j), &(data), &(l)) > 0; ) +/* Iterate through all known field names */ +#define SD_JOURNAL_FOREACH_FIELD(j, field) \ + for (sd_journal_restart_fields(j); sd_journal_enumerate_fields((j), &(field)) > 0; ) + _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_journal, sd_journal_close); _SD_END_DECLARATIONS; diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h index 072832a916..814263546b 100644 --- a/src/systemd/sd-messages.h +++ b/src/systemd/sd-messages.h @@ -86,6 +86,10 @@ _SD_BEGIN_DECLARATIONS; #define SD_MESSAGE_BOOTCHART SD_ID128_MAKE(9f,26,aa,56,2c,f4,40,c2,b1,6c,77,3d,04,79,b5,18) +#define SD_MESSAGE_DNSSEC_FAILURE SD_ID128_MAKE(16,75,d7,f1,72,17,40,98,b1,10,8b,f8,c7,dc,8f,5d) +#define SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED SD_ID128_MAKE(4d,44,08,cf,d0,d1,44,85,91,84,d1,e6,5d,7c,8a,65) +#define SD_MESSAGE_DNSSEC_DOWNGRADE SD_ID128_MAKE(36,db,2d,fa,5a,90,45,e1,bd,4a,f5,f9,3e,1c,f0,57) + _SD_END_DECLARATIONS; #endif diff --git a/src/systemd/sd-netlink.h b/src/systemd/sd-netlink.h index 98088f1204..5d0d0643e1 100644 --- a/src/systemd/sd-netlink.h +++ b/src/systemd/sd-netlink.h @@ -74,6 +74,7 @@ int sd_netlink_message_append_flag(sd_netlink_message *m, unsigned short type); int sd_netlink_message_append_u8(sd_netlink_message *m, unsigned short type, uint8_t data); int sd_netlink_message_append_u16(sd_netlink_message *m, unsigned short type, uint16_t data); int sd_netlink_message_append_u32(sd_netlink_message *m, unsigned short type, uint32_t data); +int sd_netlink_message_append_data(sd_netlink_message *m, unsigned short type, const void *data, size_t len); int sd_netlink_message_append_in_addr(sd_netlink_message *m, unsigned short type, const struct in_addr *data); int sd_netlink_message_append_in6_addr(sd_netlink_message *m, unsigned short type, const struct in6_addr *data); int sd_netlink_message_append_ether_addr(sd_netlink_message *m, unsigned short type, const struct ether_addr *data); diff --git a/src/systemd/sd-network.h b/src/systemd/sd-network.h index 79b4bf9ea3..ff0d2b191e 100644 --- a/src/systemd/sd-network.h +++ b/src/systemd/sd-network.h @@ -64,8 +64,11 @@ int sd_network_get_dns(char ***dns); * representations of IP addresses */ int sd_network_get_ntp(char ***ntp); -/* Get the search/routing domains for all links. */ -int sd_network_get_domains(char ***domains); +/* Get the search domains for all links. */ +int sd_network_get_search_domains(char ***domains); + +/* Get the search domains for all links. */ +int sd_network_get_route_domains(char ***domains); /* Get setup state from ifindex. * Possible states: @@ -111,10 +114,34 @@ int sd_network_link_get_ntp(int ifindex, char ***addr); */ int sd_network_link_get_llmnr(int ifindex, char **llmnr); +/* Indicates whether or not MulticastDNS should be enabled for the + * link. + * Possible levels of support: yes, no, resolve + * Possible return codes: + * -ENODATA: networkd is not aware of the link + */ +int sd_network_link_get_mdns(int ifindex, char **mdns); + +/* Indicates whether or not DNSSEC should be enabled for the link + * Possible levels of support: yes, no, allow-downgrade + * Possible return codes: + * -ENODATA: networkd is not aware of the link + */ +int sd_network_link_get_dnssec(int ifindex, char **dnssec); + +/* Returns the list of per-interface DNSSEC negative trust anchors + * Possible return codes: + * -ENODATA: networkd is not aware of the link, or has no such data + */ +int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char ***nta); + int sd_network_link_get_lldp(int ifindex, char **lldp); -/* Get the DNS domain names for a given link. */ -int sd_network_link_get_domains(int ifindex, char ***domains); +/* Get the search DNS domain names for a given link. */ +int sd_network_link_get_search_domains(int ifindex, char ***domains); + +/* Get the route DNS domain names for a given link. */ +int sd_network_link_get_route_domains(int ifindex, char ***domains); /* Get the CARRIERS to which current link is bound to. */ int sd_network_link_get_carrier_bound_to(int ifindex, char ***carriers); @@ -125,10 +152,6 @@ int sd_network_link_get_carrier_bound_by(int ifindex, char ***carriers); /* Get the timezone that was learnt on a specific link. */ int sd_network_link_get_timezone(int ifindex, char **timezone); -/* Returns whether or not domains that don't match any link should be resolved - * on this link. 1 for yes, 0 for no and negative value for error */ -int sd_network_link_get_wildcard_domain(int ifindex); - /* Monitor object */ typedef struct sd_network_monitor sd_network_monitor; 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..b1dd7e1913 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; } @@ -410,11 +410,13 @@ static int write_files(void) { i = hashmap_get(groups, gr->gr_name); if (i && i->todo_group) { + log_error("%s: Group \"%s\" already exists.", group_path, gr->gr_name); r = -EEXIST; goto finish; } if (hashmap_contains(todo_gids, GID_TO_PTR(gr->gr_gid))) { + log_error("%s: Detected collision for GID " GID_FMT ".", group_path, gr->gr_gid); r = -EEXIST; goto finish; } @@ -482,6 +484,7 @@ static int write_files(void) { i = hashmap_get(groups, sg->sg_namp); if (i && i->todo_group) { + log_error("%s: Group \"%s\" already exists.", gshadow_path, sg->sg_namp); r = -EEXIST; goto finish; } @@ -548,11 +551,13 @@ static int write_files(void) { i = hashmap_get(users, pw->pw_name); if (i && i->todo_user) { + log_error("%s: User \"%s\" already exists.", passwd_path, pw->pw_name); r = -EEXIST; goto finish; } if (hashmap_contains(todo_uids, UID_TO_PTR(pw->pw_uid))) { + log_error("%s: Detected collision for UID " UID_FMT ".", passwd_path, pw->pw_uid); r = -EEXIST; goto finish; } diff --git a/src/sysv-generator/sysv-generator.c b/src/sysv-generator/sysv-generator.c index 5075548507..d48d5abbe3 100644 --- a/src/sysv-generator/sysv-generator.c +++ b/src/sysv-generator/sysv-generator.c @@ -161,7 +161,6 @@ static int add_alias(const char *service, const char *alias) { } static int generate_unit_file(SysvStub *s) { - _cleanup_free_ char *before = NULL, *after = NULL, *wants = NULL, *conflicts = NULL; _cleanup_fclose_ FILE *f = NULL; const char *unit; char **p; @@ -174,14 +173,6 @@ static int generate_unit_file(SysvStub *s) { unit = strjoina(arg_dest, "/", s->name); - before = strv_join(s->before, " "); - after = strv_join(s->after, " "); - wants = strv_join(s->wants, " "); - conflicts = strv_join(s->conflicts, " "); - - if (!before || !after || !wants || !conflicts) - return log_oom(); - /* We might already have a symlink with the same name from a Provides:, * or from backup files like /etc/init.d/foo.bak. Real scripts always win, * so remove an existing link */ @@ -204,14 +195,14 @@ static int generate_unit_file(SysvStub *s) { if (s->description) fprintf(f, "Description=%s\n", s->description); - if (!isempty(before)) - fprintf(f, "Before=%s\n", before); - if (!isempty(after)) - fprintf(f, "After=%s\n", after); - if (!isempty(wants)) - fprintf(f, "Wants=%s\n", wants); - if (!isempty(conflicts)) - fprintf(f, "Conflicts=%s\n", conflicts); + STRV_FOREACH(p, s->before) + fprintf(f, "Before=%s\n", *p); + STRV_FOREACH(p, s->after) + fprintf(f, "After=%s\n", *p); + STRV_FOREACH(p, s->wants) + fprintf(f, "Wants=%s\n", *p); + STRV_FOREACH(p, s->conflicts) + fprintf(f, "Conflicts=%s\n", *p); fprintf(f, "\n[Service]\n" diff --git a/src/test/test-ask-password-api.c b/src/test/test-ask-password-api.c new file mode 100644 index 0000000000..d81f32b632 --- /dev/null +++ b/src/test/test-ask-password-api.c @@ -0,0 +1,40 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2016 Zbigniew Jędrzejewski-Szmek + + 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 "ask-password-api.h" +#include "log.h" + +static void ask_password(void) { + int r; + _cleanup_free_ char *ret; + + r = ask_password_tty("hello?", "da key", 0, 0, NULL, &ret); + assert(r >= 0); + + log_info("Got %s", ret); +} + +int main(int argc, char **argv) { + log_parse_environment(); + + ask_password(); +} diff --git a/src/test/test-capability.c b/src/test/test-capability.c index fc8d3ffe0d..629bb63c81 100644 --- a/src/test/test-capability.c +++ b/src/test/test-capability.c @@ -20,6 +20,7 @@ #include <netinet/in.h> #include <pwd.h> #include <sys/capability.h> +#include <sys/prctl.h> #include <sys/socket.h> #include <sys/wait.h> #include <unistd.h> @@ -66,8 +67,9 @@ static void show_capabilities(void) { cap_free(text); } -static int setup_tests(void) { +static int setup_tests(bool *run_ambient) { struct passwd *nobody; + int r; nobody = getpwnam("nobody"); if (!nobody) { @@ -77,6 +79,18 @@ static int setup_tests(void) { test_uid = nobody->pw_uid; test_gid = nobody->pw_gid; + *run_ambient = false; + + r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0); + + /* There's support for PR_CAP_AMBIENT if the prctl() call + * succeeded or error code was something else than EINVAL. The + * EINVAL check should be good enough to rule out false + * positives. */ + + if (r >= 0 || errno != EINVAL) + *run_ambient = true; + return 0; } @@ -140,8 +154,53 @@ static void test_have_effective_cap(void) { assert_se(!have_effective_cap(CAP_CHOWN)); } +static void test_update_inherited_set(void) { + cap_t caps; + uint64_t set = 0; + cap_flag_value_t fv; + + caps = cap_get_proc(); + assert_se(caps); + assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); + assert(fv == CAP_CLEAR); + + set = (UINT64_C(1) << CAP_CHOWN); + + assert_se(!capability_update_inherited_set(caps, set)); + assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); + assert(fv == CAP_SET); + + cap_free(caps); +} + +static void test_set_ambient_caps(void) { + cap_t caps; + uint64_t set = 0; + cap_flag_value_t fv; + + caps = cap_get_proc(); + assert_se(caps); + assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); + assert(fv == CAP_CLEAR); + cap_free(caps); + + assert_se(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0) == 0); + + set = (UINT64_C(1) << CAP_CHOWN); + + assert_se(!capability_ambient_set_apply(set, true)); + + caps = cap_get_proc(); + assert_se(!cap_get_flag(caps, CAP_CHOWN, CAP_INHERITABLE, &fv)); + assert(fv == CAP_SET); + cap_free(caps); + + assert_se(prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_IS_SET, CAP_CHOWN, 0, 0) == 1); +} + int main(int argc, char *argv[]) { int r; + bool run_ambient; log_parse_environment(); log_open(); @@ -149,14 +208,19 @@ int main(int argc, char *argv[]) { if (getuid() != 0) return EXIT_TEST_SKIP; - r = setup_tests(); + r = setup_tests(&run_ambient); if (r < 0) return -r; show_capabilities(); test_drop_privileges(); + test_update_inherited_set(); + fork_test(test_have_effective_cap); + if (run_ambient) + fork_test(test_set_ambient_caps); + return 0; } diff --git a/src/test/test-cgroup-mask.c b/src/test/test-cgroup-mask.c index 2746013522..a33519b9da 100644 --- a/src/test/test-cgroup-mask.c +++ b/src/test/test-cgroup-mask.c @@ -40,6 +40,7 @@ static int test_cgroup_mask(void) { puts("manager_new: Permission denied. Skipping test."); return EXIT_TEST_SKIP; } + assert_se(r >= 0); /* Turn off all kinds of default accouning, so that we can * verify the masks resulting of our configuration and nothing diff --git a/src/test/test-date.c b/src/test/test-date.c index c6d8bf82ea..851d384117 100644 --- a/src/test/test-date.c +++ b/src/test/test-date.c @@ -27,14 +27,16 @@ static void test_should_pass(const char *p) { usec_t t, q; - char buf[FORMAT_TIMESTAMP_MAX], buf_relative[FORMAT_TIMESTAMP_RELATIVE_MAX]; + char buf[FORMAT_TIMESTAMP_MAX], buf_relative[FORMAT_TIMESTAMP_RELATIVE_MAX], *sp; assert_se(parse_timestamp(p, &t) >= 0); format_timestamp_us(buf, sizeof(buf), t); log_info("%s", buf); /* Chop off timezone */ - *strrchr(buf, ' ') = 0; + sp = strrchr(buf, ' '); + assert_se(sp); + *sp = 0; assert_se(parse_timestamp(buf, &q) >= 0); assert_se(q == t); diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index de003e251c..3efc61ad0a 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -140,9 +140,9 @@ static void test_dns_label_unescape_suffix(void) { test_dns_label_unescape_suffix_one("hallo\\", "hallo", "hallo", 20, -EINVAL, -EINVAL); test_dns_label_unescape_suffix_one("hallo\\032 ", "hallo ", "", 20, 7, 0); test_dns_label_unescape_suffix_one(".", "", "", 20, 0, 0); - test_dns_label_unescape_suffix_one("..", "", "", 20, 0, 0); + test_dns_label_unescape_suffix_one("..", "", "", 20, 0, -EINVAL); test_dns_label_unescape_suffix_one(".foobar", "foobar", "", 20, 6, -EINVAL); - test_dns_label_unescape_suffix_one("foobar.", "", "foobar", 20, 0, 6); + test_dns_label_unescape_suffix_one("foobar.", "foobar", "", 20, 6, 0); test_dns_label_unescape_suffix_one("foo\\\\bar", "foo\\bar", "", 20, 7, 0); test_dns_label_unescape_suffix_one("foo.bar", "bar", "foo", 20, 3, 3); test_dns_label_unescape_suffix_one("foo..bar", "bar", "", 20, 3, -EINVAL); @@ -168,7 +168,7 @@ static void test_dns_label_escape_one(const char *what, size_t l, const char *ex static void test_dns_label_escape(void) { test_dns_label_escape_one("", 0, NULL, -EINVAL); test_dns_label_escape_one("hallo", 5, "hallo", 5); - test_dns_label_escape_one("hallo", 6, NULL, -EINVAL); + test_dns_label_escape_one("hallo", 6, "hallo\\000", 9); test_dns_label_escape_one("hallo hallo.foobar,waldi", 24, "hallo\\032hallo\\.foobar\\044waldi", 31); } @@ -186,15 +186,15 @@ static void test_dns_name_normalize_one(const char *what, const char *expect, in } static void test_dns_name_normalize(void) { - test_dns_name_normalize_one("", "", 0); + test_dns_name_normalize_one("", ".", 0); test_dns_name_normalize_one("f", "f", 0); test_dns_name_normalize_one("f.waldi", "f.waldi", 0); test_dns_name_normalize_one("f \\032.waldi", "f\\032\\032.waldi", 0); - test_dns_name_normalize_one("\\000", NULL, -EINVAL); + test_dns_name_normalize_one("\\000", "\\000", 0); test_dns_name_normalize_one("..", NULL, -EINVAL); test_dns_name_normalize_one(".foobar", NULL, -EINVAL); test_dns_name_normalize_one("foobar.", "foobar", 0); - test_dns_name_normalize_one(".", "", 0); + test_dns_name_normalize_one(".", ".", 0); } static void test_dns_name_equal_one(const char *a, const char *b, int ret) { @@ -216,7 +216,7 @@ static void test_dns_name_equal(void) { test_dns_name_equal_one("abc.def", "CBA.def", false); test_dns_name_equal_one("", "xxx", false); test_dns_name_equal_one("ab", "a", false); - test_dns_name_equal_one("\\000", "xxxx", -EINVAL); + test_dns_name_equal_one("\\000", "\\000", true); test_dns_name_equal_one(".", "", true); test_dns_name_equal_one(".", ".", true); test_dns_name_equal_one("..", "..", -EINVAL); @@ -276,6 +276,25 @@ static void test_dns_name_endswith(void) { test_dns_name_endswith_one("x.y\001.z", "waldo", -EINVAL); } +static void test_dns_name_startswith_one(const char *a, const char *b, int ret) { + assert_se(dns_name_startswith(a, b) == ret); +} + +static void test_dns_name_startswith(void) { + test_dns_name_startswith_one("", "", true); + test_dns_name_startswith_one("", "xxx", false); + test_dns_name_startswith_one("xxx", "", true); + test_dns_name_startswith_one("x", "x", true); + test_dns_name_startswith_one("x", "y", false); + test_dns_name_startswith_one("x.y", "x.y", true); + test_dns_name_startswith_one("x.y", "y.x", false); + test_dns_name_startswith_one("x.y", "x", true); + test_dns_name_startswith_one("x.y", "X", true); + test_dns_name_startswith_one("x.y", "y", false); + test_dns_name_startswith_one("x.y", "", true); + test_dns_name_startswith_one("x.y", "X", true); +} + static void test_dns_name_is_root(void) { assert_se(dns_name_is_root("")); assert_se(dns_name_is_root(".")); @@ -321,10 +340,18 @@ static void test_dns_name_concat_one(const char *a, const char *b, int r, const } static void test_dns_name_concat(void) { + test_dns_name_concat_one("", "", 0, "."); + test_dns_name_concat_one(".", "", 0, "."); + test_dns_name_concat_one("", ".", 0, "."); + test_dns_name_concat_one(".", ".", 0, "."); test_dns_name_concat_one("foo", "bar", 0, "foo.bar"); test_dns_name_concat_one("foo.foo", "bar.bar", 0, "foo.foo.bar.bar"); test_dns_name_concat_one("foo", NULL, 0, "foo"); + test_dns_name_concat_one("foo", ".", 0, "foo"); test_dns_name_concat_one("foo.", "bar.", 0, "foo.bar"); + test_dns_name_concat_one(NULL, NULL, 0, "."); + test_dns_name_concat_one(NULL, ".", 0, "."); + test_dns_name_concat_one(NULL, "foo", 0, "foo"); } static void test_dns_name_is_valid_one(const char *s, int ret) { @@ -410,7 +437,7 @@ static void test_dns_service_join_one(const char *a, const char *b, const char * assert_se(dns_service_split(t, &x, &y, &z) >= 0); assert_se(streq_ptr(a, x)); assert_se(streq_ptr(b, y)); - assert_se(streq_ptr(c, z)); + assert_se(dns_name_equal(c, z) > 0); } static void test_dns_service_join(void) { @@ -441,18 +468,18 @@ static void test_dns_service_split_one(const char *joined, const char *a, const if (y) { assert_se(dns_service_join(x, y, z, &t) == 0); - assert_se(streq_ptr(joined, t)); + assert_se(dns_name_equal(joined, t) > 0); } else - assert_se(!x && streq_ptr(z, joined)); + assert_se(!x && dns_name_equal(z, joined) > 0); } static void test_dns_service_split(void) { - test_dns_service_split_one("", NULL, NULL, "", 0); + test_dns_service_split_one("", NULL, NULL, ".", 0); test_dns_service_split_one("foo", NULL, NULL, "foo", 0); test_dns_service_split_one("foo.bar", NULL, NULL, "foo.bar", 0); test_dns_service_split_one("_foo.bar", NULL, NULL, "_foo.bar", 0); - test_dns_service_split_one("_foo._bar", NULL, "_foo._bar", "", 0); - test_dns_service_split_one("_meh._foo._bar", "_meh", "_foo._bar", "", 0); + test_dns_service_split_one("_foo._bar", NULL, "_foo._bar", ".", 0); + test_dns_service_split_one("_meh._foo._bar", "_meh", "_foo._bar", ".", 0); test_dns_service_split_one("Wuff\\032Wuff._foo._bar.waldo.com", "Wuff Wuff", "_foo._bar", "waldo.com", 0); } @@ -471,10 +498,134 @@ static void test_dns_name_change_suffix(void) { test_dns_name_change_suffix_one("foo.bar.waldi.quux", "quux", "piff.paff", 1, "foo.bar.waldi.piff.paff"); test_dns_name_change_suffix_one("foo.bar.waldi.quux", "", "piff.paff", 1, "foo.bar.waldi.quux.piff.paff"); test_dns_name_change_suffix_one("", "", "piff.paff", 1, "piff.paff"); - test_dns_name_change_suffix_one("", "", "", 1, ""); + test_dns_name_change_suffix_one("", "", "", 1, "."); test_dns_name_change_suffix_one("a", "b", "c", 0, NULL); } +static void test_dns_name_suffix_one(const char *name, unsigned n_labels, const char *result, int ret) { + const char *p = NULL; + + assert_se(ret == dns_name_suffix(name, n_labels, &p)); + assert_se(streq_ptr(p, result)); +} + +static void test_dns_name_suffix(void) { + test_dns_name_suffix_one("foo.bar", 2, "foo.bar", 0); + test_dns_name_suffix_one("foo.bar", 1, "bar", 1); + test_dns_name_suffix_one("foo.bar", 0, "", 2); + test_dns_name_suffix_one("foo.bar", 3, NULL, -EINVAL); + test_dns_name_suffix_one("foo.bar", 4, NULL, -EINVAL); + + test_dns_name_suffix_one("bar", 1, "bar", 0); + test_dns_name_suffix_one("bar", 0, "", 1); + test_dns_name_suffix_one("bar", 2, NULL, -EINVAL); + test_dns_name_suffix_one("bar", 3, NULL, -EINVAL); + + test_dns_name_suffix_one("", 0, "", 0); + test_dns_name_suffix_one("", 1, NULL, -EINVAL); + test_dns_name_suffix_one("", 2, NULL, -EINVAL); +} + +static void test_dns_name_count_labels_one(const char *name, int n) { + assert_se(dns_name_count_labels(name) == n); +} + +static void test_dns_name_count_labels(void) { + test_dns_name_count_labels_one("foo.bar.quux.", 3); + test_dns_name_count_labels_one("foo.bar.quux", 3); + test_dns_name_count_labels_one("foo.bar.", 2); + test_dns_name_count_labels_one("foo.bar", 2); + test_dns_name_count_labels_one("foo.", 1); + test_dns_name_count_labels_one("foo", 1); + test_dns_name_count_labels_one("", 0); + test_dns_name_count_labels_one(".", 0); + test_dns_name_count_labels_one("..", -EINVAL); +} + +static void test_dns_name_equal_skip_one(const char *a, unsigned n_labels, const char *b, int ret) { + assert_se(dns_name_equal_skip(a, n_labels, b) == ret); +} + +static void test_dns_name_equal_skip(void) { + test_dns_name_equal_skip_one("foo", 0, "bar", 0); + test_dns_name_equal_skip_one("foo", 0, "foo", 1); + test_dns_name_equal_skip_one("foo", 1, "foo", 0); + test_dns_name_equal_skip_one("foo", 2, "foo", 0); + + test_dns_name_equal_skip_one("foo.bar", 0, "foo.bar", 1); + test_dns_name_equal_skip_one("foo.bar", 1, "foo.bar", 0); + test_dns_name_equal_skip_one("foo.bar", 2, "foo.bar", 0); + test_dns_name_equal_skip_one("foo.bar", 3, "foo.bar", 0); + + test_dns_name_equal_skip_one("foo.bar", 0, "bar", 0); + test_dns_name_equal_skip_one("foo.bar", 1, "bar", 1); + test_dns_name_equal_skip_one("foo.bar", 2, "bar", 0); + test_dns_name_equal_skip_one("foo.bar", 3, "bar", 0); + + test_dns_name_equal_skip_one("foo.bar", 0, "", 0); + test_dns_name_equal_skip_one("foo.bar", 1, "", 0); + test_dns_name_equal_skip_one("foo.bar", 2, "", 1); + test_dns_name_equal_skip_one("foo.bar", 3, "", 0); + + test_dns_name_equal_skip_one("", 0, "", 1); + test_dns_name_equal_skip_one("", 1, "", 0); + test_dns_name_equal_skip_one("", 1, "foo", 0); + test_dns_name_equal_skip_one("", 2, "foo", 0); +} + +static void test_dns_name_compare_func(void) { + assert_se(dns_name_compare_func("", "") == 0); + assert_se(dns_name_compare_func("", ".") == 0); + assert_se(dns_name_compare_func(".", "") == 0); + assert_se(dns_name_compare_func("foo", "foo.") == 0); + assert_se(dns_name_compare_func("foo.", "foo") == 0); + assert_se(dns_name_compare_func("foo", "foo") == 0); + assert_se(dns_name_compare_func("foo.", "foo.") == 0); + assert_se(dns_name_compare_func("heise.de", "HEISE.DE.") == 0); + + 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(); @@ -483,6 +634,7 @@ int main(int argc, char *argv[]) { test_dns_name_normalize(); test_dns_name_equal(); test_dns_name_endswith(); + test_dns_name_startswith(); test_dns_name_between(); test_dns_name_is_root(); test_dns_name_is_single_label(); @@ -495,6 +647,12 @@ int main(int argc, char *argv[]) { test_dns_service_join(); test_dns_service_split(); test_dns_name_change_suffix(); + test_dns_name_suffix(); + test_dns_name_count_labels(); + test_dns_name_equal_skip(); + test_dns_name_compare_func(); + test_dns_name_common_suffix(); + test_dns_name_apply_idna(); return 0; } diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 753afadb0a..92857cb5e2 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -20,6 +20,7 @@ #include <grp.h> #include <pwd.h> #include <stdio.h> +#include <sys/prctl.h> #include <sys/types.h> #include "fileio.h" @@ -224,6 +225,20 @@ static void test_exec_capabilityboundingset(Manager *m) { test(m, "exec-capabilityboundingset-invert.service", 0, CLD_EXITED); } +static void test_exec_capabilityambientset(Manager *m) { + int r; + + /* Check if the kernel has support for ambient capabilities. Run + * the tests only if that's the case. Clearing all ambient + * capabilities is fine, since we are expecting them to be unset + * in the first place for the tests. */ + r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0); + if (r >= 0 || errno != EINVAL) { + test(m, "exec-capabilityambientset.service", 0, CLD_EXITED); + test(m, "exec-capabilityambientset-merge.service", 0, CLD_EXITED); + } +} + static void test_exec_privatenetwork(Manager *m) { int r; @@ -266,6 +281,7 @@ int main(int argc, char *argv[]) { test_exec_umask, test_exec_runtimedirectory, test_exec_capabilityboundingset, + test_exec_capabilityambientset, test_exec_oomscoreadjust, test_exec_ioschedulingclass, NULL, diff --git a/src/test/test-libudev.c b/src/test/test-libudev.c index 350eaf734d..94d852b3b0 100644 --- a/src/test/test-libudev.c +++ b/src/test/test-libudev.c @@ -25,6 +25,7 @@ #include "libudev.h" +#include "stdio-util.h" #include "string-util.h" #include "udev-util.h" #include "util.h" @@ -460,7 +461,7 @@ int main(int argc, char *argv[]) { /* add sys path if needed */ if (!startswith(syspath, "/sys")) { - snprintf(path, sizeof(path), "/sys/%s", syspath); + xsprintf(path, "/sys/%s", syspath); syspath = path; } diff --git a/src/test/test-rlimit-util.c b/src/test/test-rlimit-util.c index 00d3ecc0de..d9ac9368cd 100644 --- a/src/test/test-rlimit-util.c +++ b/src/test/test-rlimit-util.c @@ -17,12 +17,37 @@ #include <sys/resource.h> +#include "alloc-util.h" #include "capability-util.h" #include "macro.h" #include "rlimit-util.h" #include "string-util.h" #include "util.h" +static void test_rlimit_parse_format(int resource, const char *string, rlim_t soft, rlim_t hard, int ret, const char *formatted) { + _cleanup_free_ char *f = NULL; + struct rlimit rl = { + .rlim_cur = 4711, + .rlim_max = 4712, + }, rl2 = { + .rlim_cur = 4713, + .rlim_max = 4714 + }; + + assert_se(rlimit_parse(resource, string, &rl) == ret); + if (ret < 0) + return; + + assert_se(rl.rlim_cur == soft); + assert_se(rl.rlim_max == hard); + + assert_se(rlimit_format(&rl, &f) >= 0); + assert_se(streq(formatted, f)); + + assert_se(rlimit_parse(resource, formatted, &rl2) >= 0); + assert_se(memcmp(&rl, &rl2, sizeof(struct rlimit)) == 0); +} + int main(int argc, char *argv[]) { struct rlimit old, new, high; struct rlimit err = { @@ -37,7 +62,7 @@ int main(int argc, char *argv[]) { assert_se(getrlimit(RLIMIT_NOFILE, &old) == 0); new.rlim_cur = MIN(5U, old.rlim_max); - new.rlim_max = MIN(10U, old.rlim_max); + new.rlim_max = old.rlim_max; assert_se(setrlimit(RLIMIT_NOFILE, &new) >= 0); assert_se(rlimit_from_string("LimitNOFILE") == RLIMIT_NOFILE); @@ -53,7 +78,7 @@ int main(int argc, char *argv[]) { assert_se(old.rlim_max == new.rlim_max); assert_se(getrlimit(RLIMIT_NOFILE, &old) == 0); - high = RLIMIT_MAKE_CONST(old.rlim_max + 1); + high = RLIMIT_MAKE_CONST(old.rlim_max == RLIM_INFINITY ? old.rlim_max : old.rlim_max + 1); assert_se(setrlimit_closest(RLIMIT_NOFILE, &high) == 0); assert_se(getrlimit(RLIMIT_NOFILE, &new) == 0); assert_se(new.rlim_max == old.rlim_max); @@ -65,5 +90,15 @@ int main(int argc, char *argv[]) { assert_se(old.rlim_cur == new.rlim_cur); assert_se(old.rlim_max == new.rlim_max); + test_rlimit_parse_format(RLIMIT_NOFILE, "4:5", 4, 5, 0, "4:5"); + test_rlimit_parse_format(RLIMIT_NOFILE, "6", 6, 6, 0, "6"); + test_rlimit_parse_format(RLIMIT_NOFILE, "infinity", RLIM_INFINITY, RLIM_INFINITY, 0, "infinity"); + test_rlimit_parse_format(RLIMIT_NOFILE, "infinity:infinity", RLIM_INFINITY, RLIM_INFINITY, 0, "infinity"); + test_rlimit_parse_format(RLIMIT_NOFILE, "8:infinity", 8, RLIM_INFINITY, 0, "8:infinity"); + test_rlimit_parse_format(RLIMIT_CPU, "25min:13h", (25*USEC_PER_MINUTE) / USEC_PER_SEC, (13*USEC_PER_HOUR) / USEC_PER_SEC, 0, "1500:46800"); + test_rlimit_parse_format(RLIMIT_NOFILE, "", 0, 0, -EINVAL, NULL); + test_rlimit_parse_format(RLIMIT_NOFILE, "5:4", 0, 0, -EILSEQ, NULL); + test_rlimit_parse_format(RLIMIT_NOFILE, "5:4:3", 0, 0, -EINVAL, NULL); + return 0; } diff --git a/src/test/test-signal-util.c b/src/test/test-signal-util.c new file mode 100644 index 0000000000..3083501ce9 --- /dev/null +++ b/src/test/test-signal-util.c @@ -0,0 +1,49 @@ +/*** + 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 "signal-util.h" + +static void test_block_signals(void) { + sigset_t ss; + + assert_se(sigprocmask(0, NULL, &ss) >= 0); + + assert_se(sigismember(&ss, SIGUSR1) == 0); + assert_se(sigismember(&ss, SIGALRM) == 0); + assert_se(sigismember(&ss, SIGVTALRM) == 0); + + { + BLOCK_SIGNALS(SIGUSR1, SIGVTALRM); + + assert_se(sigprocmask(0, NULL, &ss) >= 0); + assert_se(sigismember(&ss, SIGUSR1) == 1); + assert_se(sigismember(&ss, SIGALRM) == 0); + assert_se(sigismember(&ss, SIGVTALRM) == 1); + + } + + assert_se(sigprocmask(0, NULL, &ss) >= 0); + assert_se(sigismember(&ss, SIGUSR1) == 0); + assert_se(sigismember(&ss, SIGALRM) == 0); + assert_se(sigismember(&ss, SIGVTALRM) == 0); +} + +int main(int argc, char *argv[]) { + test_block_signals(); +} diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index 25444c794a..12889ce873 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -55,7 +55,53 @@ static void test_string_erase(void) { assert_se(streq(string_erase(x), "xxxxxxxxx")); } +static void test_ascii_strcasecmp_n(void) { + + assert_se(ascii_strcasecmp_n("", "", 0) == 0); + assert_se(ascii_strcasecmp_n("", "", 1) == 0); + assert_se(ascii_strcasecmp_n("", "a", 1) < 0); + assert_se(ascii_strcasecmp_n("", "a", 2) < 0); + assert_se(ascii_strcasecmp_n("a", "", 1) > 0); + assert_se(ascii_strcasecmp_n("a", "", 2) > 0); + assert_se(ascii_strcasecmp_n("a", "a", 1) == 0); + assert_se(ascii_strcasecmp_n("a", "a", 2) == 0); + assert_se(ascii_strcasecmp_n("a", "b", 1) < 0); + assert_se(ascii_strcasecmp_n("a", "b", 2) < 0); + assert_se(ascii_strcasecmp_n("b", "a", 1) > 0); + assert_se(ascii_strcasecmp_n("b", "a", 2) > 0); + assert_se(ascii_strcasecmp_n("xxxxyxxxx", "xxxxYxxxx", 9) == 0); + assert_se(ascii_strcasecmp_n("xxxxxxxxx", "xxxxyxxxx", 9) < 0); + assert_se(ascii_strcasecmp_n("xxxxXxxxx", "xxxxyxxxx", 9) < 0); + assert_se(ascii_strcasecmp_n("xxxxxxxxx", "xxxxYxxxx", 9) < 0); + assert_se(ascii_strcasecmp_n("xxxxXxxxx", "xxxxYxxxx", 9) < 0); + + assert_se(ascii_strcasecmp_n("xxxxYxxxx", "xxxxYxxxx", 9) == 0); + assert_se(ascii_strcasecmp_n("xxxxyxxxx", "xxxxxxxxx", 9) > 0); + assert_se(ascii_strcasecmp_n("xxxxyxxxx", "xxxxXxxxx", 9) > 0); + assert_se(ascii_strcasecmp_n("xxxxYxxxx", "xxxxxxxxx", 9) > 0); + assert_se(ascii_strcasecmp_n("xxxxYxxxx", "xxxxXxxxx", 9) > 0); +} + +static void test_ascii_strcasecmp_nn(void) { + assert_se(ascii_strcasecmp_nn("", 0, "", 0) == 0); + assert_se(ascii_strcasecmp_nn("", 0, "", 1) < 0); + assert_se(ascii_strcasecmp_nn("", 1, "", 0) > 0); + assert_se(ascii_strcasecmp_nn("", 1, "", 1) == 0); + + assert_se(ascii_strcasecmp_nn("aaaa", 4, "aaAa", 4) == 0); + assert_se(ascii_strcasecmp_nn("aaa", 3, "aaAa", 4) < 0); + assert_se(ascii_strcasecmp_nn("aaa", 4, "aaAa", 4) < 0); + assert_se(ascii_strcasecmp_nn("aaaa", 4, "aaA", 3) > 0); + assert_se(ascii_strcasecmp_nn("aaaa", 4, "AAA", 4) > 0); + + assert_se(ascii_strcasecmp_nn("aaaa", 4, "bbbb", 4) < 0); + assert_se(ascii_strcasecmp_nn("aaAA", 4, "BBbb", 4) < 0); + assert_se(ascii_strcasecmp_nn("BBbb", 4, "aaaa", 4) > 0); +} + int main(int argc, char *argv[]) { test_string_erase(); + test_ascii_strcasecmp_n(); + test_ascii_strcasecmp_nn(); return 0; } diff --git a/src/test/test-time.c b/src/test/test-time.c index 8896b2c92b..254a8d0e52 100644 --- a/src/test/test-time.c +++ b/src/test/test-time.c @@ -176,12 +176,24 @@ static void test_get_timezones(void) { r = get_timezones(&zones); assert_se(r == 0); - STRV_FOREACH(zone, zones) { + STRV_FOREACH(zone, zones) assert_se(timezone_is_valid(*zone)); - } +} + +static void test_usec_add(void) { + assert_se(usec_add(0, 0) == 0); + assert_se(usec_add(1, 4) == 5); + assert_se(usec_add(USEC_INFINITY, 5) == USEC_INFINITY); + assert_se(usec_add(5, USEC_INFINITY) == USEC_INFINITY); + assert_se(usec_add(USEC_INFINITY-5, 2) == USEC_INFINITY-3); + assert_se(usec_add(USEC_INFINITY-2, 2) == USEC_INFINITY); + assert_se(usec_add(USEC_INFINITY-1, 2) == USEC_INFINITY); + assert_se(usec_add(USEC_INFINITY, 2) == USEC_INFINITY); } int main(int argc, char *argv[]) { + uintmax_t x; + test_parse_sec(); test_parse_time(); test_parse_nsec(); @@ -190,6 +202,15 @@ int main(int argc, char *argv[]) { test_format_timespan(USEC_PER_SEC); test_timezone_is_valid(); test_get_timezones(); + test_usec_add(); + + /* Ensure time_t is signed */ + assert_cc((time_t) -1 < (time_t) 1); + + /* Ensure TIME_T_MAX works correctly */ + x = (uintmax_t) TIME_T_MAX; + x ++; + assert((time_t) x < 0); return 0; } diff --git a/src/test/test-tmpfiles.c b/src/test/test-tmpfiles.c index a8bd722e44..23f26369bd 100644 --- a/src/test/test-tmpfiles.c +++ b/src/test/test-tmpfiles.c @@ -28,6 +28,8 @@ #include "fd-util.h" #include "fileio.h" #include "formats-util.h" +#include "fs-util.h" +#include "log.h" #include "string-util.h" #include "util.h" @@ -35,20 +37,29 @@ int main(int argc, char** argv) { const char *p = argv[1] ?: "/tmp"; char *pattern = strjoina(p, "/systemd-test-XXXXXX"); _cleanup_close_ int fd, fd2; - _cleanup_free_ char *cmd, *cmd2; + _cleanup_free_ char *cmd, *cmd2, *ans, *ans2; + + log_set_max_level(LOG_DEBUG); + log_parse_environment(); fd = open_tmpfile(p, O_RDWR|O_CLOEXEC); assert_se(fd >= 0); assert_se(asprintf(&cmd, "ls -l /proc/"PID_FMT"/fd/%d", getpid(), fd) > 0); - system(cmd); + (void) system(cmd); + assert_se(readlink_malloc(cmd + 6, &ans) >= 0); + log_debug("link1: %s", ans); + assert_se(endswith(ans, " (deleted)")); fd2 = mkostemp_safe(pattern, O_RDWR|O_CLOEXEC); assert_se(fd >= 0); assert_se(unlink(pattern) == 0); assert_se(asprintf(&cmd2, "ls -l /proc/"PID_FMT"/fd/%d", getpid(), fd2) > 0); - system(cmd2); + (void) system(cmd2); + assert_se(readlink_malloc(cmd2 + 6, &ans2) >= 0); + log_debug("link2: %s", ans2); + assert_se(endswith(ans2, " (deleted)")); return 0; } diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c index 0b3630f77c..a458870846 100644 --- a/src/test/test-unit-file.c +++ b/src/test/test-unit-file.c @@ -28,6 +28,7 @@ #include <unistd.h> #include "alloc-util.h" +#include "capability-util.h" #include "fd-util.h" #include "fileio.h" #include "hashmap.h" @@ -111,17 +112,30 @@ static void test_config_parse_exec(void) { ExecCommand *c = NULL, *c1; const char *ccc; + Manager *m = NULL; + Unit *u = NULL; + + r = manager_new(MANAGER_USER, true, &m); + if (MANAGER_SKIP_TEST(r)) { + printf("Skipping test: manager_new: %s\n", strerror(-r)); + return; + } + + assert_se(r >= 0); + assert_se(manager_startup(m, NULL, NULL) >= 0); + + assert_se(u = unit_new(m, sizeof(Service))); log_info("/* basic test */"); r = config_parse_exec(NULL, "fake", 1, "section", 1, "LValue", 0, "/RValue r1", - &c, NULL); + &c, u); assert_se(r >= 0); check_execcommand(c, "/RValue", "/RValue", "r1", NULL, false); r = config_parse_exec(NULL, "fake", 2, "section", 1, "LValue", 0, "/RValue///slashes r1///", - &c, NULL); + &c, u); log_info("/* test slashes */"); assert_se(r >= 0); @@ -131,14 +145,14 @@ static void test_config_parse_exec(void) { log_info("/* trailing slash */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "/RValue/ argv0 r1", - &c, NULL); + &c, u); assert_se(r == 0); assert_se(c1->command_next == NULL); log_info("/* honour_argv0 */"); r = config_parse_exec(NULL, "fake", 3, "section", 1, "LValue", 0, "@/RValue///slashes2 ///argv0 r1", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, "/RValue/slashes2", "///argv0", "r1", NULL, false); @@ -146,21 +160,21 @@ static void test_config_parse_exec(void) { log_info("/* honour_argv0, no args */"); r = config_parse_exec(NULL, "fake", 3, "section", 1, "LValue", 0, "@/RValue", - &c, NULL); + &c, u); assert_se(r == 0); assert_se(c1->command_next == NULL); log_info("/* no command, whitespace only, reset */"); r = config_parse_exec(NULL, "fake", 3, "section", 1, "LValue", 0, " ", - &c, NULL); + &c, u); assert_se(r == 0); assert_se(c == NULL); log_info("/* ignore && honour_argv0 */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "-@/RValue///slashes3 argv0a r1", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c; check_execcommand(c1, "/RValue/slashes3", "argv0a", "r1", NULL, true); @@ -168,7 +182,7 @@ static void test_config_parse_exec(void) { log_info("/* ignore && honour_argv0 */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "@-/RValue///slashes4 argv0b r1", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, "/RValue/slashes4", "argv0b", "r1", NULL, true); @@ -176,14 +190,14 @@ static void test_config_parse_exec(void) { log_info("/* ignore && ignore */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "--/RValue argv0 r1", - &c, NULL); + &c, u); assert_se(r == 0); assert_se(c1->command_next == NULL); log_info("/* ignore && ignore (2) */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "-@-/RValue argv0 r1", - &c, NULL); + &c, u); assert_se(r == 0); assert_se(c1->command_next == NULL); @@ -192,7 +206,7 @@ static void test_config_parse_exec(void) { "LValue", 0, "-@/RValue argv0 r1 ; " "/goo/goo boo", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, "/RValue", "argv0", "r1", NULL, true); @@ -205,7 +219,7 @@ static void test_config_parse_exec(void) { "LValue", 0, "-@/RValue argv0 r1 ; ; " "/goo/goo boo", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, "/RValue", "argv0", "r1", NULL, true); @@ -217,7 +231,7 @@ static void test_config_parse_exec(void) { r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "-@/RValue argv0 r1 ; ", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, "/RValue", "argv0", "r1", NULL, true); @@ -228,7 +242,7 @@ static void test_config_parse_exec(void) { r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "-@/RValue argv0 r1 ;", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, "/RValue", "argv0", "r1", NULL, true); @@ -239,7 +253,7 @@ static void test_config_parse_exec(void) { r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "-@/RValue argv0 r1 ';'", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, "/RValue", "argv0", "r1", ";", true); @@ -248,7 +262,7 @@ static void test_config_parse_exec(void) { r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "/bin/find \\;", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, "/bin/find", NULL, ";", NULL, false); @@ -257,7 +271,7 @@ static void test_config_parse_exec(void) { r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "/sbin/find \\; /x", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, @@ -267,7 +281,7 @@ static void test_config_parse_exec(void) { r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "/sbin/find \\;x", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, @@ -277,7 +291,7 @@ static void test_config_parse_exec(void) { r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "/bin/find \\073", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, "/bin/find", NULL, ";", NULL, false); @@ -286,7 +300,7 @@ static void test_config_parse_exec(void) { r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "/bin/find \";\"", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, "/bin/find", NULL, ";", NULL, false); @@ -295,7 +309,7 @@ static void test_config_parse_exec(void) { r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "/sbin/find \";\" /x", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, @@ -305,7 +319,7 @@ static void test_config_parse_exec(void) { r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "\"/PATH WITH SPACES/daemon\" -1 -2", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, @@ -315,7 +329,7 @@ static void test_config_parse_exec(void) { r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "\"/PATH WITH SPACES/daemon -1 -2\"", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, @@ -325,7 +339,7 @@ static void test_config_parse_exec(void) { r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "\"/PATH WITH SPACES/daemon\" \"-1\" '-2'", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, @@ -335,7 +349,7 @@ static void test_config_parse_exec(void) { r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "\"/PATH\\sWITH\\sSPACES/daemon\" '-1 -2'", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, @@ -345,7 +359,7 @@ static void test_config_parse_exec(void) { r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "\"/PATH\\x20WITH\\x20SPACES/daemon\" \"-1 -2\"", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, @@ -359,7 +373,7 @@ static void test_config_parse_exec(void) { log_info("/* invalid character: \\%c */", *ccc); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, path, - &c, NULL); + &c, u); assert_se(r == 0); assert_se(c1->command_next == NULL); } @@ -367,7 +381,7 @@ static void test_config_parse_exec(void) { log_info("/* valid character: \\s */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "/path\\s", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, "/path ", NULL, NULL, NULL, false); @@ -376,7 +390,7 @@ static void test_config_parse_exec(void) { r = config_parse_exec(NULL, "fake", 5, "section", 1, "LValue", 0, "/bin/grep '\\w+\\K'", - &c, NULL); + &c, u); assert_se(r >= 0); c1 = c1->command_next; check_execcommand(c1, "/bin/grep", NULL, "\\w+\\K", NULL, false); @@ -386,46 +400,49 @@ static void test_config_parse_exec(void) { /* backslash is invalid */ r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "/path\\", - &c, NULL); + &c, u); assert_se(r == 0); assert_se(c1->command_next == NULL); log_info("/* missing ending ' */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "/path 'foo", - &c, NULL); + &c, u); assert_se(r == 0); assert_se(c1->command_next == NULL); log_info("/* missing ending ' with trailing backslash */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "/path 'foo\\", - &c, NULL); + &c, u); assert_se(r == 0); assert_se(c1->command_next == NULL); log_info("/* invalid space between modifiers */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "- /path", - &c, NULL); + &c, u); assert_se(r == 0); assert_se(c1->command_next == NULL); log_info("/* only modifiers, no path */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "-", - &c, NULL); + &c, u); assert_se(r == 0); assert_se(c1->command_next == NULL); log_info("/* empty argument, reset */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "", - &c, NULL); + &c, u); assert_se(r == 0); assert_se(c == NULL); exec_command_free_list(c); + + unit_free(u); + manager_free(m); } #define env_file_1 \ @@ -625,8 +642,8 @@ static uint64_t make_cap(int cap) { return ((uint64_t) 1ULL << (uint64_t) cap); } -static void test_config_parse_bounding_set(void) { - /* int config_parse_bounding_set( +static void test_config_parse_capability_set(void) { + /* int config_parse_capability_set( const char *unit, const char *filename, unsigned line, @@ -638,38 +655,38 @@ static void test_config_parse_bounding_set(void) { void *data, void *userdata) */ int r; - uint64_t capability_bounding_set_drop = 0; + uint64_t capability_bounding_set = 0; - r = config_parse_bounding_set(NULL, "fake", 1, "section", 1, + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, "CapabilityBoundingSet", 0, "CAP_NET_RAW", - &capability_bounding_set_drop, NULL); + &capability_bounding_set, NULL); assert_se(r >= 0); - assert_se(capability_bounding_set_drop == ~make_cap(CAP_NET_RAW)); + assert_se(capability_bounding_set == make_cap(CAP_NET_RAW)); - r = config_parse_bounding_set(NULL, "fake", 1, "section", 1, + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, "CapabilityBoundingSet", 0, "CAP_NET_ADMIN", - &capability_bounding_set_drop, NULL); + &capability_bounding_set, NULL); assert_se(r >= 0); - assert_se(capability_bounding_set_drop == ~(make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN))); + assert_se(capability_bounding_set == (make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN))); - r = config_parse_bounding_set(NULL, "fake", 1, "section", 1, + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, "CapabilityBoundingSet", 0, "", - &capability_bounding_set_drop, NULL); + &capability_bounding_set, NULL); assert_se(r >= 0); - assert_se(capability_bounding_set_drop == ~((uint64_t) 0ULL)); + assert_se(capability_bounding_set == UINT64_C(0)); - r = config_parse_bounding_set(NULL, "fake", 1, "section", 1, + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, "CapabilityBoundingSet", 0, "~", - &capability_bounding_set_drop, NULL); + &capability_bounding_set, NULL); assert_se(r >= 0); - assert_se(capability_bounding_set_drop == (uint64_t) 0ULL); + assert_se(cap_test_all(capability_bounding_set)); - capability_bounding_set_drop = 0; - r = config_parse_bounding_set(NULL, "fake", 1, "section", 1, + capability_bounding_set = 0; + r = config_parse_capability_set(NULL, "fake", 1, "section", 1, "CapabilityBoundingSet", 0, " 'CAP_NET_RAW' WAT_CAP??? CAP_NET_ADMIN CAP'_trailing_garbage", - &capability_bounding_set_drop, NULL); + &capability_bounding_set, NULL); assert_se(r >= 0); - assert_se(capability_bounding_set_drop == ~(make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN))); + assert_se(capability_bounding_set == (make_cap(CAP_NET_RAW) | make_cap(CAP_NET_ADMIN))); } static void test_config_parse_rlimit(void) { @@ -695,12 +712,15 @@ static void test_config_parse_rlimit(void) { assert_se(rl[RLIMIT_NOFILE]->rlim_cur == RLIM_INFINITY); assert_se(rl[RLIMIT_NOFILE]->rlim_cur == rl[RLIMIT_NOFILE]->rlim_max); + rl[RLIMIT_NOFILE]->rlim_cur = 10; + rl[RLIMIT_NOFILE]->rlim_max = 20; + + /* Invalid values don't change rl */ assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "10:20:30", rl, NULL) >= 0); assert_se(rl[RLIMIT_NOFILE]); assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 10); assert_se(rl[RLIMIT_NOFILE]->rlim_max == 20); - /* Invalid values don't change rl */ assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "wat:wat", rl, NULL) >= 0); assert_se(rl[RLIMIT_NOFILE]); assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 10); @@ -718,64 +738,64 @@ static void test_config_parse_rlimit(void) { rl[RLIMIT_NOFILE] = mfree(rl[RLIMIT_NOFILE]); - assert_se(config_parse_sec_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "56", rl, NULL) >= 0); + assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "56", rl, NULL) >= 0); assert_se(rl[RLIMIT_CPU]); assert_se(rl[RLIMIT_CPU]->rlim_cur == 56); assert_se(rl[RLIMIT_CPU]->rlim_cur == rl[RLIMIT_CPU]->rlim_max); - assert_se(config_parse_sec_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "57s", rl, NULL) >= 0); + assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "57s", rl, NULL) >= 0); assert_se(rl[RLIMIT_CPU]); assert_se(rl[RLIMIT_CPU]->rlim_cur == 57); assert_se(rl[RLIMIT_CPU]->rlim_cur == rl[RLIMIT_CPU]->rlim_max); - assert_se(config_parse_sec_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "40s:1m", rl, NULL) >= 0); + assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "40s:1m", rl, NULL) >= 0); assert_se(rl[RLIMIT_CPU]); assert_se(rl[RLIMIT_CPU]->rlim_cur == 40); assert_se(rl[RLIMIT_CPU]->rlim_max == 60); - assert_se(config_parse_sec_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "infinity", rl, NULL) >= 0); + assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "infinity", rl, NULL) >= 0); assert_se(rl[RLIMIT_CPU]); assert_se(rl[RLIMIT_CPU]->rlim_cur == RLIM_INFINITY); assert_se(rl[RLIMIT_CPU]->rlim_cur == rl[RLIMIT_CPU]->rlim_max); - assert_se(config_parse_sec_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "1234ms", rl, NULL) >= 0); + assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "1234ms", rl, NULL) >= 0); assert_se(rl[RLIMIT_CPU]); assert_se(rl[RLIMIT_CPU]->rlim_cur == 2); assert_se(rl[RLIMIT_CPU]->rlim_cur == rl[RLIMIT_CPU]->rlim_max); rl[RLIMIT_CPU] = mfree(rl[RLIMIT_CPU]); - assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "58", rl, NULL) >= 0); + assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "58", rl, NULL) >= 0); assert_se(rl[RLIMIT_RTTIME]); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 58); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); - assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "58:60", rl, NULL) >= 0); + assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "58:60", rl, NULL) >= 0); assert_se(rl[RLIMIT_RTTIME]); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 58); assert_se(rl[RLIMIT_RTTIME]->rlim_max == 60); - assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "59s", rl, NULL) >= 0); + assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "59s", rl, NULL) >= 0); assert_se(rl[RLIMIT_RTTIME]); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 59 * USEC_PER_SEC); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); - assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "59s:123s", rl, NULL) >= 0); + assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "59s:123s", rl, NULL) >= 0); assert_se(rl[RLIMIT_RTTIME]); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 59 * USEC_PER_SEC); assert_se(rl[RLIMIT_RTTIME]->rlim_max == 123 * USEC_PER_SEC); - assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "infinity", rl, NULL) >= 0); + assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "infinity", rl, NULL) >= 0); assert_se(rl[RLIMIT_RTTIME]); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == RLIM_INFINITY); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); - assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "infinity:infinity", rl, NULL) >= 0); + assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "infinity:infinity", rl, NULL) >= 0); assert_se(rl[RLIMIT_RTTIME]); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == RLIM_INFINITY); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); - assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "2345ms", rl, NULL) >= 0); + assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "2345ms", rl, NULL) >= 0); assert_se(rl[RLIMIT_RTTIME]); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 2345 * USEC_PER_MSEC); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); @@ -829,7 +849,7 @@ int main(int argc, char *argv[]) { r = test_unit_file_get_set(); test_config_parse_exec(); - test_config_parse_bounding_set(); + test_config_parse_capability_set(); test_config_parse_rlimit(); test_config_parse_pass_environ(); test_load_env_file_1(); diff --git a/src/test/test-unit-name.c b/src/test/test-unit-name.c index 842ca40102..5287ee5e6f 100644 --- a/src/test/test-unit-name.c +++ b/src/test/test-unit-name.c @@ -27,6 +27,7 @@ #include <string.h> #include "alloc-util.h" +#include "glob-util.h" #include "hostname-util.h" #include "macro.h" #include "manager.h" @@ -66,26 +67,26 @@ static void test_unit_name_is_valid(void) { assert_se(!unit_name_is_valid("@piep.service", UNIT_NAME_ANY)); } -static void test_u_n_r_i_one(const char *pattern, const char *repl, const char *expected, int ret) { +static void test_unit_name_replace_instance_one(const char *pattern, const char *repl, const char *expected, int ret) { _cleanup_free_ char *t = NULL; assert_se(unit_name_replace_instance(pattern, repl, &t) == ret); puts(strna(t)); assert_se(streq_ptr(t, expected)); } -static void test_u_n_r_i(void) { +static void test_unit_name_replace_instance(void) { puts("-------------------------------------------------"); - test_u_n_r_i_one("foo@.service", "waldo", "foo@waldo.service", 0); - test_u_n_r_i_one("foo@xyz.service", "waldo", "foo@waldo.service", 0); - test_u_n_r_i_one("xyz", "waldo", NULL, -EINVAL); - test_u_n_r_i_one("", "waldo", NULL, -EINVAL); - test_u_n_r_i_one("foo.service", "waldo", NULL, -EINVAL); - test_u_n_r_i_one(".service", "waldo", NULL, -EINVAL); - test_u_n_r_i_one("foo@", "waldo", NULL, -EINVAL); - test_u_n_r_i_one("@bar", "waldo", NULL, -EINVAL); + test_unit_name_replace_instance_one("foo@.service", "waldo", "foo@waldo.service", 0); + test_unit_name_replace_instance_one("foo@xyz.service", "waldo", "foo@waldo.service", 0); + test_unit_name_replace_instance_one("xyz", "waldo", NULL, -EINVAL); + test_unit_name_replace_instance_one("", "waldo", NULL, -EINVAL); + test_unit_name_replace_instance_one("foo.service", "waldo", NULL, -EINVAL); + test_unit_name_replace_instance_one(".service", "waldo", NULL, -EINVAL); + test_unit_name_replace_instance_one("foo@", "waldo", NULL, -EINVAL); + test_unit_name_replace_instance_one("@bar", "waldo", NULL, -EINVAL); } -static void test_u_n_f_p_one(const char *path, const char *suffix, const char *expected, int ret) { +static void test_unit_name_from_path_one(const char *path, const char *suffix, const char *expected, int ret) { _cleanup_free_ char *t = NULL; assert_se(unit_name_from_path(path, suffix, &t) == ret); @@ -100,19 +101,19 @@ static void test_u_n_f_p_one(const char *path, const char *suffix, const char *e } } -static void test_u_n_f_p(void) { +static void test_unit_name_from_path(void) { puts("-------------------------------------------------"); - test_u_n_f_p_one("/waldo", ".mount", "waldo.mount", 0); - test_u_n_f_p_one("/waldo/quuix", ".mount", "waldo-quuix.mount", 0); - test_u_n_f_p_one("/waldo/quuix/", ".mount", "waldo-quuix.mount", 0); - test_u_n_f_p_one("", ".mount", "-.mount", 0); - test_u_n_f_p_one("/", ".mount", "-.mount", 0); - test_u_n_f_p_one("///", ".mount", "-.mount", 0); - test_u_n_f_p_one("/foo/../bar", ".mount", NULL, -EINVAL); - test_u_n_f_p_one("/foo/./bar", ".mount", NULL, -EINVAL); + test_unit_name_from_path_one("/waldo", ".mount", "waldo.mount", 0); + test_unit_name_from_path_one("/waldo/quuix", ".mount", "waldo-quuix.mount", 0); + test_unit_name_from_path_one("/waldo/quuix/", ".mount", "waldo-quuix.mount", 0); + test_unit_name_from_path_one("", ".mount", "-.mount", 0); + test_unit_name_from_path_one("/", ".mount", "-.mount", 0); + test_unit_name_from_path_one("///", ".mount", "-.mount", 0); + test_unit_name_from_path_one("/foo/../bar", ".mount", NULL, -EINVAL); + test_unit_name_from_path_one("/foo/./bar", ".mount", NULL, -EINVAL); } -static void test_u_n_f_p_i_one(const char *pattern, const char *path, const char *suffix, const char *expected, int ret) { +static void test_unit_name_from_path_instance_one(const char *pattern, const char *path, const char *suffix, const char *expected, int ret) { _cleanup_free_ char *t = NULL; assert_se(unit_name_from_path_instance(pattern, path, suffix, &t) == ret); @@ -128,65 +129,71 @@ static void test_u_n_f_p_i_one(const char *pattern, const char *path, const char } } -static void test_u_n_f_p_i(void) { +static void test_unit_name_from_path_instance(void) { puts("-------------------------------------------------"); - test_u_n_f_p_i_one("waldo", "/waldo", ".mount", "waldo@waldo.mount", 0); - test_u_n_f_p_i_one("waldo", "/waldo////quuix////", ".mount", "waldo@waldo-quuix.mount", 0); - test_u_n_f_p_i_one("waldo", "/", ".mount", "waldo@-.mount", 0); - test_u_n_f_p_i_one("waldo", "", ".mount", "waldo@-.mount", 0); - test_u_n_f_p_i_one("waldo", "///", ".mount", "waldo@-.mount", 0); - test_u_n_f_p_i_one("waldo", "..", ".mount", NULL, -EINVAL); - test_u_n_f_p_i_one("waldo", "/foo", ".waldi", NULL, -EINVAL); - test_u_n_f_p_i_one("wa--ldo", "/--", ".mount", "wa--ldo@\\x2d\\x2d.mount", 0); + test_unit_name_from_path_instance_one("waldo", "/waldo", ".mount", "waldo@waldo.mount", 0); + test_unit_name_from_path_instance_one("waldo", "/waldo////quuix////", ".mount", "waldo@waldo-quuix.mount", 0); + test_unit_name_from_path_instance_one("waldo", "/", ".mount", "waldo@-.mount", 0); + test_unit_name_from_path_instance_one("waldo", "", ".mount", "waldo@-.mount", 0); + test_unit_name_from_path_instance_one("waldo", "///", ".mount", "waldo@-.mount", 0); + test_unit_name_from_path_instance_one("waldo", "..", ".mount", NULL, -EINVAL); + test_unit_name_from_path_instance_one("waldo", "/foo", ".waldi", NULL, -EINVAL); + test_unit_name_from_path_instance_one("wa--ldo", "/--", ".mount", "wa--ldo@\\x2d\\x2d.mount", 0); } -static void test_u_n_t_p_one(const char *unit, const char *path, int ret) { +static void test_unit_name_to_path_one(const char *unit, const char *path, int ret) { _cleanup_free_ char *p = NULL; assert_se(unit_name_to_path(unit, &p) == ret); assert_se(streq_ptr(path, p)); } -static void test_u_n_t_p(void) { - test_u_n_t_p_one("home.mount", "/home", 0); - test_u_n_t_p_one("home-lennart.mount", "/home/lennart", 0); - test_u_n_t_p_one("home-lennart-.mount", NULL, -EINVAL); - test_u_n_t_p_one("-home-lennart.mount", NULL, -EINVAL); - test_u_n_t_p_one("-home--lennart.mount", NULL, -EINVAL); - test_u_n_t_p_one("home-..-lennart.mount", NULL, -EINVAL); - test_u_n_t_p_one("", NULL, -EINVAL); - test_u_n_t_p_one("home/foo", NULL, -EINVAL); +static void test_unit_name_to_path(void) { + test_unit_name_to_path_one("home.mount", "/home", 0); + test_unit_name_to_path_one("home-lennart.mount", "/home/lennart", 0); + test_unit_name_to_path_one("home-lennart-.mount", NULL, -EINVAL); + test_unit_name_to_path_one("-home-lennart.mount", NULL, -EINVAL); + test_unit_name_to_path_one("-home--lennart.mount", NULL, -EINVAL); + test_unit_name_to_path_one("home-..-lennart.mount", NULL, -EINVAL); + test_unit_name_to_path_one("", NULL, -EINVAL); + test_unit_name_to_path_one("home/foo", NULL, -EINVAL); } -static void test_u_n_m_one(const char *pattern, const char *expect, int ret) { +static void test_unit_name_mangle_one(UnitNameMangle allow_globs, const char *pattern, const char *expect, int ret) { _cleanup_free_ char *t = NULL; - assert_se(unit_name_mangle(pattern, UNIT_NAME_NOGLOB, &t) == ret); + assert_se(unit_name_mangle(pattern, allow_globs, &t) == ret); puts(strna(t)); assert_se(streq_ptr(t, expect)); if (t) { _cleanup_free_ char *k = NULL; - assert_se(unit_name_is_valid(t, UNIT_NAME_ANY)); + assert_se(unit_name_is_valid(t, UNIT_NAME_ANY) || + (allow_globs == UNIT_NAME_GLOB && string_is_glob(t))); - assert_se(unit_name_mangle(t, UNIT_NAME_NOGLOB, &k) == 0); + assert_se(unit_name_mangle(t, allow_globs, &k) == 0); assert_se(streq_ptr(t, k)); } } -static void test_u_n_m(void) { +static void test_unit_name_mangle(void) { puts("-------------------------------------------------"); - test_u_n_m_one("foo.service", "foo.service", 0); - test_u_n_m_one("/home", "home.mount", 1); - test_u_n_m_one("/dev/sda", "dev-sda.device", 1); - test_u_n_m_one("üxknürz.service", "\\xc3\\xbcxkn\\xc3\\xbcrz.service", 1); - test_u_n_m_one("foobar-meh...waldi.service", "foobar-meh...waldi.service", 0); - test_u_n_m_one("_____####----.....service", "_____\\x23\\x23\\x23\\x23----.....service", 1); - test_u_n_m_one("_____##@;;;,,,##----.....service", "_____\\x23\\x23@\\x3b\\x3b\\x3b\\x2c\\x2c\\x2c\\x23\\x23----.....service", 1); - test_u_n_m_one("xxx@@@@/////\\\\\\\\\\yyy.service", "xxx@@@@-----\\\\\\\\\\yyy.service", 1); - test_u_n_m_one("", NULL, -EINVAL); + test_unit_name_mangle_one(UNIT_NAME_NOGLOB, "foo.service", "foo.service", 0); + test_unit_name_mangle_one(UNIT_NAME_NOGLOB, "/home", "home.mount", 1); + test_unit_name_mangle_one(UNIT_NAME_NOGLOB, "/dev/sda", "dev-sda.device", 1); + test_unit_name_mangle_one(UNIT_NAME_NOGLOB, "üxknürz.service", "\\xc3\\xbcxkn\\xc3\\xbcrz.service", 1); + test_unit_name_mangle_one(UNIT_NAME_NOGLOB, "foobar-meh...waldi.service", "foobar-meh...waldi.service", 0); + test_unit_name_mangle_one(UNIT_NAME_NOGLOB, "_____####----.....service", "_____\\x23\\x23\\x23\\x23----.....service", 1); + test_unit_name_mangle_one(UNIT_NAME_NOGLOB, "_____##@;;;,,,##----.....service", "_____\\x23\\x23@\\x3b\\x3b\\x3b\\x2c\\x2c\\x2c\\x23\\x23----.....service", 1); + test_unit_name_mangle_one(UNIT_NAME_NOGLOB, "xxx@@@@/////\\\\\\\\\\yyy.service", "xxx@@@@-----\\\\\\\\\\yyy.service", 1); + test_unit_name_mangle_one(UNIT_NAME_NOGLOB, "", NULL, -EINVAL); + + test_unit_name_mangle_one(UNIT_NAME_GLOB, "foo.service", "foo.service", 0); + test_unit_name_mangle_one(UNIT_NAME_GLOB, "foo", "foo.service", 1); + test_unit_name_mangle_one(UNIT_NAME_GLOB, "foo*", "foo*", 0); + test_unit_name_mangle_one(UNIT_NAME_GLOB, "ü*", "\\xc3\\xbc*", 1); } static int test_unit_printf(void) { @@ -460,11 +467,11 @@ static void test_unit_name_path_unescape(void) { int main(int argc, char* argv[]) { int rc = 0; test_unit_name_is_valid(); - test_u_n_r_i(); - test_u_n_f_p(); - test_u_n_f_p_i(); - test_u_n_m(); - test_u_n_t_p(); + test_unit_name_replace_instance(); + test_unit_name_from_path(); + test_unit_name_from_path_instance(); + test_unit_name_mangle(); + test_unit_name_to_path(); TEST_REQ_RUNNING_SYSTEMD(rc = test_unit_printf()); test_unit_instance_is_valid(); test_unit_prefix_is_valid(); diff --git a/src/test/test-util.c b/src/test/test-util.c index f6ed55878c..e199497818 100644 --- a/src/test/test-util.c +++ b/src/test/test-util.c @@ -545,38 +545,31 @@ static void test_unbase32hexmem(void) { static void test_base64mem(void) { char *b64; - b64 = base64mem("", strlen("")); - assert_se(b64); + assert_se(base64mem("", strlen(""), &b64) == 0); assert_se(streq(b64, "")); free(b64); - b64 = base64mem("f", strlen("f")); - assert_se(b64); + assert_se(base64mem("f", strlen("f"), &b64) == 4); assert_se(streq(b64, "Zg==")); free(b64); - b64 = base64mem("fo", strlen("fo")); - assert_se(b64); + assert_se(base64mem("fo", strlen("fo"), &b64) == 4); assert_se(streq(b64, "Zm8=")); free(b64); - b64 = base64mem("foo", strlen("foo")); - assert_se(b64); + assert_se(base64mem("foo", strlen("foo"), &b64) == 4); assert_se(streq(b64, "Zm9v")); free(b64); - b64 = base64mem("foob", strlen("foob")); - assert_se(b64); + assert_se(base64mem("foob", strlen("foob"), &b64) == 8); assert_se(streq(b64, "Zm9vYg==")); free(b64); - b64 = base64mem("fooba", strlen("fooba")); - assert_se(b64); + assert_se(base64mem("fooba", strlen("fooba"), &b64) == 8); assert_se(streq(b64, "Zm9vYmE=")); free(b64); - b64 = base64mem("foobar", strlen("foobar")); - assert_se(b64); + assert_se(base64mem("foobar", strlen("foobar"), &b64) == 8); assert_se(streq(b64, "Zm9vYmFy")); free(b64); } diff --git a/src/timesync/timesyncd-manager.c b/src/timesync/timesyncd-manager.c index 8dca538b3b..5627d17de1 100644 --- a/src/timesync/timesyncd-manager.c +++ b/src/timesync/timesyncd-manager.c @@ -372,7 +372,8 @@ static int manager_adjust_clock(Manager *m, double offset, int leap_sec) { if (r < 0) return -errno; - touch("/var/lib/systemd/clock"); + /* If touch fails, there isn't much we can do. Maybe it'll work next time. */ + (void) touch("/var/lib/systemd/clock"); m->drift_ppm = tmx.freq / 65536; diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index f9a759e223..59ef940a4d 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; @@ -1153,6 +1153,7 @@ static int create_item(Item *i) { _cleanup_free_ char *resolved = NULL; struct stat st; int r = 0; + int q = 0; CreationMode creation; assert(i); @@ -1279,27 +1280,23 @@ static int create_item(Item *i) { if (IN_SET(i->type, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA)) { r = btrfs_subvol_auto_qgroup(i->path, 0, i->type == CREATE_SUBVOLUME_NEW_QUOTA); - if (r == -ENOTTY) { + if (r == -ENOTTY) log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" because of unsupported file system or because directory is not a subvolume: %m", i->path); - return 0; - } - if (r == -EROFS) { + else if (r == -EROFS) log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" because of read-only file system: %m", i->path); - return 0; - } - if (r == -ENOPROTOOPT) { + else if (r == -ENOPROTOOPT) log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" because quota support is disabled: %m", i->path); - return 0; - } - if (r < 0) - return log_error_errno(r, "Failed to adjust quota for subvolume \"%s\": %m", i->path); - if (r > 0) + else if (r < 0) + q = log_error_errno(r, "Failed to adjust quota for subvolume \"%s\": %m", i->path); + else if (r > 0) log_debug("Adjusted quota for subvolume \"%s\".", i->path); - if (r == 0) + else if (r == 0) log_debug("Quota for subvolume \"%s\" already in place, no change made.", i->path); } r = path_set_perms(i, i->path); + if (q < 0) + return q; if (r < 0) return r; diff --git a/src/udev/collect/collect.c b/src/udev/collect/collect.c index b6c95cd452..349585b634 100644 --- a/src/udev/collect/collect.c +++ b/src/udev/collect/collect.c @@ -27,6 +27,7 @@ #include "alloc-util.h" #include "libudev-private.h" #include "macro.h" +#include "stdio-util.h" #include "string-util.h" #define BUFSIZE 16 @@ -91,7 +92,7 @@ static int prepare(char *dir, char *filename) if (r < 0 && errno != EEXIST) return -errno; - snprintf(buf, sizeof(buf), "%s/%s", dir, filename); + xsprintf(buf, "%s/%s", dir, filename); fd = open(buf,O_RDWR|O_CREAT|O_CLOEXEC, S_IRUSR|S_IWUSR); if (fd < 0) diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index 0b1ae706e7..018b4dc596 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -124,7 +124,7 @@ static int find_gpt_root(struct udev_device *dev, blkid_probe pr, bool test) { errno = 0; pl = blkid_probe_get_partitions(pr); if (!pl) - return errno ? -errno : -ENOMEM; + return errno > 0 ? -errno : -ENOMEM; nvals = blkid_partlist_numof_partitions(pl); for (i = 0; i < nvals; i++) { diff --git a/src/udev/udev-builtin-input_id.c b/src/udev/udev-builtin-input_id.c index 1d31829a08..691ef5656d 100644 --- a/src/udev/udev-builtin-input_id.c +++ b/src/udev/udev-builtin-input_id.c @@ -33,6 +33,7 @@ #include <linux/input.h> #include "fd-util.h" +#include "stdio-util.h" #include "string-util.h" #include "udev.h" #include "util.h" @@ -66,8 +67,8 @@ static void extract_info(struct udev_device *dev, const char *devpath, bool test if (xabsinfo.resolution <= 0 || yabsinfo.resolution <= 0) return; - snprintf(width, sizeof(width), "%d", abs_size_mm(&xabsinfo)); - snprintf(height, sizeof(height), "%d", abs_size_mm(&yabsinfo)); + xsprintf(width, "%d", abs_size_mm(&xabsinfo)); + xsprintf(height, "%d", abs_size_mm(&yabsinfo)); udev_builtin_add_property(dev, test, "ID_INPUT_WIDTH_MM", width); udev_builtin_add_property(dev, test, "ID_INPUT_HEIGHT_MM", height); @@ -93,7 +94,7 @@ static void get_cap_mask(struct udev_device *dev, if (!v) v = ""; - snprintf(text, sizeof(text), "%s", v); + xsprintf(text, "%s", v); log_debug("%s raw kernel attribute: %s", attr, text); memzero(bitmask, bitmask_size); @@ -115,7 +116,8 @@ static void get_cap_mask(struct udev_device *dev, if (test) { /* printf pattern with the right unsigned long number of hex chars */ - snprintf(text, sizeof(text), " bit %%4u: %%0%zulX\n", 2 * sizeof(unsigned long)); + xsprintf(text, " bit %%4u: %%0%zulX\n", + 2 * sizeof(unsigned long)); log_debug("%s decoded bit map:", attr); val = bitmask_size / sizeof (unsigned long); /* skip over leading zeros */ @@ -205,12 +207,12 @@ static bool test_pointers(struct udev_device *dev, /* This path is taken by VMware's USB mouse, which has * absolute axes, but no touch/pressure button. */ is_mouse = true; - else if (has_touch) + else if (has_touch || is_direct) is_touchscreen = true; else if (has_joystick_axes_or_buttons) is_joystick = true; } - if (has_mt_coordinates && is_direct) + if (has_mt_coordinates && (is_direct || has_touch)) is_touchscreen = true; if (has_rel_coordinates && has_mouse_button) diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c index e549fdbee9..4f8a759d04 100644 --- a/src/udev/udev-builtin-net_id.c +++ b/src/udev/udev-builtin-net_id.c @@ -36,7 +36,7 @@ * * Type of names: * b<number> -- BCMA bus core number - * ccw<name> -- CCW bus group name + * c<bus_id> -- CCW bus group name, without leading zeros [s390] * o<index>[d<dev_port>] -- on-board device index number * s<slot>[f<function>][d<dev_port>] -- hotplug slot index number * x<MAC> -- MAC address @@ -102,9 +102,12 @@ #include "fd-util.h" #include "fileio.h" +#include "stdio-util.h" #include "string-util.h" #include "udev.h" +#define ONBOARD_INDEX_MAX (16*1024-1) + enum netname_type{ NET_UNDEF, NET_PCI, @@ -151,6 +154,13 @@ static int dev_pci_onboard(struct udev_device *dev, struct netnames *names) { if (idx <= 0) return -EINVAL; + /* Some BIOSes report rubbish indexes that are excessively high (2^24-1 is an index VMware likes to report for + * example). Let's define a cut-off where we don't consider the index reliable anymore. We pick some arbitrary + * cut-off, which is somewhere beyond the realistic number of physical network interface a system might + * have. Ideally the kernel would already filter his crap for us, but it doesn't currently. */ + if (idx > ONBOARD_INDEX_MAX) + return -ENOENT; + /* kernel provided port index for multiple ports on a single PCI function */ attr = udev_device_get_sysattr_value(dev, "dev_port"); if (attr) @@ -228,7 +238,7 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) { err = -ENOENT; goto out; } - snprintf(slots, sizeof(slots), "%s/slots", udev_device_get_syspath(pci)); + xsprintf(slots, "%s/slots", udev_device_get_syspath(pci)); dir = opendir(slots); if (!dir) { err = -errno; @@ -247,7 +257,7 @@ static int dev_pci_slot(struct udev_device *dev, struct netnames *names) { continue; if (i < 1) continue; - snprintf(str, sizeof(str), "%s/%s/address", slots, dent->d_name); + xsprintf(str, "%s/%s/address", slots, dent->d_name); if (read_one_line_file(str, &address) >= 0) { /* match slot address with device by stripping the function */ if (strneq(address, udev_device_get_sysname(names->pcidev), strlen(address))) @@ -380,7 +390,7 @@ static int names_bcma(struct udev_device *dev, struct netnames *names) { return -EINVAL; /* suppress the common core == 0 */ if (core > 0) - snprintf(names->bcma_core, sizeof(names->bcma_core), "b%u", core); + xsprintf(names->bcma_core, "b%u", core); names->type = NET_BCMA; return 0; @@ -420,8 +430,15 @@ static int names_ccw(struct udev_device *dev, struct netnames *names) { if (!bus_id_len || bus_id_len < 8 || bus_id_len > 9) return -EINVAL; + /* Strip leading zeros from the bus id for aesthetic purposes. This + * keeps the ccw names stable, yet much shorter in general case of + * bus_id 0.0.0600 -> 600. This is similar to e.g. how PCI domain is + * not prepended when it is zero. + */ + bus_id += strspn(bus_id, ".0"); + /* Store the CCW bus-ID for use as network device name */ - rc = snprintf(names->ccw_group, sizeof(names->ccw_group), "ccw%s", bus_id); + rc = snprintf(names->ccw_group, sizeof(names->ccw_group), "c%s", bus_id); if (rc >= 0 && rc < (int)sizeof(names->ccw_group)) names->type = NET_CCWGROUP; return 0; @@ -469,9 +486,9 @@ static int ieee_oui(struct udev_device *dev, struct netnames *names, bool test) /* skip commonly misused 00:00:00 (Xerox) prefix */ if (memcmp(names->mac, "\0\0\0", 3) == 0) return -EINVAL; - snprintf(str, sizeof(str), "OUI:%02X%02X%02X%02X%02X%02X", - names->mac[0], names->mac[1], names->mac[2], - names->mac[3], names->mac[4], names->mac[5]); + xsprintf(str, "OUI:%02X%02X%02X%02X%02X%02X", names->mac[0], + names->mac[1], names->mac[2], names->mac[3], names->mac[4], + names->mac[5]); udev_builtin_hwdb_lookup(dev, NULL, str, NULL, test); return 0; } @@ -523,7 +540,7 @@ static int builtin_net_id(struct udev_device *dev, int argc, char *argv[], bool if (err >= 0 && names.mac_valid) { char str[IFNAMSIZ]; - snprintf(str, sizeof(str), "%sx%02x%02x%02x%02x%02x%02x", prefix, + xsprintf(str, "%sx%02x%02x%02x%02x%02x%02x", prefix, names.mac[0], names.mac[1], names.mac[2], names.mac[3], names.mac[4], names.mac[5]); udev_builtin_add_property(dev, test, "ID_NET_NAME_MAC", str); diff --git a/src/udev/udev-node.c b/src/udev/udev-node.c index 39ae2cc1b1..fd7936c2dc 100644 --- a/src/udev/udev-node.c +++ b/src/udev/udev-node.c @@ -31,6 +31,7 @@ #include "fs-util.h" #include "selinux-util.h" #include "smack-util.h" +#include "stdio-util.h" #include "string-util.h" #include "udev.h" @@ -348,9 +349,10 @@ void udev_node_add(struct udev_device *dev, bool apply, return; /* always add /dev/{block,char}/$major:$minor */ - snprintf(filename, sizeof(filename), "/dev/%s/%u:%u", + xsprintf(filename, "/dev/%s/%u:%u", streq(udev_device_get_subsystem(dev), "block") ? "block" : "char", - major(udev_device_get_devnum(dev)), minor(udev_device_get_devnum(dev))); + major(udev_device_get_devnum(dev)), + minor(udev_device_get_devnum(dev))); node_symlink(dev, udev_device_get_devnode(dev), filename); /* create/update symlinks, add symlinks to name index */ @@ -367,8 +369,9 @@ void udev_node_remove(struct udev_device *dev) { link_update(dev, udev_list_entry_get_name(list_entry), false); /* remove /dev/{block,char}/$major:$minor */ - snprintf(filename, sizeof(filename), "/dev/%s/%u:%u", + xsprintf(filename, "/dev/%s/%u:%u", streq(udev_device_get_subsystem(dev), "block") ? "block" : "char", - major(udev_device_get_devnum(dev)), minor(udev_device_get_devnum(dev))); + major(udev_device_get_devnum(dev)), + minor(udev_device_get_devnum(dev))); unlink(filename); } diff --git a/src/udev/udev-watch.c b/src/udev/udev-watch.c index 60de703706..c0f4973f93 100644 --- a/src/udev/udev-watch.c +++ b/src/udev/udev-watch.c @@ -26,6 +26,7 @@ #include <sys/inotify.h> #include <unistd.h> +#include "stdio-util.h" #include "udev.h" static int inotify_fd = -1; @@ -105,7 +106,7 @@ void udev_watch_begin(struct udev *udev, struct udev_device *dev) { return; } - snprintf(filename, sizeof(filename), "/run/udev/watch/%d", wd); + xsprintf(filename, "/run/udev/watch/%d", wd); mkdir_parents(filename, 0755); unlink(filename); r = symlink(udev_device_get_id_filename(dev), filename); @@ -129,7 +130,7 @@ void udev_watch_end(struct udev *udev, struct udev_device *dev) { log_debug("removing watch on '%s'", udev_device_get_devnode(dev)); inotify_rm_watch(inotify_fd, wd); - snprintf(filename, sizeof(filename), "/run/udev/watch/%d", wd); + xsprintf(filename, "/run/udev/watch/%d", wd); unlink(filename); udev_device_set_watch_handle(dev, -1); @@ -143,7 +144,7 @@ struct udev_device *udev_watch_lookup(struct udev *udev, int wd) { if (inotify_fd < 0 || wd < 0) return NULL; - snprintf(filename, sizeof(filename), "/run/udev/watch/%d", wd); + xsprintf(filename, "/run/udev/watch/%d", wd); len = readlink(filename, device, sizeof(device)); if (len <= 0 || (size_t)len == sizeof(device)) return NULL; diff --git a/src/udev/udevd.c b/src/udev/udevd.c index 366e7fbb87..e658d6a079 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; + int fd_ctrl = -1, fd_uevent = -1; + int r; log_set_target(LOG_TARGET_AUTO); log_parse_environment(); diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c index a5f4529cfd..622fbe9a6d 100644 --- a/src/vconsole/vconsole-setup.c +++ b/src/vconsole/vconsole-setup.c @@ -39,6 +39,7 @@ #include "log.h" #include "process-util.h" #include "signal-util.h" +#include "stdio-util.h" #include "string-util.h" #include "terminal-util.h" #include "util.h" @@ -215,11 +216,11 @@ static void font_copy_to_all_vcs(int fd) { continue; /* skip non-allocated ttys */ - snprintf(vcname, sizeof(vcname), "/dev/vcs%i", i); + xsprintf(vcname, "/dev/vcs%i", i); if (access(vcname, F_OK) < 0) continue; - snprintf(vcname, sizeof(vcname), "/dev/tty%i", i); + xsprintf(vcname, "/dev/tty%i", i); vcfd = open_terminal(vcname, O_RDWR|O_CLOEXEC); if (vcfd < 0) continue; diff --git a/sysusers.d/basic.conf.in b/sysusers.d/basic.conf.in index 823d6cb200..b2dc5ebd4f 100644 --- a/sysusers.d/basic.conf.in +++ b/sysusers.d/basic.conf.in @@ -19,7 +19,6 @@ g wheel - - - # Access to certain kernel and userspace facilities g kmem - - - -g lock - - - g tty @TTY_GID@ - - g utmp - - - diff --git a/test/TEST-02-CRYPTSETUP/test.sh b/test/TEST-02-CRYPTSETUP/test.sh index dada99df59..242090c761 100755 --- a/test/TEST-02-CRYPTSETUP/test.sh +++ b/test/TEST-02-CRYPTSETUP/test.sh @@ -77,7 +77,6 @@ EOF /dev/mapper/varcrypt /var ext3 defaults 0 1 EOF ) || return 1 - setup_nspawn_root ddebug "umount $TESTDIR/root/var" umount $TESTDIR/root/var diff --git a/test/TEST-03-JOBS/test-jobs.sh b/test/TEST-03-JOBS/test-jobs.sh index 42d475fe2f..4252a9a75d 100755 --- a/test/TEST-03-JOBS/test-jobs.sh +++ b/test/TEST-03-JOBS/test-jobs.sh @@ -4,9 +4,12 @@ # installed job. systemctl start --no-block hello-after-sleep.target -# sleep is now running, hello/start is waiting. Verify that: + systemctl list-jobs > /root/list-jobs.txt -grep 'sleep\.service.*running' /root/list-jobs.txt || exit 1 +while ! grep 'sleep\.service.*running' /root/list-jobs.txt; do + systemctl list-jobs > /root/list-jobs.txt +done + grep 'hello\.service.*waiting' /root/list-jobs.txt || exit 1 # This is supposed to finish quickly, not wait for sleep to finish. diff --git a/test/TEST-04-JOURNAL/test-journal.sh b/test/TEST-04-JOURNAL/test-journal.sh index 1ee39df432..3a05619ad5 100755 --- a/test/TEST-04-JOURNAL/test-journal.sh +++ b/test/TEST-04-JOURNAL/test-journal.sh @@ -51,5 +51,13 @@ journalctl --sync journalctl -b -o cat -t "$ID" >/output cmp /expected /output +# Don't lose streams on restart +systemctl start forever-print-hola +sleep 3 +systemctl restart systemd-journald +sleep 3 +systemctl stop forever-print-hola +[[ ! -f "/i-lose-my-logs" ]] + touch /testok exit 0 diff --git a/test/TEST-04-JOURNAL/test.sh b/test/TEST-04-JOURNAL/test.sh index 6c5b5cf34e..1a14f76060 100755 --- a/test/TEST-04-JOURNAL/test.sh +++ b/test/TEST-04-JOURNAL/test.sh @@ -57,6 +57,15 @@ ExecStart=/test-journal.sh Type=oneshot EOF + cat >$initdir/etc/systemd/system/forever-print-hola.service <<EOF +[Unit] +Description=ForeverPrintHola service + +[Service] +Type=simple +ExecStart=/bin/sh -x -c 'while :; do printf "Hola\n" || touch /i-lose-my-logs; sleep 1; done' +EOF + cp test-journal.sh $initdir/ setup_testsuite diff --git a/test/TEST-06-SELINUX/Makefile b/test/TEST-06-SELINUX/Makefile new file mode 100644 index 0000000000..5e89a29eff --- /dev/null +++ b/test/TEST-06-SELINUX/Makefile @@ -0,0 +1,10 @@ +all: + @make -s --no-print-directory -C ../.. all + @basedir=../.. TEST_BASE_DIR=../ ./test.sh --all +setup: + @make --no-print-directory -C ../.. all + @basedir=../.. TEST_BASE_DIR=../ ./test.sh --setup +clean: + @basedir=../.. TEST_BASE_DIR=../ ./test.sh --clean +run: + @basedir=../.. TEST_BASE_DIR=../ ./test.sh --run diff --git a/test/TEST-06-SELINUX/systemd_test.if b/test/TEST-06-SELINUX/systemd_test.if new file mode 100644 index 0000000000..25c91adce9 --- /dev/null +++ b/test/TEST-06-SELINUX/systemd_test.if @@ -0,0 +1,8 @@ +template(`systemd_test_base_template', ` + gen_require(` + attribute systemd_test_domain_type; + ') + + type $1_t, systemd_test_domain_type; + domain_type($1_t) +') diff --git a/test/TEST-06-SELINUX/systemd_test.te b/test/TEST-06-SELINUX/systemd_test.te new file mode 100644 index 0000000000..ff01c09b5e --- /dev/null +++ b/test/TEST-06-SELINUX/systemd_test.te @@ -0,0 +1,50 @@ +policy_module(systemd_test, 0.0.1) + +# declarations +attribute systemd_test_domain_type; + +systemd_test_base_template(systemd_test) +systemd_test_base_template(systemd_test_status) +systemd_test_base_template(systemd_test_start) +systemd_test_base_template(systemd_test_stop) +systemd_test_base_template(systemd_test_reload) + +# systemd_test_domain_type + +require { + role system_r; + role unconfined_r; + type bin_t; + type initrc_t; + type systemd_systemctl_exec_t; + type unconfined_service_t; +} + +role system_r types systemd_test_domain_type; +role unconfined_r types systemd_test_domain_type; + +allow systemd_test_domain_type bin_t: file entrypoint; +allow systemd_test_domain_type systemd_systemctl_exec_t: file entrypoint; +allow initrc_t systemd_test_domain_type: process transition; +allow unconfined_service_t systemd_test_domain_type: process transition; +corecmd_exec_bin(systemd_test_domain_type) +init_signal_script(systemd_test_domain_type) +init_sigchld_script(systemd_test_domain_type) +systemd_exec_systemctl(systemd_test_domain_type) +userdom_use_user_ttys(systemd_test_domain_type) +userdom_use_user_ptys(systemd_test_domain_type) + +optional_policy(` + dbus_system_bus_client(systemd_test_domain_type) + init_dbus_chat(systemd_test_domain_type) +') + +# systemd_test_*_t +require { + type systemd_unit_file_t; +} + +allow systemd_test_status_t systemd_unit_file_t: service { status }; +allow systemd_test_start_t systemd_unit_file_t: service { start }; +allow systemd_test_stop_t systemd_unit_file_t: service { stop }; +allow systemd_test_reload_t systemd_unit_file_t: service { reload }; diff --git a/test/TEST-06-SELINUX/test-selinux-checks.sh b/test/TEST-06-SELINUX/test-selinux-checks.sh new file mode 100755 index 0000000000..08d2ddf4f0 --- /dev/null +++ b/test/TEST-06-SELINUX/test-selinux-checks.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -x +set -e +set -o pipefail + +echo 1 >/sys/fs/selinux/enforce +runcon -t systemd_test_start_t systemctl start hola +runcon -t systemd_test_reload_t systemctl reload hola +runcon -t systemd_test_stop_t systemctl stop hola + +touch /testok +exit 0 diff --git a/test/TEST-06-SELINUX/test.sh b/test/TEST-06-SELINUX/test.sh new file mode 100755 index 0000000000..4f5895be66 --- /dev/null +++ b/test/TEST-06-SELINUX/test.sh @@ -0,0 +1,135 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +TEST_DESCRIPTION="SELinux tests" + +# Requirements: +# Fedora 23 +# selinux-policy-targeted +# selinux-policy-devel + +. $TEST_BASE_DIR/test-functions +SETUP_SELINUX=yes +KERNEL_APPEND="$KERNEL_APPEND selinux=1" + +check_result_qemu() { + ret=1 + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + [[ -e $TESTDIR/root/testok ]] && ret=0 + [[ -f $TESTDIR/root/failed ]] && cp -a $TESTDIR/root/failed $TESTDIR + cp -a $TESTDIR/root/var/log/journal $TESTDIR + umount $TESTDIR/root + [[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed + ls -l $TESTDIR/journal/*/*.journal + test -s $TESTDIR/failed && ret=$(($ret+1)) + return $ret +} + +test_run() { + if run_qemu; then + check_result_qemu || return 1 + else + dwarn "can't run QEMU, skipping" + fi + return 0 +} + +test_setup() { + create_empty_image + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + + # Create what will eventually be our root filesystem onto an overlay + ( + LOG_LEVEL=5 + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + setup_basic_environment + + # setup the testsuite service + cat <<EOF >$initdir/etc/systemd/system/testsuite.service +[Unit] +Description=Testsuite service +After=multi-user.target + +[Service] +ExecStart=/test-selinux-checks.sh +Type=oneshot +EOF + + cat <<EOF >$initdir/etc/systemd/system/hola.service +[Service] +Type=oneshot +ExecStart=/bin/echo Start Hola +ExecReload=/bin/echo Reload Hola +ExecStop=/bin/echo Stop Hola +RemainAfterExit=yes +EOF + + setup_testsuite + + cat <<EOF >$initdir/etc/systemd/system/load-systemd-test-module.service +[Unit] +Description=Load systemd-test module +DefaultDependencies=no +Requires=local-fs.target +Conflicts=shutdown.target +After=local-fs.target +Before=sysinit.target shutdown.target autorelabel.service +ConditionSecurity=selinux +ConditionPathExists=|/.load-systemd-test-module + +[Service] +ExecStart=/bin/sh -x -c 'echo 0 >/sys/fs/selinux/enforce && cd /systemd-test-module && make -f /usr/share/selinux/devel/Makefile load && rm /.load-systemd-test-module' +Type=oneshot +TimeoutSec=0 +RemainAfterExit=yes +EOF + + touch $initdir/.load-systemd-test-module + mkdir -p $initdir/etc/systemd/system/basic.target.wants + ln -fs load-systemd-test-module.service $initdir/etc/systemd/system/basic.target.wants/load-systemd-test-module.service + + local _modules_dir=/var/lib/selinux + rm -rf $initdir/$_modules_dir + if ! cp -ar $_modules_dir $initdir/$_modules_dir; then + dfatal "Failed to copy $_modules_dir" + exit 1 + fi + + local _policy_headers_dir=/usr/share/selinux/devel + rm -rf $initdir/$_policy_headers_dir + inst_dir /usr/share/selinux + if ! cp -ar $_policy_headers_dir $initdir/$_policy_headers_dir; then + dfatal "Failed to copy $_policy_headers_dir" + exit 1 + fi + + mkdir $initdir/systemd-test-module + cp systemd_test.te $initdir/systemd-test-module + cp systemd_test.if $initdir/systemd-test-module + cp test-selinux-checks.sh $initdir + dracut_install -o sesearch + dracut_install runcon + dracut_install checkmodule semodule semodule_package m4 make /usr/libexec/selinux/hll/pp load_policy sefcontext_compile + ) || return 1 + + # mask some services that we do not want to run in these tests + ln -s /dev/null $initdir/etc/systemd/system/systemd-hwdb-update.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-journal-catalog-update.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-networkd.service + ln -s /dev/null $initdir/etc/systemd/system/systemd-networkd.socket + ln -s /dev/null $initdir/etc/systemd/system/systemd-resolved.service + + ddebug "umount $TESTDIR/root" + umount $TESTDIR/root +} + +test_cleanup() { + umount $TESTDIR/root 2>/dev/null + [[ $LOOPDEV ]] && losetup -d $LOOPDEV + return 0 +} + +do_test "$@" diff --git a/test/sysv-generator-test.py b/test/sysv-generator-test.py index 721e53a4ee..aca5f1eec6 100644 --- a/test/sysv-generator-test.py +++ b/test/sysv-generator-test.py @@ -23,6 +23,7 @@ import subprocess import tempfile import shutil from glob import glob +import collections try: from configparser import RawConfigParser @@ -32,6 +33,12 @@ except ImportError: sysv_generator = os.path.join(os.environ.get('builddir', '.'), 'systemd-sysv-generator') +class MultiDict(collections.OrderedDict): + def __setitem__(self, key, value): + if isinstance(value, list) and key in self: + self[key].extend(value) + else: + super(MultiDict, self).__setitem__(key, value) class SysvGeneratorTest(unittest.TestCase): def setUp(self): @@ -77,7 +84,14 @@ class SysvGeneratorTest(unittest.TestCase): for service in glob(self.out_dir + '/*.service'): if os.path.islink(service): continue - cp = RawConfigParser() + try: + # for python3 we need here strict=False to parse multiple + # lines with the same key + cp = RawConfigParser(dict_type=MultiDict, strict=False) + except TypeError: + # RawConfigParser in python2 does not have the strict option + # but it allows multiple lines with the same key by default + cp = RawConfigParser(dict_type=MultiDict) cp.optionxform = lambda o: o # don't lower-case option names with open(service) as f: cp.readfp(f) @@ -224,7 +238,7 @@ class SysvGeneratorTest(unittest.TestCase): s = self.run_generator()[1]['foo.service'] self.assertEqual(set(s.options('Unit')), set(['Documentation', 'SourcePath', 'Description', 'After'])) - self.assertEqual(s.get('Unit', 'After'), 'nss-lookup.target rpcbind.target') + self.assertEqual(s.get('Unit', 'After').split(), ['nss-lookup.target', 'rpcbind.target']) def test_lsb_deps(self): '''LSB header dependencies to other services''' diff --git a/test/test-execute/exec-capabilityambientset-merge.service b/test/test-execute/exec-capabilityambientset-merge.service new file mode 100644 index 0000000000..64964380e2 --- /dev/null +++ b/test/test-execute/exec-capabilityambientset-merge.service @@ -0,0 +1,9 @@ +[Unit] +Description=Test for AmbientCapabilities + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000003000"' +Type=oneshot +User=nobody +AmbientCapabilities=CAP_NET_ADMIN +AmbientCapabilities=CAP_NET_RAW diff --git a/test/test-execute/exec-capabilityambientset.service b/test/test-execute/exec-capabilityambientset.service new file mode 100644 index 0000000000..d63f884ef8 --- /dev/null +++ b/test/test-execute/exec-capabilityambientset.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for AmbientCapabilities + +[Service] +ExecStart=/bin/sh -x -c 'c=$$(grep "CapAmb:" /proc/self/status); test "$$c" = "CapAmb: 0000000000003000"' +Type=oneshot +User=nobody +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW diff --git a/test/test-functions b/test/test-functions index 9288200717..59167b009b 100644 --- a/test/test-functions +++ b/test/test-functions @@ -92,7 +92,7 @@ $KERNEL_APPEND \ run_nspawn() { set -x - ../../systemd-nspawn --register=no --boot --directory=$TESTDIR/nspawn-root $ROOTLIBDIR/systemd $KERNEL_APPEND + ../../systemd-nspawn --register=no --directory=$TESTDIR/nspawn-root $ROOTLIBDIR/systemd $KERNEL_APPEND } setup_basic_environment() { @@ -111,21 +111,105 @@ setup_basic_environment() { install_keymaps install_terminfo install_execs + install_fsck install_plymouth install_debug_tools install_ld_so_conf + setup_selinux strip_binaries install_depmod_files generate_module_dependencies - # softlink mtab - ln -fs /proc/self/mounts $initdir/etc/mtab +} + +setup_selinux() { + # don't forget KERNEL_APPEND='... selinux=1 ...' + if [[ "$SETUP_SELINUX" != "yes" ]]; then + ddebug "Don't setup SELinux" + return 0 + fi + ddebug "Setup SELinux" + local _conf_dir=/etc/selinux + local _fixfiles_tools="bash uname cat sort uniq awk grep egrep head expr find rm secon setfiles" + + rm -rf $initdir/$_conf_dir + if ! cp -ar $_conf_dir $initdir/$_conf_dir; then + dfatal "Failed to copy $_conf_dir" + exit 1 + fi + + cat <<EOF >$initdir/etc/systemd/system/autorelabel.service +[Unit] +Description=Relabel all filesystems +DefaultDependencies=no +Requires=local-fs.target +Conflicts=shutdown.target +After=local-fs.target +Before=sysinit.target shutdown.target +ConditionSecurity=selinux +ConditionPathExists=|/.autorelabel + +[Service] +ExecStart=/bin/sh -x -c 'echo 0 >/sys/fs/selinux/enforce && fixfiles -f -F relabel && rm /.autorelabel && systemctl --force reboot' +Type=oneshot +TimeoutSec=0 +RemainAfterExit=yes +EOF + + touch $initdir/.autorelabel + mkdir -p $initdir/etc/systemd/system/basic.target.wants + ln -fs autorelabel.service $initdir/etc/systemd/system/basic.target.wants/autorelabel.service + + dracut_install $_fixfiles_tools + dracut_install fixfiles + dracut_install sestatus +} + +install_valgrind() { + if ! type -p valgrind; then + dfatal "Failed to install valgrind" + exit 1 + fi + + local _valgrind_bins=$(strace -e execve valgrind /bin/true 2>&1 >/dev/null | perl -lne 'print $1 if /^execve\("([^"]+)"/') + dracut_install $_valgrind_bins + + local _valgrind_libs=$(LD_DEBUG=files valgrind /bin/true 2>&1 >/dev/null | perl -lne 'print $1 if m{calling init: (/.*vgpreload_.*)}') + dracut_install $_valgrind_libs + + local _valgrind_dbg_and_supp=$( + strace -e open valgrind /bin/true 2>&1 >/dev/null | + perl -lne 'if (my ($fname) = /^open\("([^"]+).*= (?!-)\d+/) { print $fname if $fname =~ /debug|\.supp$/ }' + ) + dracut_install $_valgrind_dbg_and_supp +} + +create_valgrind_wrapper() { + local _valgrind_wrapper=$initdir/$ROOTLIBDIR/systemd-under-valgrind + ddebug "Create $_valgrind_wrapper" + cat >$_valgrind_wrapper <<EOF +#!/bin/bash + +exec valgrind --leak-check=full --log-file=/valgrind.out $ROOTLIBDIR/systemd "\$@" +EOF + chmod 0755 $_valgrind_wrapper +} + +install_fsck() { + dracut_install /sbin/fsck* + dracut_install -o /bin/fsck* } install_dmevent() { instmods dm_crypt =crypto type -P dmeventd >/dev/null && dracut_install dmeventd inst_libdir_file "libdevmapper-event.so*" - inst_rules 10-dm.rules 13-dm-disk.rules 95-dm-notify.rules + if [[ "$LOOKS_LIKE_DEBIAN" ]]; then + # dmsetup installs 55-dm and 60-persistent-storage-dm on Debian/Ubuntu + # see https://anonscm.debian.org/cgit/pkg-lvm/lvm2.git/tree/debian/patches/0007-udev.patch + inst_rules 55-dm.rules 60-persistent-storage-dm.rules + else + inst_rules 10-dm.rules 13-dm-disk.rules 95-dm-notify.rules + fi } install_systemd() { @@ -172,6 +256,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 } @@ -291,6 +379,10 @@ install_pam() { inst $file done + # pam_unix depends on unix_chkpwd. + # see http://www.linux-pam.org/Linux-PAM-html/sag-pam_unix.html + dracut_install -o unix_chkpwd + [[ "$LOOKS_LIKE_DEBIAN" ]] && cp /etc/pam.d/systemd-user $initdir/etc/pam.d/ } diff --git a/tmpfiles.d/legacy.conf b/tmpfiles.d/legacy.conf index 3cb0c63815..62e2ae0986 100644 --- a/tmpfiles.d/legacy.conf +++ b/tmpfiles.d/legacy.conf @@ -18,14 +18,6 @@ L /var/lock - - - - ../run/lock d /run/lock/subsys 0755 root root - -# /run/lock/lockdev is used to serialize access to tty devices via -# LCK..xxx style lock files, For more information see: -# http://lists.freedesktop.org/archives/systemd-devel/2011-March/001823.html -# On modern systems a BSD file lock is a better choice if -# serialization is needed on those devices. - -d /run/lock/lockdev 0775 root lock - - # /forcefsck, /fastboot and /forcequotacheck are deprecated in favor of the # kernel command line options 'fsck.mode=force', 'fsck.mode=skip' and # 'quotacheck.mode=force' diff --git a/tmpfiles.d/systemd-remote.conf b/tmpfiles.d/systemd-remote.conf index 1b8973a889..e19230f648 100644 --- a/tmpfiles.d/systemd-remote.conf +++ b/tmpfiles.d/systemd-remote.conf @@ -7,5 +7,7 @@ # See tmpfiles.d(5) for details -z /var/log/journal/remote 2755 root systemd-journal-remote - - -z /run/log/journal/remote 2755 root systemd-journal-remote - - +d /var/lib/systemd/journal-upload 0755 systemd-journal-upload systemd-journal-upload - - + +z /var/log/journal/remote 2755 systemd-journal-remote systemd-journal-remote - - +z /run/log/journal/remote 2755 systemd-journal-remote systemd-journal-remote - - diff --git a/units/console-shell.service.m4.in b/units/console-shell.service.m4.in index 5c80722829..a345ec25d4 100644 --- a/units/console-shell.service.m4.in +++ b/units/console-shell.service.m4.in @@ -16,7 +16,7 @@ Before=getty.target [Service] Environment=HOME=/root -WorkingDirectory=/root +WorkingDirectory=-/root ExecStart=-@SULOGIN@ ExecStopPost=-@SYSTEMCTL@ poweroff Type=idle diff --git a/units/emergency.service.in b/units/emergency.service.in index 8dc3cbdede..fb390eacfe 100644 --- a/units/emergency.service.in +++ b/units/emergency.service.in @@ -15,7 +15,7 @@ Before=shutdown.target [Service] Environment=HOME=/root -WorkingDirectory=/root +WorkingDirectory=-/root ExecStartPre=-/bin/plymouth --wait quit ExecStartPre=-/bin/echo -e 'Welcome to emergency mode! After logging in, type "journalctl -xb" to view\\nsystem logs, "systemctl reboot" to reboot, "systemctl default" or ^D to\\ntry again to boot into default mode.' ExecStart=-/bin/sh -c "@SULOGIN@; @SYSTEMCTL@ --job-mode=fail --no-block default" diff --git a/units/kmod-static-nodes.service.in b/units/kmod-static-nodes.service.in index 0934a8751f..a9c8df1184 100644 --- a/units/kmod-static-nodes.service.in +++ b/units/kmod-static-nodes.service.in @@ -10,7 +10,7 @@ Description=Create list of required static device nodes for the current kernel DefaultDependencies=no Before=sysinit.target systemd-tmpfiles-setup-dev.service ConditionCapability=CAP_SYS_MODULE -ConditionPathExists=/lib/modules/%v/modules.devname +ConditionFileNotEmpty=/lib/modules/%v/modules.devname [Service] Type=oneshot diff --git a/units/rescue.service.in b/units/rescue.service.in index 432e4f3c84..6c202174d3 100644 --- a/units/rescue.service.in +++ b/units/rescue.service.in @@ -15,7 +15,7 @@ Before=shutdown.target [Service] Environment=HOME=/root -WorkingDirectory=/root +WorkingDirectory=-/root ExecStartPre=-/bin/plymouth quit ExecStartPre=-/bin/echo -e 'Welcome to emergency mode! After logging in, type "journalctl -xb" to view\\nsystem logs, "systemctl reboot" to reboot, "systemctl default" or ^D to\\nboot into default mode.' ExecStart=-/bin/sh -c "@SULOGIN@; @SYSTEMCTL@ --job-mode=fail --no-block default" diff --git a/units/systemd-journal-gatewayd.service.in b/units/systemd-journal-gatewayd.service.in index 987220e554..f4f845841d 100644 --- a/units/systemd-journal-gatewayd.service.in +++ b/units/systemd-journal-gatewayd.service.in @@ -7,6 +7,7 @@ [Unit] Description=Journal Gateway Service +Documentation=man:systemd-journal-gatewayd(8) Requires=systemd-journal-gatewayd.socket [Service] diff --git a/units/systemd-journal-gatewayd.socket b/units/systemd-journal-gatewayd.socket index fd11058ab4..79d9b04210 100644 --- a/units/systemd-journal-gatewayd.socket +++ b/units/systemd-journal-gatewayd.socket @@ -7,6 +7,7 @@ [Unit] Description=Journal Gateway Service Socket +Documentation=man:systemd-journal-gatewayd(8) [Socket] ListenStream=19531 diff --git a/units/systemd-journal-remote.service.in b/units/systemd-journal-remote.service.in index 2928a23021..fdf3da4b64 100644 --- a/units/systemd-journal-remote.service.in +++ b/units/systemd-journal-remote.service.in @@ -7,6 +7,7 @@ [Unit] Description=Journal Remote Sink Service +Documentation=man:systemd-journal-remote(8) man:journal-remote.conf(5) Requires=systemd-journal-remote.socket [Service] diff --git a/units/systemd-journal-upload.service.in b/units/systemd-journal-upload.service.in index a757673a62..1f488ff425 100644 --- a/units/systemd-journal-upload.service.in +++ b/units/systemd-journal-upload.service.in @@ -7,12 +7,14 @@ [Unit] Description=Journal Remote Upload Service +Documentation=man:systemd-journal-upload(8) After=network.target [Service] ExecStart=@rootlibexecdir@/systemd-journal-upload \ --save-state User=systemd-journal-upload +SupplementaryGroups=systemd-journal PrivateTmp=yes PrivateDevices=yes WatchdogSec=3min |