From 0ecb658eee5b317d27ecd368b672ebddc0c5adc2 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Thu, 26 May 2016 00:27:56 -0400 Subject: move stuff --- src/analyze/.gitignore | 1 - src/analyze/Makefile | 35 - src/analyze/analyze-verify.c | 306 -- src/analyze/analyze-verify.h | 26 - src/analyze/analyze.c | 1492 ---------- src/bootchart/Makefile | 47 - src/bootchart/bootchart.c | 531 ---- src/bootchart/bootchart.conf | 26 - src/bootchart/bootchart.h | 119 - src/bootchart/store.c | 555 ---- src/bootchart/store.h | 36 - src/bootchart/svg.c | 1375 --------- src/bootchart/svg.h | 35 - src/libfirewall/firewall-util.c | 349 +++ src/libfirewall/firewall-util.h | 83 + src/libshared/firewall-util.c | 349 --- src/libshared/firewall-util.h | 83 - src/machine/.gitignore | 1 - src/machine/Makefile | 124 - src/machine/image-dbus.c | 361 --- src/machine/image-dbus.h | 35 - src/machine/machine-dbus.c | 1435 ---------- src/machine/machine-dbus.h | 43 - src/machine/machine.c | 659 ----- src/machine/machine.h | 124 - src/machine/machinectl.c | 2678 ----------------- src/machine/machined-dbus.c | 1551 ---------- src/machine/machined.c | 407 --- src/machine/machined.h | 78 - src/machine/org.freedesktop.machine1.conf | 194 -- src/machine/org.freedesktop.machine1.policy.in | 102 - src/machine/org.freedesktop.machine1.service | 12 - src/machine/src/.gitignore | 1 + src/machine/src/Makefile | 124 + src/machine/src/image-dbus.c | 361 +++ src/machine/src/image-dbus.h | 35 + src/machine/src/machine-dbus.c | 1435 ++++++++++ src/machine/src/machine-dbus.h | 43 + src/machine/src/machine.c | 659 +++++ src/machine/src/machine.h | 124 + src/machine/src/machinectl.c | 2678 +++++++++++++++++ src/machine/src/machined-dbus.c | 1551 ++++++++++ src/machine/src/machined.c | 407 +++ src/machine/src/machined.h | 78 + src/machine/src/org.freedesktop.machine1.conf | 194 ++ src/machine/src/org.freedesktop.machine1.policy.in | 102 + src/machine/src/org.freedesktop.machine1.service | 12 + src/machine/src/test-machine-tables.c | 29 + src/machine/test-machine-tables.c | 29 - src/resolve/.gitignore | 6 - src/resolve/Makefile | 204 -- src/resolve/RFCs | 59 - src/resolve/dns-type.c | 306 -- src/resolve/dns-type.h | 154 - src/resolve/org.freedesktop.resolve1.conf | 27 - src/resolve/org.freedesktop.resolve1.service | 12 - src/resolve/resolve-tool.c | 1272 -------- src/resolve/resolved-bus.c | 1565 ---------- src/resolve/resolved-bus.h | 25 - src/resolve/resolved-conf.c | 236 -- src/resolve/resolved-conf.h | 36 - src/resolve/resolved-def.h | 38 - src/resolve/resolved-dns-answer.c | 858 ------ src/resolve/resolved-dns-answer.h | 143 - src/resolve/resolved-dns-cache.c | 1088 ------- src/resolve/resolved-dns-cache.h | 52 - src/resolve/resolved-dns-dnssec.c | 2211 -------------- src/resolve/resolved-dns-dnssec.h | 102 - src/resolve/resolved-dns-packet.c | 2295 --------------- src/resolve/resolved-dns-packet.h | 272 -- src/resolve/resolved-dns-query.c | 1114 ------- src/resolve/resolved-dns-query.h | 133 - src/resolve/resolved-dns-question.c | 468 --- src/resolve/resolved-dns-question.h | 69 - src/resolve/resolved-dns-rr.c | 1521 ---------- src/resolve/resolved-dns-rr.h | 333 --- src/resolve/resolved-dns-scope.c | 1028 ------- src/resolve/resolved-dns-scope.h | 109 - src/resolve/resolved-dns-search-domain.c | 227 -- src/resolve/resolved-dns-search-domain.h | 74 - src/resolve/resolved-dns-server.c | 740 ----- src/resolve/resolved-dns-server.h | 143 - src/resolve/resolved-dns-stream.c | 403 --- src/resolve/resolved-dns-stream.h | 61 - src/resolve/resolved-dns-synthesize.c | 413 --- src/resolve/resolved-dns-synthesize.h | 30 - src/resolve/resolved-dns-transaction.c | 3026 -------------------- src/resolve/resolved-dns-transaction.h | 177 -- src/resolve/resolved-dns-trust-anchor.c | 743 ----- src/resolve/resolved-dns-trust-anchor.h | 43 - src/resolve/resolved-dns-zone.c | 656 ----- src/resolve/resolved-dns-zone.h | 81 - src/resolve/resolved-etc-hosts.c | 448 --- src/resolve/resolved-etc-hosts.h | 28 - src/resolve/resolved-gperf.gperf | 21 - src/resolve/resolved-link-bus.c | 550 ---- src/resolve/resolved-link-bus.h | 38 - src/resolve/resolved-link.c | 840 ------ src/resolve/resolved-link.h | 111 - src/resolve/resolved-llmnr.c | 476 --- src/resolve/resolved-llmnr.h | 32 - src/resolve/resolved-manager.c | 1239 -------- src/resolve/resolved-manager.h | 171 -- src/resolve/resolved-mdns.c | 287 -- src/resolve/resolved-mdns.h | 30 - src/resolve/resolved-resolv-conf.c | 267 -- src/resolve/resolved-resolv-conf.h | 27 - src/resolve/resolved.c | 112 - src/resolve/resolved.conf.in | 19 - src/resolve/src/.gitignore | 6 + src/resolve/src/Makefile | 204 ++ src/resolve/src/RFCs | 59 + src/resolve/src/dns-type.c | 306 ++ src/resolve/src/dns-type.h | 154 + src/resolve/src/org.freedesktop.resolve1.conf | 27 + src/resolve/src/org.freedesktop.resolve1.service | 12 + src/resolve/src/resolve-tool.c | 1272 ++++++++ src/resolve/src/resolved-bus.c | 1565 ++++++++++ src/resolve/src/resolved-bus.h | 25 + src/resolve/src/resolved-conf.c | 236 ++ src/resolve/src/resolved-conf.h | 36 + src/resolve/src/resolved-def.h | 38 + src/resolve/src/resolved-dns-answer.c | 858 ++++++ src/resolve/src/resolved-dns-answer.h | 143 + src/resolve/src/resolved-dns-cache.c | 1088 +++++++ src/resolve/src/resolved-dns-cache.h | 52 + src/resolve/src/resolved-dns-dnssec.c | 2211 ++++++++++++++ src/resolve/src/resolved-dns-dnssec.h | 102 + src/resolve/src/resolved-dns-packet.c | 2295 +++++++++++++++ src/resolve/src/resolved-dns-packet.h | 272 ++ src/resolve/src/resolved-dns-query.c | 1114 +++++++ src/resolve/src/resolved-dns-query.h | 133 + src/resolve/src/resolved-dns-question.c | 468 +++ src/resolve/src/resolved-dns-question.h | 69 + src/resolve/src/resolved-dns-rr.c | 1521 ++++++++++ src/resolve/src/resolved-dns-rr.h | 333 +++ src/resolve/src/resolved-dns-scope.c | 1028 +++++++ src/resolve/src/resolved-dns-scope.h | 109 + src/resolve/src/resolved-dns-search-domain.c | 227 ++ src/resolve/src/resolved-dns-search-domain.h | 74 + src/resolve/src/resolved-dns-server.c | 740 +++++ src/resolve/src/resolved-dns-server.h | 143 + src/resolve/src/resolved-dns-stream.c | 403 +++ src/resolve/src/resolved-dns-stream.h | 61 + src/resolve/src/resolved-dns-synthesize.c | 413 +++ src/resolve/src/resolved-dns-synthesize.h | 30 + src/resolve/src/resolved-dns-transaction.c | 3026 ++++++++++++++++++++ src/resolve/src/resolved-dns-transaction.h | 177 ++ src/resolve/src/resolved-dns-trust-anchor.c | 743 +++++ src/resolve/src/resolved-dns-trust-anchor.h | 43 + src/resolve/src/resolved-dns-zone.c | 656 +++++ src/resolve/src/resolved-dns-zone.h | 81 + src/resolve/src/resolved-etc-hosts.c | 448 +++ src/resolve/src/resolved-etc-hosts.h | 28 + src/resolve/src/resolved-gperf.gperf | 21 + src/resolve/src/resolved-link-bus.c | 550 ++++ src/resolve/src/resolved-link-bus.h | 38 + src/resolve/src/resolved-link.c | 840 ++++++ src/resolve/src/resolved-link.h | 111 + src/resolve/src/resolved-llmnr.c | 476 +++ src/resolve/src/resolved-llmnr.h | 32 + src/resolve/src/resolved-manager.c | 1239 ++++++++ src/resolve/src/resolved-manager.h | 171 ++ src/resolve/src/resolved-mdns.c | 287 ++ src/resolve/src/resolved-mdns.h | 30 + src/resolve/src/resolved-resolv-conf.c | 267 ++ src/resolve/src/resolved-resolv-conf.h | 27 + src/resolve/src/resolved.c | 112 + src/resolve/src/resolved.conf.in | 19 + src/resolve/src/test-dnssec-complex.c | 236 ++ src/resolve/src/test-dnssec.c | 336 +++ src/resolve/src/test-resolve-tables.c | 27 + src/resolve/test-dnssec-complex.c | 236 -- src/resolve/test-dnssec.c | 336 --- src/resolve/test-resolve-tables.c | 27 - src/system/systemd/Makefile | 63 + src/system/systemd/macros.systemd.in | 105 + src/system/systemd/main.c | 2142 ++++++++++++++ src/system/systemd/org.freedesktop.systemd1.conf | 212 ++ .../systemd/org.freedesktop.systemd1.policy.in.in | 70 + .../systemd/org.freedesktop.systemd1.service | 11 + src/system/systemd/system.conf | 60 + src/system/systemd/systemd.pc.in | 34 + src/system/systemd/triggers.systemd.in | 64 + src/system/systemd/user.conf | 44 + src/systemd-analyze/.gitignore | 1 + src/systemd-analyze/Makefile | 35 + src/systemd-analyze/analyze-verify.c | 306 ++ src/systemd-analyze/analyze-verify.h | 26 + src/systemd-analyze/analyze.c | 1492 ++++++++++ src/systemd-bootchart/Makefile | 47 + src/systemd-bootchart/bootchart.c | 531 ++++ src/systemd-bootchart/bootchart.conf | 26 + src/systemd-bootchart/bootchart.h | 119 + src/systemd-bootchart/store.c | 555 ++++ src/systemd-bootchart/store.h | 36 + src/systemd-bootchart/svg.c | 1375 +++++++++ src/systemd-bootchart/svg.h | 35 + src/systemd/Makefile | 63 - src/systemd/macros.systemd.in | 105 - src/systemd/main.c | 2142 -------------- src/systemd/org.freedesktop.systemd1.conf | 212 -- src/systemd/org.freedesktop.systemd1.policy.in.in | 70 - src/systemd/org.freedesktop.systemd1.service | 11 - src/systemd/system.conf | 60 - src/systemd/systemd.pc.in | 34 - src/systemd/triggers.systemd.in | 64 - src/systemd/user.conf | 44 - 208 files changed, 43502 insertions(+), 43502 deletions(-) delete mode 100644 src/analyze/.gitignore delete mode 100644 src/analyze/Makefile delete mode 100644 src/analyze/analyze-verify.c delete mode 100644 src/analyze/analyze-verify.h delete mode 100644 src/analyze/analyze.c delete mode 100644 src/bootchart/Makefile delete mode 100644 src/bootchart/bootchart.c delete mode 100644 src/bootchart/bootchart.conf delete mode 100644 src/bootchart/bootchart.h delete mode 100644 src/bootchart/store.c delete mode 100644 src/bootchart/store.h delete mode 100644 src/bootchart/svg.c delete mode 100644 src/bootchart/svg.h create mode 100644 src/libfirewall/firewall-util.c create mode 100644 src/libfirewall/firewall-util.h delete mode 100644 src/libshared/firewall-util.c delete mode 100644 src/libshared/firewall-util.h delete mode 100644 src/machine/.gitignore delete mode 100644 src/machine/Makefile delete mode 100644 src/machine/image-dbus.c delete mode 100644 src/machine/image-dbus.h delete mode 100644 src/machine/machine-dbus.c delete mode 100644 src/machine/machine-dbus.h delete mode 100644 src/machine/machine.c delete mode 100644 src/machine/machine.h delete mode 100644 src/machine/machinectl.c delete mode 100644 src/machine/machined-dbus.c delete mode 100644 src/machine/machined.c delete mode 100644 src/machine/machined.h delete mode 100644 src/machine/org.freedesktop.machine1.conf delete mode 100644 src/machine/org.freedesktop.machine1.policy.in delete mode 100644 src/machine/org.freedesktop.machine1.service create mode 100644 src/machine/src/.gitignore create mode 100644 src/machine/src/Makefile create mode 100644 src/machine/src/image-dbus.c create mode 100644 src/machine/src/image-dbus.h create mode 100644 src/machine/src/machine-dbus.c create mode 100644 src/machine/src/machine-dbus.h create mode 100644 src/machine/src/machine.c create mode 100644 src/machine/src/machine.h create mode 100644 src/machine/src/machinectl.c create mode 100644 src/machine/src/machined-dbus.c create mode 100644 src/machine/src/machined.c create mode 100644 src/machine/src/machined.h create mode 100644 src/machine/src/org.freedesktop.machine1.conf create mode 100644 src/machine/src/org.freedesktop.machine1.policy.in create mode 100644 src/machine/src/org.freedesktop.machine1.service create mode 100644 src/machine/src/test-machine-tables.c delete mode 100644 src/machine/test-machine-tables.c delete mode 100644 src/resolve/.gitignore delete mode 100644 src/resolve/Makefile delete mode 100644 src/resolve/RFCs delete mode 100644 src/resolve/dns-type.c delete mode 100644 src/resolve/dns-type.h delete mode 100644 src/resolve/org.freedesktop.resolve1.conf delete mode 100644 src/resolve/org.freedesktop.resolve1.service delete mode 100644 src/resolve/resolve-tool.c delete mode 100644 src/resolve/resolved-bus.c delete mode 100644 src/resolve/resolved-bus.h delete mode 100644 src/resolve/resolved-conf.c delete mode 100644 src/resolve/resolved-conf.h delete mode 100644 src/resolve/resolved-def.h delete mode 100644 src/resolve/resolved-dns-answer.c delete mode 100644 src/resolve/resolved-dns-answer.h delete mode 100644 src/resolve/resolved-dns-cache.c delete mode 100644 src/resolve/resolved-dns-cache.h delete mode 100644 src/resolve/resolved-dns-dnssec.c delete mode 100644 src/resolve/resolved-dns-dnssec.h delete mode 100644 src/resolve/resolved-dns-packet.c delete mode 100644 src/resolve/resolved-dns-packet.h delete mode 100644 src/resolve/resolved-dns-query.c delete mode 100644 src/resolve/resolved-dns-query.h delete mode 100644 src/resolve/resolved-dns-question.c delete mode 100644 src/resolve/resolved-dns-question.h delete mode 100644 src/resolve/resolved-dns-rr.c delete mode 100644 src/resolve/resolved-dns-rr.h delete mode 100644 src/resolve/resolved-dns-scope.c delete mode 100644 src/resolve/resolved-dns-scope.h delete mode 100644 src/resolve/resolved-dns-search-domain.c delete mode 100644 src/resolve/resolved-dns-search-domain.h delete mode 100644 src/resolve/resolved-dns-server.c delete mode 100644 src/resolve/resolved-dns-server.h delete mode 100644 src/resolve/resolved-dns-stream.c delete mode 100644 src/resolve/resolved-dns-stream.h delete mode 100644 src/resolve/resolved-dns-synthesize.c delete mode 100644 src/resolve/resolved-dns-synthesize.h delete mode 100644 src/resolve/resolved-dns-transaction.c delete mode 100644 src/resolve/resolved-dns-transaction.h delete mode 100644 src/resolve/resolved-dns-trust-anchor.c delete mode 100644 src/resolve/resolved-dns-trust-anchor.h delete mode 100644 src/resolve/resolved-dns-zone.c delete mode 100644 src/resolve/resolved-dns-zone.h delete mode 100644 src/resolve/resolved-etc-hosts.c delete mode 100644 src/resolve/resolved-etc-hosts.h delete mode 100644 src/resolve/resolved-gperf.gperf delete mode 100644 src/resolve/resolved-link-bus.c delete mode 100644 src/resolve/resolved-link-bus.h delete mode 100644 src/resolve/resolved-link.c delete mode 100644 src/resolve/resolved-link.h delete mode 100644 src/resolve/resolved-llmnr.c delete mode 100644 src/resolve/resolved-llmnr.h delete mode 100644 src/resolve/resolved-manager.c delete mode 100644 src/resolve/resolved-manager.h delete mode 100644 src/resolve/resolved-mdns.c delete mode 100644 src/resolve/resolved-mdns.h delete mode 100644 src/resolve/resolved-resolv-conf.c delete mode 100644 src/resolve/resolved-resolv-conf.h delete mode 100644 src/resolve/resolved.c delete mode 100644 src/resolve/resolved.conf.in create mode 100644 src/resolve/src/.gitignore create mode 100644 src/resolve/src/Makefile create mode 100644 src/resolve/src/RFCs create mode 100644 src/resolve/src/dns-type.c create mode 100644 src/resolve/src/dns-type.h create mode 100644 src/resolve/src/org.freedesktop.resolve1.conf create mode 100644 src/resolve/src/org.freedesktop.resolve1.service create mode 100644 src/resolve/src/resolve-tool.c create mode 100644 src/resolve/src/resolved-bus.c create mode 100644 src/resolve/src/resolved-bus.h create mode 100644 src/resolve/src/resolved-conf.c create mode 100644 src/resolve/src/resolved-conf.h create mode 100644 src/resolve/src/resolved-def.h create mode 100644 src/resolve/src/resolved-dns-answer.c create mode 100644 src/resolve/src/resolved-dns-answer.h create mode 100644 src/resolve/src/resolved-dns-cache.c create mode 100644 src/resolve/src/resolved-dns-cache.h create mode 100644 src/resolve/src/resolved-dns-dnssec.c create mode 100644 src/resolve/src/resolved-dns-dnssec.h create mode 100644 src/resolve/src/resolved-dns-packet.c create mode 100644 src/resolve/src/resolved-dns-packet.h create mode 100644 src/resolve/src/resolved-dns-query.c create mode 100644 src/resolve/src/resolved-dns-query.h create mode 100644 src/resolve/src/resolved-dns-question.c create mode 100644 src/resolve/src/resolved-dns-question.h create mode 100644 src/resolve/src/resolved-dns-rr.c create mode 100644 src/resolve/src/resolved-dns-rr.h create mode 100644 src/resolve/src/resolved-dns-scope.c create mode 100644 src/resolve/src/resolved-dns-scope.h create mode 100644 src/resolve/src/resolved-dns-search-domain.c create mode 100644 src/resolve/src/resolved-dns-search-domain.h create mode 100644 src/resolve/src/resolved-dns-server.c create mode 100644 src/resolve/src/resolved-dns-server.h create mode 100644 src/resolve/src/resolved-dns-stream.c create mode 100644 src/resolve/src/resolved-dns-stream.h create mode 100644 src/resolve/src/resolved-dns-synthesize.c create mode 100644 src/resolve/src/resolved-dns-synthesize.h create mode 100644 src/resolve/src/resolved-dns-transaction.c create mode 100644 src/resolve/src/resolved-dns-transaction.h create mode 100644 src/resolve/src/resolved-dns-trust-anchor.c create mode 100644 src/resolve/src/resolved-dns-trust-anchor.h create mode 100644 src/resolve/src/resolved-dns-zone.c create mode 100644 src/resolve/src/resolved-dns-zone.h create mode 100644 src/resolve/src/resolved-etc-hosts.c create mode 100644 src/resolve/src/resolved-etc-hosts.h create mode 100644 src/resolve/src/resolved-gperf.gperf create mode 100644 src/resolve/src/resolved-link-bus.c create mode 100644 src/resolve/src/resolved-link-bus.h create mode 100644 src/resolve/src/resolved-link.c create mode 100644 src/resolve/src/resolved-link.h create mode 100644 src/resolve/src/resolved-llmnr.c create mode 100644 src/resolve/src/resolved-llmnr.h create mode 100644 src/resolve/src/resolved-manager.c create mode 100644 src/resolve/src/resolved-manager.h create mode 100644 src/resolve/src/resolved-mdns.c create mode 100644 src/resolve/src/resolved-mdns.h create mode 100644 src/resolve/src/resolved-resolv-conf.c create mode 100644 src/resolve/src/resolved-resolv-conf.h create mode 100644 src/resolve/src/resolved.c create mode 100644 src/resolve/src/resolved.conf.in create mode 100644 src/resolve/src/test-dnssec-complex.c create mode 100644 src/resolve/src/test-dnssec.c create mode 100644 src/resolve/src/test-resolve-tables.c delete mode 100644 src/resolve/test-dnssec-complex.c delete mode 100644 src/resolve/test-dnssec.c delete mode 100644 src/resolve/test-resolve-tables.c create mode 100644 src/system/systemd/Makefile create mode 100644 src/system/systemd/macros.systemd.in create mode 100644 src/system/systemd/main.c create mode 100644 src/system/systemd/org.freedesktop.systemd1.conf create mode 100644 src/system/systemd/org.freedesktop.systemd1.policy.in.in create mode 100644 src/system/systemd/org.freedesktop.systemd1.service create mode 100644 src/system/systemd/system.conf create mode 100644 src/system/systemd/systemd.pc.in create mode 100644 src/system/systemd/triggers.systemd.in create mode 100644 src/system/systemd/user.conf create mode 100644 src/systemd-analyze/.gitignore create mode 100644 src/systemd-analyze/Makefile create mode 100644 src/systemd-analyze/analyze-verify.c create mode 100644 src/systemd-analyze/analyze-verify.h create mode 100644 src/systemd-analyze/analyze.c create mode 100644 src/systemd-bootchart/Makefile create mode 100644 src/systemd-bootchart/bootchart.c create mode 100644 src/systemd-bootchart/bootchart.conf create mode 100644 src/systemd-bootchart/bootchart.h create mode 100644 src/systemd-bootchart/store.c create mode 100644 src/systemd-bootchart/store.h create mode 100644 src/systemd-bootchart/svg.c create mode 100644 src/systemd-bootchart/svg.h delete mode 100644 src/systemd/Makefile delete mode 100644 src/systemd/macros.systemd.in delete mode 100644 src/systemd/main.c delete mode 100644 src/systemd/org.freedesktop.systemd1.conf delete mode 100644 src/systemd/org.freedesktop.systemd1.policy.in.in delete mode 100644 src/systemd/org.freedesktop.systemd1.service delete mode 100644 src/systemd/system.conf delete mode 100644 src/systemd/systemd.pc.in delete mode 100644 src/systemd/triggers.systemd.in delete mode 100644 src/systemd/user.conf diff --git a/src/analyze/.gitignore b/src/analyze/.gitignore deleted file mode 100644 index 752ea236c8..0000000000 --- a/src/analyze/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/systemd-analyze diff --git a/src/analyze/Makefile b/src/analyze/Makefile deleted file mode 100644 index 5b2f58d7e4..0000000000 --- a/src/analyze/Makefile +++ /dev/null @@ -1,35 +0,0 @@ -# -*- Mode: makefile; indent-tabs-mode: t -*- -# -# This file is part of systemd. -# -# Copyright 2010-2012 Lennart Poettering -# Copyright 2010-2012 Kay Sievers -# Copyright 2013 Zbigniew Jędrzejewski-Szmek -# Copyright 2013 David Strauss -# Copyright 2016 Luke Shumaker -# -# 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 . -systemd_analyze_SOURCES = \ - src/analyze/analyze.c \ - src/analyze/analyze-verify.c \ - src/analyze/analyze-verify.h - -systemd_analyze_CFLAGS = \ - $(AM_CFLAGS) \ - $(SECCOMP_CFLAGS) \ - $(MOUNT_CFLAGS) - -systemd_analyze_LDADD = \ - libcore.la - diff --git a/src/analyze/analyze-verify.c b/src/analyze/analyze-verify.c deleted file mode 100644 index d36c8db3d4..0000000000 --- a/src/analyze/analyze-verify.c +++ /dev/null @@ -1,306 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -#include "alloc-util.h" -#include "analyze-verify.h" -#include "bus-error.h" -#include "bus-util.h" -#include "log.h" -#include "manager.h" -#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; - - _cleanup_strv_free_ char **ans = NULL; - int r; - - STRV_FOREACH(filename, filenames) { - char *t; - - t = dirname_malloc(*filename); - if (!t) - return -ENOMEM; - - r = strv_consume(&ans, t); - if (r < 0) - return r; - } - - assert_se(strv_uniq(ans)); - - r = strv_extend(&ans, ""); - if (r < 0) - return r; - - *var = strv_join(ans, ":"); - if (!*var) - return -ENOMEM; - - return 0; -} - -static int verify_socket(Unit *u) { - int r; - - assert(u); - - if (u->type != UNIT_SOCKET) - return 0; - - /* Cannot run this without the service being around */ - - /* This makes sure instance is created if necessary. */ - r = socket_instantiate_service(SOCKET(u)); - if (r < 0) { - log_unit_error_errno(u, r, "Socket cannot be started, failed to create instance: %m"); - return r; - } - - /* This checks both type of sockets */ - if (UNIT_ISSET(SOCKET(u)->service)) { - Service *service; - - service = SERVICE(UNIT_DEREF(SOCKET(u)->service)); - log_unit_debug(u, "Using %s", UNIT(service)->id); - - if (UNIT(service)->load_state != UNIT_LOADED) { - log_unit_error(u, "Service %s not loaded, %s cannot be started.", UNIT(service)->id, u->id); - return -ENOENT; - } - } - - return 0; -} - -static int verify_executable(Unit *u, ExecCommand *exec) { - if (exec == NULL) - return 0; - - if (access(exec->path, X_OK) < 0) - return log_unit_error_errno(u, errno, "Command %s is not executable: %m", exec->path); - - return 0; -} - -static int verify_executables(Unit *u) { - ExecCommand *exec; - int r = 0, k; - unsigned i; - - assert(u); - - exec = u->type == UNIT_SOCKET ? SOCKET(u)->control_command : - u->type == UNIT_MOUNT ? MOUNT(u)->control_command : - u->type == UNIT_SWAP ? SWAP(u)->control_command : NULL; - k = verify_executable(u, exec); - if (k < 0 && r == 0) - r = k; - - if (u->type == UNIT_SERVICE) - for (i = 0; i < ELEMENTSOF(SERVICE(u)->exec_command); i++) { - k = verify_executable(u, SERVICE(u)->exec_command[i]); - if (k < 0 && r == 0) - r = k; - } - - if (u->type == UNIT_SOCKET) - for (i = 0; i < ELEMENTSOF(SOCKET(u)->exec_command); i++) { - k = verify_executable(u, SOCKET(u)->exec_command[i]); - if (k < 0 && r == 0) - r = k; - } - - return r; -} - -static int verify_documentation(Unit *u, bool check_man) { - char **p; - int r = 0, k; - - STRV_FOREACH(p, u->documentation) { - log_unit_debug(u, "Found documentation item: %s", *p); - - if (check_man && startswith(*p, "man:")) { - k = show_man_page(*p + 4, true); - if (k != 0) { - if (k < 0) - log_unit_error_errno(u, r, "Can't show %s: %m", *p); - else { - log_unit_error_errno(u, r, "man %s command failed with code %d", *p + 4, k); - k = -ENOEXEC; - } - if (r == 0) - r = k; - } - } - } - - /* Check remote URLs? */ - - return r; -} - -static int verify_unit(Unit *u, bool check_man) { - _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; - int r, k; - - assert(u); - - if (log_get_max_level() >= LOG_DEBUG) - unit_dump(u, stdout, "\t"); - - log_unit_debug(u, "Creating %s/start job", u->id); - r = manager_add_job(u->manager, JOB_START, u, JOB_REPLACE, &err, NULL); - if (r < 0) - log_unit_error_errno(u, r, "Failed to create %s/start: %s", u->id, bus_error_message(&err, r)); - - k = verify_socket(u); - if (k < 0 && r == 0) - r = k; - - k = verify_executables(u); - if (k < 0 && r == 0) - r = k; - - k = verify_documentation(u, check_man); - if (k < 0 && r == 0) - r = k; - - return r; -} - -int verify_units(char **filenames, ManagerRunningAs running_as, bool check_man) { - _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; - Manager *m = NULL; - FILE *serial = NULL; - FDSet *fdset = NULL; - - _cleanup_free_ char *var = NULL; - - char **filename; - int r = 0, k; - - Unit *units[strv_length(filenames)]; - int i, count = 0; - - if (strv_isempty(filenames)) - return 0; - - /* set the path */ - r = generate_path(&var, filenames); - if (r < 0) - return log_error_errno(r, "Failed to generate unit load path: %m"); - - assert_se(set_unit_path(var) >= 0); - - r = manager_new(running_as, true, &m); - if (r < 0) - return log_error_errno(r, "Failed to initialize manager: %m"); - - log_debug("Starting manager..."); - - r = manager_startup(m, serial, fdset); - if (r < 0) { - log_error_errno(r, "Failed to start manager: %m"); - goto finish; - } - - manager_clear_jobs(m); - - log_debug("Loading remaining units from the command line..."); - - STRV_FOREACH(filename, filenames) { - _cleanup_free_ char *prepared = NULL; - - log_debug("Handling %s...", *filename); - - 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) - r = k; - } else - count ++; - } - - for (i = 0; i < count; i++) { - k = verify_unit(units[i], check_man); - if (k < 0 && r == 0) - r = k; - } - -finish: - manager_free(m); - - return r; -} diff --git a/src/analyze/analyze-verify.h b/src/analyze/analyze-verify.h deleted file mode 100644 index 54adad93e1..0000000000 --- a/src/analyze/analyze-verify.h +++ /dev/null @@ -1,26 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#pragma once - -#include - -#include "path-lookup.h" - -int verify_units(char **filenames, ManagerRunningAs running_as, bool check_man); diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c deleted file mode 100644 index 1a28093b07..0000000000 --- a/src/analyze/analyze.c +++ /dev/null @@ -1,1492 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2013 Lennart Poettering - Copyright 2013 Simon Peeters - - 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 . -***/ - -#include -#include -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "analyze-verify.h" -#include "bus-error.h" -#include "bus-util.h" -#include "glob-util.h" -#include "hashmap.h" -#include "locale-util.h" -#include "log.h" -#include "pager.h" -#include "parse-util.h" -#include "special.h" -#include "strv.h" -#include "strxcpyx.h" -#include "terminal-util.h" -#include "unit-name.h" -#include "util.h" - -#define SCALE_X (0.1 / 1000.0) /* pixels per us */ -#define SCALE_Y (20.0) - -#define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0)) - -#define svg(...) printf(__VA_ARGS__) - -#define svg_bar(class, x1, x2, y) \ - svg(" \n", \ - (class), \ - SCALE_X * (x1), SCALE_Y * (y), \ - SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0) - -#define svg_text(b, x, y, format, ...) \ - do { \ - svg(" ", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \ - svg(format, ## __VA_ARGS__); \ - svg("\n"); \ - } while(false) - -static enum dot { - DEP_ALL, - DEP_ORDER, - DEP_REQUIRE -} arg_dot = DEP_ALL; -static char** arg_dot_from_patterns = NULL; -static char** arg_dot_to_patterns = NULL; -static usec_t arg_fuzz = 0; -static bool arg_no_pager = false; -static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; -static char *arg_host = NULL; -static bool arg_user = false; -static bool arg_man = true; - -struct boot_times { - usec_t firmware_time; - usec_t loader_time; - usec_t kernel_time; - usec_t kernel_done_time; - usec_t initrd_time; - usec_t userspace_time; - usec_t finish_time; - usec_t security_start_time; - usec_t security_finish_time; - usec_t generators_start_time; - usec_t generators_finish_time; - usec_t unitsload_start_time; - usec_t unitsload_finish_time; - - /* - * If we're analyzing the user instance, all timestamps will be offset - * by its own start-up timestamp, which may be arbitrarily big. - * With "plot", this causes arbitrarily wide output SVG files which almost - * completely consist of empty space. Thus we cancel out this offset. - * - * This offset is subtracted from times above by acquire_boot_times(), - * but it still needs to be subtracted from unit-specific timestamps - * (so it is stored here for reference). - */ - usec_t reverse_offset; -}; - -struct unit_times { - char *name; - usec_t activating; - usec_t activated; - usec_t deactivated; - usec_t deactivating; - usec_t time; -}; - -struct host_info { - char *hostname; - char *kernel_name; - char *kernel_release; - char *kernel_version; - char *os_pretty_name; - char *virtualization; - char *architecture; -}; - -static void pager_open_if_enabled(void) { - - if (arg_no_pager) - return; - - pager_open(false); -} - -static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *interface, const char *property, uint64_t *val) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(property); - assert(val); - - r = sd_bus_get_property_trivial( - bus, - "org.freedesktop.systemd1", - path, - interface, - property, - &error, - 't', val); - - if (r < 0) { - log_error("Failed to parse reply: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char *property, char ***strv) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(path); - assert(property); - assert(strv); - - r = sd_bus_get_property_strv( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - property, - &error, - strv); - if (r < 0) { - log_error("Failed to get unit property %s: %s", property, bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int compare_unit_time(const void *a, const void *b) { - return compare(((struct unit_times *)b)->time, - ((struct unit_times *)a)->time); -} - -static int compare_unit_start(const void *a, const void *b) { - return compare(((struct unit_times *)a)->activating, - ((struct unit_times *)b)->activating); -} - -static void free_unit_times(struct unit_times *t, unsigned n) { - struct unit_times *p; - - for (p = t; p < t + n; p++) - free(p->name); - - free(t); -} - -static void subtract_timestamp(usec_t *a, usec_t b) { - assert(a); - - if (*a > 0) { - assert(*a >= b); - *a -= b; - } -} - -static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) { - static struct boot_times times; - static bool cached = false; - - if (cached) - goto finish; - - assert_cc(sizeof(usec_t) == sizeof(uint64_t)); - - if (bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "FirmwareTimestampMonotonic", - ×.firmware_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "LoaderTimestampMonotonic", - ×.loader_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "KernelTimestamp", - ×.kernel_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "InitRDTimestampMonotonic", - ×.initrd_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UserspaceTimestampMonotonic", - ×.userspace_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "FinishTimestampMonotonic", - ×.finish_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SecurityStartTimestampMonotonic", - ×.security_start_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SecurityFinishTimestampMonotonic", - ×.security_finish_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GeneratorsStartTimestampMonotonic", - ×.generators_start_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GeneratorsFinishTimestampMonotonic", - ×.generators_finish_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UnitsLoadStartTimestampMonotonic", - ×.unitsload_start_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UnitsLoadFinishTimestampMonotonic", - ×.unitsload_finish_time) < 0) - return -EIO; - - if (times.finish_time <= 0) { - log_error("Bootup is not yet finished. Please try again later."); - return -EINPROGRESS; - } - - if (arg_user) { - /* - * User-instance-specific timestamps processing - * (see comment to reverse_offset in struct boot_times). - */ - times.reverse_offset = times.userspace_time; - - times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time = times.userspace_time = 0; - subtract_timestamp(×.finish_time, times.reverse_offset); - - subtract_timestamp(×.security_start_time, times.reverse_offset); - subtract_timestamp(×.security_finish_time, times.reverse_offset); - - subtract_timestamp(×.generators_start_time, times.reverse_offset); - subtract_timestamp(×.generators_finish_time, times.reverse_offset); - - subtract_timestamp(×.unitsload_start_time, times.reverse_offset); - subtract_timestamp(×.unitsload_finish_time, times.reverse_offset); - } else { - if (times.initrd_time) - times.kernel_done_time = times.initrd_time; - else - times.kernel_done_time = times.userspace_time; - } - - cached = true; - -finish: - *bt = × - return 0; -} - -static void free_host_info(struct host_info *hi) { - - if (!hi) - return; - - free(hi->hostname); - free(hi->kernel_name); - free(hi->kernel_release); - free(hi->kernel_version); - free(hi->os_pretty_name); - free(hi->virtualization); - free(hi->architecture); - free(hi); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info*, free_host_info); - -static int acquire_time_data(sd_bus *bus, struct unit_times **out) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r, c = 0; - struct boot_times *boot_times = NULL; - struct unit_times *unit_times = NULL; - size_t size = 0; - UnitInfo u; - - r = acquire_boot_times(bus, &boot_times); - if (r < 0) - goto fail; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "ListUnits", - &error, &reply, - NULL); - if (r < 0) { - log_error("Failed to list units: %s", bus_error_message(&error, -r)); - goto fail; - } - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); - if (r < 0) { - bus_log_parse_error(r); - goto fail; - } - - while ((r = bus_parse_unit_info(reply, &u)) > 0) { - struct unit_times *t; - - if (!GREEDY_REALLOC(unit_times, size, c+1)) { - r = log_oom(); - goto fail; - } - - t = unit_times+c; - t->name = NULL; - - assert_cc(sizeof(usec_t) == sizeof(uint64_t)); - - if (bus_get_uint64_property(bus, u.unit_path, - "org.freedesktop.systemd1.Unit", - "InactiveExitTimestampMonotonic", - &t->activating) < 0 || - bus_get_uint64_property(bus, u.unit_path, - "org.freedesktop.systemd1.Unit", - "ActiveEnterTimestampMonotonic", - &t->activated) < 0 || - bus_get_uint64_property(bus, u.unit_path, - "org.freedesktop.systemd1.Unit", - "ActiveExitTimestampMonotonic", - &t->deactivating) < 0 || - bus_get_uint64_property(bus, u.unit_path, - "org.freedesktop.systemd1.Unit", - "InactiveEnterTimestampMonotonic", - &t->deactivated) < 0) { - r = -EIO; - goto fail; - } - - subtract_timestamp(&t->activating, boot_times->reverse_offset); - subtract_timestamp(&t->activated, boot_times->reverse_offset); - subtract_timestamp(&t->deactivating, boot_times->reverse_offset); - subtract_timestamp(&t->deactivated, boot_times->reverse_offset); - - if (t->activated >= t->activating) - t->time = t->activated - t->activating; - else if (t->deactivated >= t->activating) - t->time = t->deactivated - t->activating; - else - t->time = 0; - - if (t->activating == 0) - continue; - - t->name = strdup(u.id); - if (t->name == NULL) { - r = log_oom(); - goto fail; - } - c++; - } - if (r < 0) { - bus_log_parse_error(r); - goto fail; - } - - *out = unit_times; - return c; - -fail: - if (unit_times) - free_unit_times(unit_times, (unsigned) c); - return r; -} - -static int acquire_host_info(sd_bus *bus, struct host_info **hi) { - static const struct bus_properties_map hostname_map[] = { - { "Hostname", "s", NULL, offsetof(struct host_info, hostname) }, - { "KernelName", "s", NULL, offsetof(struct host_info, kernel_name) }, - { "KernelRelease", "s", NULL, offsetof(struct host_info, kernel_release) }, - { "KernelVersion", "s", NULL, offsetof(struct host_info, kernel_version) }, - { "OperatingSystemPrettyName", "s", NULL, offsetof(struct host_info, os_pretty_name) }, - {} - }; - - static const struct bus_properties_map manager_map[] = { - { "Virtualization", "s", NULL, offsetof(struct host_info, virtualization) }, - { "Architecture", "s", NULL, offsetof(struct host_info, architecture) }, - {} - }; - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(free_host_infop) struct host_info *host; - int r; - - host = new0(struct host_info, 1); - if (!host) - return log_oom(); - - r = bus_map_all_properties(bus, - "org.freedesktop.hostname1", - "/org/freedesktop/hostname1", - hostname_map, - host); - if (r < 0) - log_debug_errno(r, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error, r)); - - r = bus_map_all_properties(bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - manager_map, - host); - if (r < 0) - return log_error_errno(r, "Failed to get host information from systemd: %s", bus_error_message(&error, r)); - - *hi = host; - host = NULL; - - return 0; -} - -static int pretty_boot_time(sd_bus *bus, char **_buf) { - char ts[FORMAT_TIMESPAN_MAX]; - struct boot_times *t; - static char buf[4096]; - size_t size; - char *ptr; - int r; - - r = acquire_boot_times(bus, &t); - if (r < 0) - return r; - - ptr = buf; - size = sizeof(buf); - - size = strpcpyf(&ptr, size, "Startup finished in "); - if (t->firmware_time) - size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC)); - if (t->loader_time) - size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC)); - if (t->kernel_time) - size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC)); - if (t->initrd_time > 0) - size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC)); - - size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC)); - strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC)); - - ptr = strdup(buf); - if (!ptr) - return log_oom(); - - *_buf = ptr; - return 0; -} - -static void svg_graph_box(double height, double begin, double end) { - long long i; - - /* outside box, fill */ - svg("\n", - SCALE_X * (end - begin), SCALE_Y * height); - - for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) { - /* lines for each second */ - if (i % 5000000 == 0) - svg(" \n" - " %.01fs\n", - SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i); - else if (i % 1000000 == 0) - svg(" \n" - " %.01fs\n", - SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i); - else - svg(" \n", - SCALE_X * i, SCALE_X * i, SCALE_Y * height); - } -} - -static int analyze_plot(sd_bus *bus) { - _cleanup_(free_host_infop) struct host_info *host = NULL; - struct unit_times *times; - struct boot_times *boot; - int n, m = 1, y=0; - double width; - _cleanup_free_ char *pretty_times = NULL; - struct unit_times *u; - - n = acquire_boot_times(bus, &boot); - if (n < 0) - return n; - - n = pretty_boot_time(bus, &pretty_times); - if (n < 0) - return n; - - n = acquire_host_info(bus, &host); - if (n < 0) - return n; - - n = acquire_time_data(bus, ×); - if (n <= 0) - return n; - - qsort(times, n, sizeof(struct unit_times), compare_unit_start); - - width = SCALE_X * (boot->firmware_time + boot->finish_time); - if (width < 800.0) - width = 800.0; - - if (boot->firmware_time > boot->loader_time) - m++; - if (boot->loader_time) { - m++; - if (width < 1000.0) - width = 1000.0; - } - if (boot->initrd_time) - m++; - if (boot->kernel_time) - m++; - - for (u = times; u < times + n; u++) { - double text_start, text_width; - - if (u->activating < boot->userspace_time || - u->activating > boot->finish_time) { - u->name = mfree(u->name); - continue; - } - - /* If the text cannot fit on the left side then - * increase the svg width so it fits on the right. - * TODO: calculate the text width more accurately */ - text_width = 8.0 * strlen(u->name); - text_start = (boot->firmware_time + u->activating) * SCALE_X; - if (text_width > text_start && text_width + text_start > width) - width = text_width + text_start; - - if (u->deactivated > u->activating && u->deactivated <= boot->finish_time - && u->activated == 0 && u->deactivating == 0) - u->activated = u->deactivating = u->deactivated; - if (u->activated < u->activating || u->activated > boot->finish_time) - u->activated = boot->finish_time; - if (u->deactivating < u->activated || u->activated > boot->finish_time) - u->deactivating = boot->finish_time; - if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time) - u->deactivated = boot->finish_time; - m++; - } - - svg("\n" - "\n"); - - svg("\n\n", - 80.0 + width, 150.0 + (m * SCALE_Y) + - 5 * SCALE_Y /* legend */); - - /* write some basic info as a comment, including some help */ - svg("\n" - "\n" - "\n" - "\n" - "\n\n" - "\n\n", VERSION); - - /* style sheet */ - svg("\n \n\n\n"); - - svg("\n"); - svg("%s", pretty_times); - svg("%s %s (%s %s %s) %s %s", - isempty(host->os_pretty_name) ? "GNU/Linux" : host->os_pretty_name, - strempty(host->hostname), - strempty(host->kernel_name), - strempty(host->kernel_release), - strempty(host->kernel_version), - strempty(host->architecture), - strempty(host->virtualization)); - - svg("\n", 20.0 + (SCALE_X * boot->firmware_time)); - svg_graph_box(m, -(double) boot->firmware_time, boot->finish_time); - - if (boot->firmware_time) { - svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y); - svg_text(true, -(double) boot->firmware_time, y, "firmware"); - y++; - } - if (boot->loader_time) { - svg_bar("loader", -(double) boot->loader_time, 0, y); - svg_text(true, -(double) boot->loader_time, y, "loader"); - y++; - } - if (boot->kernel_time) { - svg_bar("kernel", 0, boot->kernel_done_time, y); - svg_text(true, 0, y, "kernel"); - y++; - } - if (boot->initrd_time) { - svg_bar("initrd", boot->initrd_time, boot->userspace_time, y); - svg_text(true, boot->initrd_time, y, "initrd"); - y++; - } - svg_bar("active", boot->userspace_time, boot->finish_time, y); - svg_bar("security", boot->security_start_time, boot->security_finish_time, y); - svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y); - svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y); - svg_text(true, boot->userspace_time, y, "systemd"); - y++; - - for (u = times; u < times + n; u++) { - char ts[FORMAT_TIMESPAN_MAX]; - bool b; - - if (!u->name) - continue; - - svg_bar("activating", u->activating, u->activated, y); - svg_bar("active", u->activated, u->deactivating, y); - svg_bar("deactivating", u->deactivating, u->deactivated, y); - - /* place the text on the left if we have passed the half of the svg width */ - b = u->activating * SCALE_X < width / 2; - if (u->time) - svg_text(b, u->activating, y, "%s (%s)", - u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC)); - else - svg_text(b, u->activating, y, "%s", u->name); - y++; - } - - svg("\n"); - - /* Legend */ - svg("\n"); - y++; - svg_bar("activating", 0, 300000, y); - svg_text(true, 400000, y, "Activating"); - y++; - svg_bar("active", 0, 300000, y); - svg_text(true, 400000, y, "Active"); - y++; - svg_bar("deactivating", 0, 300000, y); - svg_text(true, 400000, y, "Deactivating"); - y++; - svg_bar("security", 0, 300000, y); - svg_text(true, 400000, y, "Setting up security module"); - y++; - svg_bar("generators", 0, 300000, y); - svg_text(true, 400000, y, "Generators"); - y++; - svg_bar("unitsload", 0, 300000, y); - svg_text(true, 400000, y, "Loading unit files"); - y++; - - svg("\n\n"); - - svg("\n"); - - free_unit_times(times, (unsigned) n); - - n = 0; - return n; -} - -static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches, - bool last, struct unit_times *times, struct boot_times *boot) { - unsigned int i; - char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX]; - - for (i = level; i != 0; i--) - printf("%s", draw_special_char(branches & (1 << (i-1)) ? DRAW_TREE_VERTICAL : DRAW_TREE_SPACE)); - - printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH)); - - if (times) { - if (times->time) - printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED, name, - format_timespan(ts, sizeof(ts), times->activating - boot->userspace_time, USEC_PER_MSEC), - format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ANSI_NORMAL); - else if (times->activated > boot->userspace_time) - printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC)); - else - printf("%s", name); - } else - printf("%s", name); - printf("\n"); - - return 0; -} - -static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) { - _cleanup_free_ char *path = NULL; - - assert(bus); - assert(name); - assert(deps); - - path = unit_dbus_path_from_name(name); - if (path == NULL) - return -ENOMEM; - - return bus_get_unit_property_strv(bus, path, "After", deps); -} - -static Hashmap *unit_times_hashmap; - -static int list_dependencies_compare(const void *_a, const void *_b) { - const char **a = (const char**) _a, **b = (const char**) _b; - usec_t usa = 0, usb = 0; - struct unit_times *times; - - times = hashmap_get(unit_times_hashmap, *a); - if (times) - usa = times->activated; - times = hashmap_get(unit_times_hashmap, *b); - if (times) - usb = times->activated; - - return usb - usa; -} - -static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int level, char ***units, - unsigned int branches) { - _cleanup_strv_free_ char **deps = NULL; - char **c; - int r = 0; - usec_t service_longest = 0; - int to_print = 0; - struct unit_times *times; - struct boot_times *boot; - - if (strv_extend(units, name)) - return log_oom(); - - r = list_dependencies_get_dependencies(bus, name, &deps); - if (r < 0) - return r; - - qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare); - - r = acquire_boot_times(bus, &boot); - if (r < 0) - return r; - - STRV_FOREACH(c, deps) { - times = hashmap_get(unit_times_hashmap, *c); - if (times - && times->activated - && times->activated <= boot->finish_time - && (times->activated >= service_longest - || service_longest == 0)) { - service_longest = times->activated; - break; - } - } - - if (service_longest == 0 ) - return r; - - STRV_FOREACH(c, deps) { - times = hashmap_get(unit_times_hashmap, *c); - if (times && times->activated && times->activated <= boot->finish_time && (service_longest - times->activated) <= arg_fuzz) - to_print++; - } - - if (!to_print) - return r; - - STRV_FOREACH(c, deps) { - times = hashmap_get(unit_times_hashmap, *c); - if (!times - || !times->activated - || times->activated > boot->finish_time - || service_longest - times->activated > arg_fuzz) - continue; - - to_print--; - - r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot); - if (r < 0) - return r; - - if (strv_contains(*units, *c)) { - r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0), - true, NULL, boot); - if (r < 0) - return r; - continue; - } - - r = list_dependencies_one(bus, *c, level + 1, units, - (branches << 1) | (to_print ? 1 : 0)); - if (r < 0) - return r; - - if (!to_print) - break; - } - return 0; -} - -static int list_dependencies(sd_bus *bus, const char *name) { - _cleanup_strv_free_ char **units = NULL; - char ts[FORMAT_TIMESPAN_MAX]; - struct unit_times *times; - int r; - const char *id; - _cleanup_free_ char *path = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - struct boot_times *boot; - - assert(bus); - - path = unit_dbus_path_from_name(name); - if (path == NULL) - return -ENOMEM; - - r = sd_bus_get_property( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "Id", - &error, - &reply, - "s"); - if (r < 0) { - log_error("Failed to get ID: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "s", &id); - if (r < 0) - return bus_log_parse_error(r); - - times = hashmap_get(unit_times_hashmap, id); - - r = acquire_boot_times(bus, &boot); - if (r < 0) - return r; - - if (times) { - if (times->time) - printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED, id, - format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ANSI_NORMAL); - else if (times->activated > boot->userspace_time) - printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC)); - else - printf("%s\n", id); - } - - return list_dependencies_one(bus, name, 0, &units, 0); -} - -static int analyze_critical_chain(sd_bus *bus, char *names[]) { - struct unit_times *times; - unsigned int i; - Hashmap *h; - int n, r; - - n = acquire_time_data(bus, ×); - if (n <= 0) - return n; - - h = hashmap_new(&string_hash_ops); - if (!h) - return -ENOMEM; - - for (i = 0; i < (unsigned)n; i++) { - r = hashmap_put(h, times[i].name, ×[i]); - if (r < 0) - return r; - } - unit_times_hashmap = h; - - pager_open_if_enabled(); - - puts("The time after the unit is active or started is printed after the \"@\" character.\n" - "The time the unit takes to start is printed after the \"+\" character.\n"); - - if (!strv_isempty(names)) { - char **name; - STRV_FOREACH(name, names) - list_dependencies(bus, *name); - } else - list_dependencies(bus, SPECIAL_DEFAULT_TARGET); - - hashmap_free(h); - free_unit_times(times, (unsigned) n); - return 0; -} - -static int analyze_blame(sd_bus *bus) { - struct unit_times *times; - unsigned i; - int n; - - n = acquire_time_data(bus, ×); - if (n <= 0) - return n; - - qsort(times, n, sizeof(struct unit_times), compare_unit_time); - - pager_open_if_enabled(); - - for (i = 0; i < (unsigned) n; i++) { - char ts[FORMAT_TIMESPAN_MAX]; - - if (times[i].time > 0) - printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name); - } - - free_unit_times(times, (unsigned) n); - return 0; -} - -static int analyze_time(sd_bus *bus) { - _cleanup_free_ char *buf = NULL; - int r; - - r = pretty_boot_time(bus, &buf); - if (r < 0) - return r; - - puts(buf); - return 0; -} - -static int graph_one_property(sd_bus *bus, const UnitInfo *u, const char* prop, const char *color, char* patterns[], char* from_patterns[], char* to_patterns[]) { - _cleanup_strv_free_ char **units = NULL; - char **unit; - int r; - bool match_patterns; - - assert(u); - assert(prop); - assert(color); - - match_patterns = strv_fnmatch(patterns, u->id, 0); - - if (!strv_isempty(from_patterns) && - !match_patterns && - !strv_fnmatch(from_patterns, u->id, 0)) - return 0; - - r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units); - if (r < 0) - return r; - - STRV_FOREACH(unit, units) { - bool match_patterns2; - - match_patterns2 = strv_fnmatch(patterns, *unit, 0); - - if (!strv_isempty(to_patterns) && - !match_patterns2 && - !strv_fnmatch(to_patterns, *unit, 0)) - continue; - - if (!strv_isempty(patterns) && !match_patterns && !match_patterns2) - continue; - - printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color); - } - - return 0; -} - -static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[], char *from_patterns[], char *to_patterns[]) { - int r; - - assert(bus); - assert(u); - - if (IN_SET(arg_dot, DEP_ORDER, DEP_ALL)) { - r = graph_one_property(bus, u, "After", "green", patterns, from_patterns, to_patterns); - if (r < 0) - return r; - } - - if (IN_SET(arg_dot, DEP_REQUIRE, DEP_ALL)) { - r = graph_one_property(bus, u, "Requires", "black", patterns, from_patterns, to_patterns); - if (r < 0) - return r; - r = graph_one_property(bus, u, "Requisite", "darkblue", patterns, from_patterns, to_patterns); - if (r < 0) - return r; - r = graph_one_property(bus, u, "Wants", "grey66", patterns, from_patterns, to_patterns); - if (r < 0) - return r; - r = graph_one_property(bus, u, "Conflicts", "red", patterns, from_patterns, to_patterns); - if (r < 0) - return r; - } - - return 0; -} - -static int expand_patterns(sd_bus *bus, char **patterns, char ***ret) { - _cleanup_strv_free_ char **expanded_patterns = NULL; - char **pattern; - int r; - - STRV_FOREACH(pattern, patterns) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *unit = NULL, *unit_id = NULL; - - if (strv_extend(&expanded_patterns, *pattern) < 0) - return log_oom(); - - if (string_is_glob(*pattern)) - continue; - - unit = unit_dbus_path_from_name(*pattern); - if (!unit) - return log_oom(); - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - unit, - "org.freedesktop.systemd1.Unit", - "Id", - &error, - &unit_id); - if (r < 0) - return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r)); - - if (!streq(*pattern, unit_id)) { - if (strv_extend(&expanded_patterns, unit_id) < 0) - return log_oom(); - } - } - - *ret = expanded_patterns; - expanded_patterns = NULL; /* do not free */ - - return 0; -} - -static int dot(sd_bus *bus, char* patterns[]) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_strv_free_ char **expanded_patterns = NULL; - _cleanup_strv_free_ char **expanded_from_patterns = NULL; - _cleanup_strv_free_ char **expanded_to_patterns = NULL; - int r; - UnitInfo u; - - r = expand_patterns(bus, patterns, &expanded_patterns); - if (r < 0) - return r; - - r = expand_patterns(bus, arg_dot_from_patterns, &expanded_from_patterns); - if (r < 0) - return r; - - r = expand_patterns(bus, arg_dot_to_patterns, &expanded_to_patterns); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "ListUnits", - &error, - &reply, - ""); - if (r < 0) { - log_error("Failed to list units: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); - if (r < 0) - return bus_log_parse_error(r); - - printf("digraph systemd {\n"); - - while ((r = bus_parse_unit_info(reply, &u)) > 0) { - - r = graph_one(bus, &u, expanded_patterns, expanded_from_patterns, expanded_to_patterns); - if (r < 0) - return r; - } - if (r < 0) - return bus_log_parse_error(r); - - printf("}\n"); - - log_info(" Color legend: black = Requires\n" - " dark blue = Requisite\n" - " dark grey = Wants\n" - " red = Conflicts\n" - " green = After\n"); - - if (on_tty()) - log_notice("-- You probably want to process this output with graphviz' dot tool.\n" - "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n"); - - return 0; -} - -static int dump(sd_bus *bus, char **args) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *text = NULL; - int r; - - if (!strv_isempty(args)) { - log_error("Too many arguments."); - return -E2BIG; - } - - pager_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "Dump", - &error, - &reply, - ""); - if (r < 0) - return log_error_errno(r, "Failed issue method call: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "s", &text); - if (r < 0) - return bus_log_parse_error(r); - - fputs(text, stdout); - return 0; -} - -static int set_log_level(sd_bus *bus, char **args) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(args); - - if (strv_length(args) != 1) { - log_error("This command expects one argument only."); - return -E2BIG; - } - - r = sd_bus_set_property( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "LogLevel", - &error, - "s", - args[0]); - if (r < 0) - return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r)); - - return 0; -} - -static int set_log_target(sd_bus *bus, char **args) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(args); - - if (strv_length(args) != 1) { - log_error("This command expects one argument only."); - return -E2BIG; - } - - r = sd_bus_set_property( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "LogTarget", - &error, - "s", - args[0]); - if (r < 0) - return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r)); - - return 0; -} - -static void help(void) { - - pager_open_if_enabled(); - - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Profile systemd, show unit dependencies, check unit files.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --system Operate on system systemd instance\n" - " --user Operate on user systemd instance\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --order Show only order in the graph\n" - " --require Show only requirement in the graph\n" - " --from-pattern=GLOB Show only origins in the graph\n" - " --to-pattern=GLOB Show only destinations in the graph\n" - " --fuzz=SECONDS Also print also services which finished SECONDS\n" - " earlier than the latest in the branch\n" - " --man[=BOOL] Do [not] check for existence of man pages\n\n" - "Commands:\n" - " time Print time spent in the kernel\n" - " blame Print list of running units ordered by time to init\n" - " critical-chain Print a tree of the time critical chain of units\n" - " plot Output SVG graphic showing service initialization\n" - " dot Output dependency graph in dot(1) format\n" - " set-log-level LEVEL Set logging threshold for manager\n" - " set-log-target TARGET Set logging target for manager\n" - " dump Output state serialization of service manager\n" - " verify FILE... Check unit files for correctness\n" - , program_invocation_short_name); - - /* When updating this list, including descriptions, apply - * changes to shell-completion/bash/systemd-analyze and - * shell-completion/zsh/_systemd-analyze too. */ -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_ORDER, - ARG_REQUIRE, - ARG_USER, - ARG_SYSTEM, - ARG_DOT_FROM_PATTERN, - ARG_DOT_TO_PATTERN, - ARG_FUZZ, - ARG_NO_PAGER, - ARG_MAN, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "order", no_argument, NULL, ARG_ORDER }, - { "require", no_argument, NULL, ARG_REQUIRE }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN }, - { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN }, - { "fuzz", required_argument, NULL, ARG_FUZZ }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "man", optional_argument, NULL, ARG_MAN }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - {} - }; - - int r, c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_USER: - arg_user = true; - break; - - case ARG_SYSTEM: - arg_user = false; - break; - - case ARG_ORDER: - arg_dot = DEP_ORDER; - break; - - case ARG_REQUIRE: - arg_dot = DEP_REQUIRE; - break; - - case ARG_DOT_FROM_PATTERN: - if (strv_extend(&arg_dot_from_patterns, optarg) < 0) - return log_oom(); - - break; - - case ARG_DOT_TO_PATTERN: - if (strv_extend(&arg_dot_to_patterns, optarg) < 0) - return log_oom(); - - break; - - case ARG_FUZZ: - r = parse_sec(optarg, &arg_fuzz); - if (r < 0) - return r; - break; - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; - - case 'M': - arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; - break; - - case ARG_MAN: - if (optarg) { - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --man= argument."); - return -EINVAL; - } - - arg_man = !!r; - } else - arg_man = true; - - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option code."); - } - - return 1; /* work to do */ -} - -int main(int argc, char *argv[]) { - int r; - - setlocale(LC_ALL, ""); - setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */ - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (streq_ptr(argv[optind], "verify")) - r = verify_units(argv+optind+1, - arg_user ? MANAGER_USER : MANAGER_SYSTEM, - arg_man); - else { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - - r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus); - if (r < 0) { - log_error_errno(r, "Failed to create bus connection: %m"); - goto finish; - } - - if (!argv[optind] || streq(argv[optind], "time")) - r = analyze_time(bus); - else if (streq(argv[optind], "blame")) - r = analyze_blame(bus); - else if (streq(argv[optind], "critical-chain")) - r = analyze_critical_chain(bus, argv+optind+1); - else if (streq(argv[optind], "plot")) - r = analyze_plot(bus); - else if (streq(argv[optind], "dot")) - r = dot(bus, argv+optind+1); - else if (streq(argv[optind], "dump")) - r = dump(bus, argv+optind+1); - else if (streq(argv[optind], "set-log-level")) - r = set_log_level(bus, argv+optind+1); - else if (streq(argv[optind], "set-log-target")) - r = set_log_target(bus, argv+optind+1); - else - log_error("Unknown operation '%s'.", argv[optind]); - } - -finish: - pager_close(); - - strv_free(arg_dot_from_patterns); - strv_free(arg_dot_to_patterns); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/bootchart/Makefile b/src/bootchart/Makefile deleted file mode 100644 index 5af4cebd20..0000000000 --- a/src/bootchart/Makefile +++ /dev/null @@ -1,47 +0,0 @@ -# -*- Mode: makefile; indent-tabs-mode: t -*- -# -# This file is part of systemd. -# -# Copyright 2010-2012 Lennart Poettering -# Copyright 2010-2012 Kay Sievers -# Copyright 2013 Zbigniew Jędrzejewski-Szmek -# Copyright 2013 David Strauss -# Copyright 2016 Luke Shumaker -# -# 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 . -if ENABLE_BOOTCHART -systemd_bootchart_SOURCES = \ - src/bootchart/bootchart.c \ - src/bootchart/bootchart.h \ - src/bootchart/store.c \ - src/bootchart/store.h \ - src/bootchart/svg.c \ - src/bootchart/svg.h - -systemd_bootchart_LDADD = \ - libshared.la - -rootlibexec_PROGRAMS += \ - systemd-bootchart - -dist_pkgsysconf_DATA += \ - src/bootchart/bootchart.conf - -nodist_systemunit_DATA += \ - units/systemd-bootchart.service -endif - -EXTRA_DIST += \ - units/systemd-bootchart.service.in - diff --git a/src/bootchart/bootchart.c b/src/bootchart/bootchart.c deleted file mode 100644 index 77d158f5f9..0000000000 --- a/src/bootchart/bootchart.c +++ /dev/null @@ -1,531 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2009-2013 Intel Corporation - - Authors: - Auke Kok - - 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 . - ***/ - -/*** - - Many thanks to those who contributed ideas and code: - - Ziga Mahkovec - Original bootchart author - - Anders Norgaard - PyBootchartgui - - Michael Meeks - bootchart2 - - Scott James Remnant - Ubuntu C-based logger - - Arjan van der Ven - for the idea to merge bootgraph.pl functionality - - ***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-journal.h" - -#include "alloc-util.h" -#include "bootchart.h" -#include "conf-parser.h" -#include "def.h" -#include "fd-util.h" -#include "fileio.h" -#include "io-util.h" -#include "list.h" -#include "macro.h" -#include "parse-util.h" -#include "path-util.h" -#include "store.h" -#include "string-util.h" -#include "strxcpyx.h" -#include "svg.h" -#include "util.h" - -static int exiting = 0; - -#define DEFAULT_SAMPLES_LEN 500 -#define DEFAULT_HZ 25.0 -#define DEFAULT_SCALE_X 100.0 /* 100px = 1sec */ -#define DEFAULT_SCALE_Y 20.0 /* 16px = 1 process bar */ -#define DEFAULT_INIT ROOTLIBEXECDIR "/systemd" -#define DEFAULT_OUTPUT "/run/log" - -/* graph defaults */ -bool arg_entropy = false; -bool arg_initcall = true; -bool arg_relative = false; -bool arg_filter = true; -bool arg_show_cmdline = false; -bool arg_show_cgroup = false; -bool arg_pss = false; -bool arg_percpu = false; -int arg_samples_len = DEFAULT_SAMPLES_LEN; /* we record len+1 (1 start sample) */ -double arg_hz = DEFAULT_HZ; -double arg_scale_x = DEFAULT_SCALE_X; -double arg_scale_y = DEFAULT_SCALE_Y; - -char arg_init_path[PATH_MAX] = DEFAULT_INIT; -char arg_output_path[PATH_MAX] = DEFAULT_OUTPUT; - -static void signal_handler(int sig) { - exiting = 1; -} - -#define BOOTCHART_MAX (16*1024*1024) - -static void parse_conf(void) { - char *init = NULL, *output = NULL; - const ConfigTableItem items[] = { - { "Bootchart", "Samples", config_parse_int, 0, &arg_samples_len }, - { "Bootchart", "Frequency", config_parse_double, 0, &arg_hz }, - { "Bootchart", "Relative", config_parse_bool, 0, &arg_relative }, - { "Bootchart", "Filter", config_parse_bool, 0, &arg_filter }, - { "Bootchart", "Output", config_parse_path, 0, &output }, - { "Bootchart", "Init", config_parse_path, 0, &init }, - { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &arg_pss }, - { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &arg_entropy }, - { "Bootchart", "ScaleX", config_parse_double, 0, &arg_scale_x }, - { "Bootchart", "ScaleY", config_parse_double, 0, &arg_scale_y }, - { "Bootchart", "ControlGroup", config_parse_bool, 0, &arg_show_cgroup }, - { "Bootchart", "PerCPU", config_parse_bool, 0, &arg_percpu }, - { NULL, NULL, NULL, 0, NULL } - }; - - config_parse_many(PKGSYSCONFDIR "/bootchart.conf", - CONF_PATHS_NULSTR("systemd/bootchart.conf.d"), - NULL, config_item_table_lookup, items, true, NULL); - - if (init != NULL) - strscpy(arg_init_path, sizeof(arg_init_path), init); - if (output != NULL) - strscpy(arg_output_path, sizeof(arg_output_path), output); -} - -static void help(void) { - printf("Usage: %s [OPTIONS]\n\n" - "Options:\n" - " -r --rel Record time relative to recording\n" - " -f --freq=FREQ Sample frequency [%g]\n" - " -n --samples=N Stop sampling at [%d] samples\n" - " -x --scale-x=N Scale the graph horizontally [%g] \n" - " -y --scale-y=N Scale the graph vertically [%g] \n" - " -p --pss Enable PSS graph (CPU intensive)\n" - " -e --entropy Enable the entropy_avail graph\n" - " -o --output=PATH Path to output files [%s]\n" - " -i --init=PATH Path to init executable [%s]\n" - " -F --no-filter Disable filtering of unimportant or ephemeral processes\n" - " -C --cmdline Display full command lines with arguments\n" - " -c --control-group Display process control group\n" - " --per-cpu Draw each CPU utilization and wait bar also\n" - " -h --help Display this message\n\n" - "See bootchart.conf for more information.\n", - program_invocation_short_name, - DEFAULT_HZ, - DEFAULT_SAMPLES_LEN, - DEFAULT_SCALE_X, - DEFAULT_SCALE_Y, - DEFAULT_OUTPUT, - DEFAULT_INIT); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_PERCPU = 0x100, - }; - - static const struct option options[] = { - {"rel", no_argument, NULL, 'r' }, - {"freq", required_argument, NULL, 'f' }, - {"samples", required_argument, NULL, 'n' }, - {"pss", no_argument, NULL, 'p' }, - {"output", required_argument, NULL, 'o' }, - {"init", required_argument, NULL, 'i' }, - {"no-filter", no_argument, NULL, 'F' }, - {"cmdline", no_argument, NULL, 'C' }, - {"control-group", no_argument, NULL, 'c' }, - {"help", no_argument, NULL, 'h' }, - {"scale-x", required_argument, NULL, 'x' }, - {"scale-y", required_argument, NULL, 'y' }, - {"entropy", no_argument, NULL, 'e' }, - {"per-cpu", no_argument, NULL, ARG_PERCPU}, - {} - }; - int c, r; - - if (getpid() == 1) - opterr = 0; - - while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0) - switch (c) { - - case 'r': - arg_relative = true; - break; - case 'f': - r = safe_atod(optarg, &arg_hz); - if (r < 0) - log_warning_errno(r, "failed to parse --freq/-f argument '%s': %m", - optarg); - break; - case 'F': - arg_filter = false; - break; - case 'C': - arg_show_cmdline = true; - break; - case 'c': - arg_show_cgroup = true; - break; - case 'n': - r = safe_atoi(optarg, &arg_samples_len); - if (r < 0) - log_warning_errno(r, "failed to parse --samples/-n argument '%s': %m", - optarg); - break; - case 'o': - path_kill_slashes(optarg); - strscpy(arg_output_path, sizeof(arg_output_path), optarg); - break; - case 'i': - path_kill_slashes(optarg); - strscpy(arg_init_path, sizeof(arg_init_path), optarg); - break; - case 'p': - arg_pss = true; - break; - case 'x': - r = safe_atod(optarg, &arg_scale_x); - if (r < 0) - log_warning_errno(r, "failed to parse --scale-x/-x argument '%s': %m", - optarg); - break; - case 'y': - r = safe_atod(optarg, &arg_scale_y); - if (r < 0) - log_warning_errno(r, "failed to parse --scale-y/-y argument '%s': %m", - optarg); - break; - case 'e': - arg_entropy = true; - break; - case ARG_PERCPU: - arg_percpu = true; - break; - case 'h': - help(); - return 0; - case '?': - if (getpid() != 1) - return -EINVAL; - else - return 0; - default: - assert_not_reached("Unhandled option code."); - } - - if (arg_hz <= 0) { - log_error("Frequency needs to be > 0"); - return -EINVAL; - } - - return 1; -} - -static int do_journal_append(char *file) { - _cleanup_free_ char *bootchart_message = NULL; - _cleanup_free_ char *bootchart_file = NULL; - _cleanup_free_ char *p = NULL; - _cleanup_close_ int fd = -1; - struct iovec iovec[5]; - int r, j = 0; - ssize_t n; - - bootchart_file = strappend("BOOTCHART_FILE=", file); - if (!bootchart_file) - return log_oom(); - - IOVEC_SET_STRING(iovec[j++], bootchart_file); - IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518"); - IOVEC_SET_STRING(iovec[j++], "PRIORITY=7"); - bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL); - if (!bootchart_message) - return log_oom(); - - IOVEC_SET_STRING(iovec[j++], bootchart_message); - - p = malloc(10 + BOOTCHART_MAX); - if (!p) - return log_oom(); - - memcpy(p, "BOOTCHART=", 10); - - fd = open(file, O_RDONLY|O_CLOEXEC); - if (fd < 0) - return log_error_errno(errno, "Failed to open bootchart data \"%s\": %m", file); - - n = loop_read(fd, p + 10, BOOTCHART_MAX, false); - if (n < 0) - return log_error_errno(n, "Failed to read bootchart data: %m"); - - iovec[j].iov_base = p; - iovec[j].iov_len = 10 + n; - j++; - - r = sd_journal_sendv(iovec, j); - if (r < 0) - log_error_errno(r, "Failed to send bootchart: %m"); - - return 0; -} - -int main(int argc, char *argv[]) { - static struct list_sample_data *sampledata; - _cleanup_closedir_ DIR *proc = NULL; - _cleanup_free_ char *build = NULL; - _cleanup_fclose_ FILE *of = NULL; - _cleanup_close_ int sysfd = -1; - struct ps_struct *ps_first; - double graph_start; - double log_start; - double interval; - char output_file[PATH_MAX]; - char datestr[200]; - int pscount = 0; - int n_cpus = 0; - int overrun = 0; - time_t t = 0; - int r, samples; - struct ps_struct *ps; - struct rlimit rlim; - struct list_sample_data *head; - struct sigaction sig = { - .sa_handler = signal_handler, - }; - - parse_conf(); - - r = parse_argv(argc, argv); - if (r < 0) - return EXIT_FAILURE; - - if (r == 0) - return EXIT_SUCCESS; - - /* - * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then - * fork: - * - parent execs executable specified via init_path[] (/usr/lib/systemd/systemd by default) as pid=1 - * - child logs data - */ - if (getpid() == 1) { - if (fork()) - /* parent */ - execl(arg_init_path, arg_init_path, NULL); - } - argv[0][0] = '@'; - - rlim.rlim_cur = 4096; - rlim.rlim_max = 4096; - (void) setrlimit(RLIMIT_NOFILE, &rlim); - - /* start with empty ps LL */ - ps_first = new0(struct ps_struct, 1); - if (!ps_first) { - log_oom(); - return EXIT_FAILURE; - } - - /* handle TERM/INT nicely */ - sigaction(SIGHUP, &sig, NULL); - - interval = (1.0 / arg_hz) * 1000000000.0; - - if (arg_relative) - graph_start = log_start = gettime_ns(); - else { - struct timespec n; - double uptime; - - clock_gettime(clock_boottime_or_monotonic(), &n); - uptime = (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC)); - - log_start = gettime_ns(); - graph_start = log_start - uptime; - } - - if (graph_start < 0.0) { - log_error("Failed to setup graph start time.\n\n" - "The system uptime probably includes time that the system was suspended. " - "Use --rel to bypass this issue."); - return EXIT_FAILURE; - } - - LIST_HEAD_INIT(head); - - /* main program loop */ - for (samples = 0; !exiting && samples < arg_samples_len; samples++) { - int res; - double sample_stop; - double elapsed; - double timeleft; - - sampledata = new0(struct list_sample_data, 1); - if (sampledata == NULL) { - log_oom(); - return EXIT_FAILURE; - } - - sampledata->sampletime = gettime_ns(); - sampledata->counter = samples; - - if (sysfd < 0) - sysfd = open("/sys", O_RDONLY|O_CLOEXEC); - - if (!build) { - if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &build, NULL) == -ENOENT) - parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &build, NULL); - } - - if (proc) - rewinddir(proc); - else - proc = opendir("/proc"); - - /* wait for /proc to become available, discarding samples */ - if (proc) { - r = log_sample(proc, samples, ps_first, &sampledata, &pscount, &n_cpus); - if (r < 0) - return EXIT_FAILURE; - } - - sample_stop = gettime_ns(); - - elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0; - timeleft = interval - elapsed; - - /* - * check if we have not consumed our entire timeslice. If we - * do, don't sleep and take a new sample right away. - * we'll lose all the missed samples and overrun our total - * time - */ - if (timeleft > 0) { - struct timespec req; - - req.tv_sec = (time_t)(timeleft / 1000000000.0); - req.tv_nsec = (long)(timeleft - (req.tv_sec * 1000000000.0)); - - res = nanosleep(&req, NULL); - if (res) { - if (errno == EINTR) - /* caught signal, probably HUP! */ - break; - log_error_errno(errno, "nanosleep() failed: %m"); - return EXIT_FAILURE; - } - } else { - overrun++; - /* calculate how many samples we lost and scrap them */ - arg_samples_len -= (int)(-timeleft / interval); - } - LIST_PREPEND(link, head, sampledata); - } - - /* do some cleanup, close fd's */ - ps = ps_first; - while (ps->next_ps) { - ps = ps->next_ps; - ps->schedstat = safe_close(ps->schedstat); - ps->sched = safe_close(ps->sched); - ps->smaps = safe_fclose(ps->smaps); - } - - if (!of) { - t = time(NULL); - r = strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t)); - assert_se(r > 0); - - snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr); - of = fopen(output_file, "we"); - } - - if (!of) { - log_error("Error opening output file '%s': %m\n", output_file); - return EXIT_FAILURE; - } - - r = svg_do(of, strna(build), head, ps_first, - samples, pscount, n_cpus, graph_start, - log_start, interval, overrun); - - if (r < 0) { - log_error_errno(r, "Error generating svg file: %m"); - return EXIT_FAILURE; - } - - log_info("systemd-bootchart wrote %s\n", output_file); - - r = do_journal_append(output_file); - if (r < 0) - return EXIT_FAILURE; - - /* nitpic cleanups */ - ps = ps_first->next_ps; - while (ps->next_ps) { - struct ps_struct *old; - - old = ps; - old->sample = ps->first; - ps = ps->next_ps; - while (old->sample->next) { - struct ps_sched_struct *oldsample = old->sample; - - old->sample = old->sample->next; - free(oldsample); - } - free(old->cgroup); - free(old->sample); - free(old); - } - - free(ps->cgroup); - free(ps->sample); - free(ps); - - sampledata = head; - while (sampledata->link_prev) { - struct list_sample_data *old_sampledata = sampledata; - sampledata = sampledata->link_prev; - free(old_sampledata); - } - free(sampledata); - - /* don't complain when overrun once, happens most commonly on 1st sample */ - if (overrun > 1) - log_warning("systemd-bootchart: sample time overrun %i times\n", overrun); - - return 0; -} diff --git a/src/bootchart/bootchart.conf b/src/bootchart/bootchart.conf deleted file mode 100644 index 4f5e50936e..0000000000 --- a/src/bootchart/bootchart.conf +++ /dev/null @@ -1,26 +0,0 @@ -# This file is part of systemd. -# -# 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. -# -# Entries in this file show the compile time defaults. -# You can change settings by editing this file. -# Defaults can be restored by simply deleting this file. -# -# See bootchart.conf(5) for details. - -[Bootchart] -#Samples=500 -#Frequency=25 -#Relative=no -#Filter=yes -#Output= -#Init=/path/to/init-binary -#PlotMemoryUsage=no -#PlotEntropyGraph=no -#ScaleX=100 -#ScaleY=20 -#ControlGroup=no -#PerCPU=no diff --git a/src/bootchart/bootchart.h b/src/bootchart/bootchart.h deleted file mode 100644 index 1b445b954b..0000000000 --- a/src/bootchart/bootchart.h +++ /dev/null @@ -1,119 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright (C) 2009-2013 Intel Corporation - - Authors: - Auke Kok - - 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 . -***/ - -#include - -#include "list.h" - -#define MAXCPUS 16 -#define MAXPIDS 65535 - -struct block_stat_struct { - /* /proc/vmstat pgpgin & pgpgout */ - int bi; - int bo; -}; - -struct cpu_stat_sample_struct { - /* /proc/schedstat fields 10 & 11 (after name) */ - double runtime; - double waittime; -}; - -/* per process, per sample data we will log */ -struct ps_sched_struct { - /* /proc//schedstat fields 1 & 2 */ - double runtime; - double waittime; - int pss; - struct list_sample_data *sampledata; - struct ps_sched_struct *next; - struct ps_sched_struct *prev; - struct ps_sched_struct *cross; /* cross pointer */ - struct ps_struct *ps_new; -}; - -struct list_sample_data { - double runtime[MAXCPUS]; - double waittime[MAXCPUS]; - double sampletime; - int entropy_avail; - struct block_stat_struct blockstat; - LIST_FIELDS(struct list_sample_data, link); /* DLL */ - int counter; -}; - -/* process info */ -struct ps_struct { - struct ps_struct *next_ps; /* SLL pointer */ - struct ps_struct *parent; /* ppid ref */ - struct ps_struct *children; /* children */ - struct ps_struct *next; /* siblings */ - - /* must match - otherwise it's a new process with same PID */ - char name[256]; - int pid; - int ppid; - char *cgroup; - - /* cache fd's */ - int sched; - int schedstat; - FILE *smaps; - - /* pointers to first/last seen timestamps */ - struct ps_sched_struct *first; - struct ps_sched_struct *last; - - /* records actual start time, may be way before bootchart runs */ - double starttime; - - /* record human readable total cpu time */ - double total; - - /* largest PSS size found */ - int pss_max; - - /* for drawing connection lines later */ - double pos_x; - double pos_y; - - struct ps_sched_struct *sample; -}; - -extern bool arg_relative; -extern bool arg_filter; -extern bool arg_show_cmdline; -extern bool arg_show_cgroup; -extern bool arg_pss; -extern bool arg_entropy; -extern bool arg_percpu; -extern bool arg_initcall; -extern int arg_samples_len; -extern double arg_hz; -extern double arg_scale_x; -extern double arg_scale_y; - -extern char arg_output_path[PATH_MAX]; -extern char arg_init_path[PATH_MAX]; diff --git a/src/bootchart/store.c b/src/bootchart/store.c deleted file mode 100644 index 42cb8043ce..0000000000 --- a/src/bootchart/store.c +++ /dev/null @@ -1,555 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2009-2013 Intel Corporation - - Authors: - Auke Kok - - 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 . - ***/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "bootchart.h" -#include "cgroup-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "parse-util.h" -#include "store.h" -#include "string-util.h" -#include "strxcpyx.h" -#include "time-util.h" -#include "util.h" - -/* - * Alloc a static 4k buffer for stdio - primarily used to increase - * PSS buffering from the default 1k stdin buffer to reduce - * read() overhead. - */ -static char smaps_buf[4096]; -static int skip = 0; - -double gettime_ns(void) { - struct timespec n; - - clock_gettime(CLOCK_MONOTONIC, &n); - - return (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC)); -} - -static char *bufgetline(char *buf) { - char *c; - - if (!buf) - return NULL; - - c = strchr(buf, '\n'); - if (c) - c++; - - return c; -} - -static int pid_cmdline_strscpy(int procfd, char *buffer, size_t buf_len, int pid) { - char filename[PATH_MAX]; - _cleanup_close_ int fd = -1; - ssize_t n; - - sprintf(filename, "%d/cmdline", pid); - fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC); - if (fd < 0) - return -errno; - - n = read(fd, buffer, buf_len-1); - if (n > 0) { - int i; - for (i = 0; i < n; i++) - if (buffer[i] == '\0') - buffer[i] = ' '; - buffer[n] = '\0'; - } - - return 0; -} - -int log_sample(DIR *proc, - int sample, - struct ps_struct *ps_first, - struct list_sample_data **ptr, - int *pscount, - int *cpus) { - - static int vmstat = -1; - _cleanup_free_ char *buf_schedstat = NULL; - char buf[4096]; - char key[256]; - char val[256]; - char rt[256]; - char wt[256]; - char *m; - int r; - int c; - int p; - int mod; - static int e_fd = -1; - ssize_t s; - ssize_t n; - struct dirent *ent; - int fd; - struct list_sample_data *sampledata; - struct ps_sched_struct *ps_prev = NULL; - int procfd; - int taskfd = -1; - - sampledata = *ptr; - - procfd = dirfd(proc); - if (procfd < 0) - return -errno; - - if (vmstat < 0) { - /* block stuff */ - vmstat = openat(procfd, "vmstat", O_RDONLY|O_CLOEXEC); - if (vmstat < 0) - return log_error_errno(errno, "Failed to open /proc/vmstat: %m"); - } - - n = pread(vmstat, buf, sizeof(buf) - 1, 0); - if (n <= 0) { - vmstat = safe_close(vmstat); - if (n < 0) - return -errno; - return -ENODATA; - } - - buf[n] = '\0'; - - m = buf; - while (m) { - if (sscanf(m, "%s %s", key, val) < 2) - goto vmstat_next; - if (streq(key, "pgpgin")) - sampledata->blockstat.bi = atoi(val); - if (streq(key, "pgpgout")) { - sampledata->blockstat.bo = atoi(val); - break; - } -vmstat_next: - m = bufgetline(m); - if (!m) - break; - } - - /* Parse "/proc/schedstat" for overall CPU utilization */ - r = read_full_file("/proc/schedstat", &buf_schedstat, NULL); - if (r < 0) - return log_error_errno(r, "Unable to read schedstat: %m"); - - m = buf_schedstat; - while (m) { - if (sscanf(m, "%s %*s %*s %*s %*s %*s %*s %s %s", key, rt, wt) < 3) - goto schedstat_next; - - if (strstr(key, "cpu")) { - r = safe_atoi((const char*)(key+3), &c); - if (r < 0 || c > MAXCPUS -1) - /* Oops, we only have room for MAXCPUS data */ - break; - sampledata->runtime[c] = atoll(rt); - sampledata->waittime[c] = atoll(wt); - - if (c == *cpus) - *cpus = c + 1; - } -schedstat_next: - m = bufgetline(m); - if (!m) - break; - } - - if (arg_entropy) { - if (e_fd < 0) { - e_fd = openat(procfd, "sys/kernel/random/entropy_avail", O_RDONLY|O_CLOEXEC); - if (e_fd < 0) - return log_error_errno(errno, "Failed to open /proc/sys/kernel/random/entropy_avail: %m"); - } - - n = pread(e_fd, buf, sizeof(buf) - 1, 0); - if (n <= 0) { - e_fd = safe_close(e_fd); - } else { - buf[n] = '\0'; - sampledata->entropy_avail = atoi(buf); - } - } - - while ((ent = readdir(proc)) != NULL) { - char filename[PATH_MAX]; - int pid; - struct ps_struct *ps; - - if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9')) - continue; - - pid = atoi(ent->d_name); - - if (pid >= MAXPIDS) - continue; - - ps = ps_first; - while (ps->next_ps) { - ps = ps->next_ps; - if (ps->pid == pid) - break; - } - - /* end of our LL? then append a new record */ - if (ps->pid != pid) { - _cleanup_fclose_ FILE *st = NULL; - char t[32]; - struct ps_struct *parent; - - ps->next_ps = new0(struct ps_struct, 1); - if (!ps->next_ps) - return log_oom(); - - ps = ps->next_ps; - ps->pid = pid; - ps->sched = -1; - ps->schedstat = -1; - - ps->sample = new0(struct ps_sched_struct, 1); - if (!ps->sample) - return log_oom(); - - ps->sample->sampledata = sampledata; - - (*pscount)++; - - /* mark our first sample */ - ps->first = ps->last = ps->sample; - ps->sample->runtime = atoll(rt); - ps->sample->waittime = atoll(wt); - - /* get name, start time */ - if (ps->sched < 0) { - sprintf(filename, "%d/sched", pid); - ps->sched = openat(procfd, filename, O_RDONLY|O_CLOEXEC); - if (ps->sched < 0) - continue; - } - - s = pread(ps->sched, buf, sizeof(buf) - 1, 0); - if (s <= 0) { - ps->sched = safe_close(ps->sched); - continue; - } - buf[s] = '\0'; - - if (!sscanf(buf, "%s %*s %*s", key)) - continue; - - strscpy(ps->name, sizeof(ps->name), key); - - /* cmdline */ - if (arg_show_cmdline) - pid_cmdline_strscpy(procfd, ps->name, sizeof(ps->name), pid); - - /* discard line 2 */ - m = bufgetline(buf); - if (!m) - continue; - - m = bufgetline(m); - if (!m) - continue; - - if (!sscanf(m, "%*s %*s %s", t)) - continue; - - r = safe_atod(t, &ps->starttime); - if (r < 0) - continue; - - ps->starttime /= 1000.0; - - if (arg_show_cgroup) - /* if this fails, that's OK */ - cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, - ps->pid, &ps->cgroup); - - /* ppid */ - sprintf(filename, "%d/stat", pid); - fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC); - if (fd < 0) - continue; - - st = fdopen(fd, "re"); - if (!st) { - close(fd); - continue; - } - - if (!fscanf(st, "%*s %*s %*s %i", &p)) - continue; - - ps->ppid = p; - - /* - * setup child pointers - * - * these are used to paint the tree coherently later - * each parent has a LL of children, and a LL of siblings - */ - if (pid == 1) - continue; /* nothing to do for init atm */ - - /* kthreadd has ppid=0, which breaks our tree ordering */ - if (ps->ppid == 0) - ps->ppid = 1; - - parent = ps_first; - while ((parent->next_ps && parent->pid != ps->ppid)) - parent = parent->next_ps; - - if (parent->pid != ps->ppid) { - /* orphan */ - ps->ppid = 1; - parent = ps_first->next_ps; - } - - ps->parent = parent; - - if (!parent->children) { - /* it's the first child */ - parent->children = ps; - } else { - /* walk all children and append */ - struct ps_struct *children; - children = parent->children; - while (children->next) - children = children->next; - - children->next = ps; - } - } - - /* else -> found pid, append data in ps */ - - /* below here is all continuous logging parts - we get here on every - * iteration */ - - /* rt, wt */ - if (ps->schedstat < 0) { - sprintf(filename, "%d/schedstat", pid); - ps->schedstat = openat(procfd, filename, O_RDONLY|O_CLOEXEC); - if (ps->schedstat < 0) - continue; - } - - s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0); - if (s <= 0) { - /* clean up our file descriptors - assume that the process exited */ - close(ps->schedstat); - ps->schedstat = -1; - ps->sched = safe_close(ps->sched); - continue; - } - - buf[s] = '\0'; - - if (!sscanf(buf, "%s %s %*s", rt, wt)) - continue; - - ps->sample->next = new0(struct ps_sched_struct, 1); - if (!ps->sample->next) - return log_oom(); - - ps->sample->next->prev = ps->sample; - ps->sample = ps->sample->next; - ps->last = ps->sample; - ps->sample->runtime = atoll(rt); - ps->sample->waittime = atoll(wt); - ps->sample->sampledata = sampledata; - ps->sample->ps_new = ps; - if (ps_prev) - ps_prev->cross = ps->sample; - - ps_prev = ps->sample; - ps->total = (ps->last->runtime - ps->first->runtime) - / 1000000000.0; - - /* Take into account CPU runtime/waittime spent in non-main threads of the process - * by parsing "/proc/[pid]/task/[tid]/schedstat" for all [tid] != [pid] - * See https://github.com/systemd/systemd/issues/139 - */ - - /* Browse directory "/proc/[pid]/task" to know the thread ids of process [pid] */ - snprintf(filename, sizeof(filename), PID_FMT "/task", pid); - taskfd = openat(procfd, filename, O_RDONLY|O_DIRECTORY|O_CLOEXEC); - if (taskfd >= 0) { - _cleanup_closedir_ DIR *taskdir = NULL; - - taskdir = fdopendir(taskfd); - if (!taskdir) { - safe_close(taskfd); - return -errno; - } - FOREACH_DIRENT(ent, taskdir, break) { - int tid = -1; - _cleanup_close_ int tid_schedstat = -1; - long long delta_rt; - long long delta_wt; - - if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9')) - continue; - - /* Skip main thread as it was already accounted */ - r = safe_atoi(ent->d_name, &tid); - if (r < 0 || tid == pid) - continue; - - /* Parse "/proc/[pid]/task/[tid]/schedstat" */ - snprintf(filename, sizeof(filename), PID_FMT "/schedstat", tid); - tid_schedstat = openat(taskfd, filename, O_RDONLY|O_CLOEXEC); - - if (tid_schedstat == -1) - continue; - - s = pread(tid_schedstat, buf, sizeof(buf) - 1, 0); - if (s <= 0) - continue; - buf[s] = '\0'; - - if (!sscanf(buf, "%s %s %*s", rt, wt)) - continue; - - r = safe_atolli(rt, &delta_rt); - if (r < 0) - continue; - r = safe_atolli(rt, &delta_wt); - if (r < 0) - continue; - ps->sample->runtime += delta_rt; - ps->sample->waittime += delta_wt; - } - } - - if (!arg_pss) - goto catch_rename; - - /* Pss */ - if (!ps->smaps) { - sprintf(filename, "%d/smaps", pid); - fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC); - if (fd < 0) - continue; - ps->smaps = fdopen(fd, "re"); - if (!ps->smaps) { - close(fd); - continue; - } - setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf)); - } else { - rewind(ps->smaps); - } - - /* test to see if we need to skip another field */ - if (skip == 0) { - if (fgets(buf, sizeof(buf), ps->smaps) == NULL) { - continue; - } - if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) { - continue; - } - if (buf[392] == 'V') { - skip = 2; - } - else { - skip = 1; - } - rewind(ps->smaps); - } - - while (1) { - int pss_kb; - - /* skip one line, this contains the object mapped. */ - if (fgets(buf, sizeof(buf), ps->smaps) == NULL) { - break; - } - /* then there's a 28 char 14 line block */ - if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) { - break; - } - pss_kb = atoi(&buf[61]); - ps->sample->pss += pss_kb; - - /* skip one more line if this is a newer kernel */ - if (skip == 2) { - if (fgets(buf, sizeof(buf), ps->smaps) == NULL) - break; - } - } - - if (ps->sample->pss > ps->pss_max) - ps->pss_max = ps->sample->pss; - -catch_rename: - /* catch process rename, try to randomize time */ - mod = (arg_hz < 4.0) ? 4.0 : (arg_hz / 4.0); - if (((sample - ps->pid) + pid) % (int)(mod) == 0) { - - /* re-fetch name */ - /* get name, start time */ - if (ps->sched < 0) { - sprintf(filename, "%d/sched", pid); - ps->sched = openat(procfd, filename, O_RDONLY|O_CLOEXEC); - if (ps->sched < 0) - continue; - } - - s = pread(ps->sched, buf, sizeof(buf) - 1, 0); - if (s <= 0) { - /* clean up file descriptors */ - ps->sched = safe_close(ps->sched); - ps->schedstat = safe_close(ps->schedstat); - continue; - } - - buf[s] = '\0'; - - if (!sscanf(buf, "%s %*s %*s", key)) - continue; - - strscpy(ps->name, sizeof(ps->name), key); - - /* cmdline */ - if (arg_show_cmdline) - pid_cmdline_strscpy(procfd, ps->name, sizeof(ps->name), pid); - } - } - - return 0; -} diff --git a/src/bootchart/store.h b/src/bootchart/store.h deleted file mode 100644 index 6e9acf2a6f..0000000000 --- a/src/bootchart/store.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright (C) 2009-2013 Intel Corporation - - Authors: - Auke Kok - - 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 . -***/ - -#include - -#include "bootchart.h" - -double gettime_ns(void); -void log_uptime(void); -int log_sample(DIR *proc, - int sample, - struct ps_struct *ps_first, - struct list_sample_data **ptr, - int *pscount, - int *cpus); diff --git a/src/bootchart/svg.c b/src/bootchart/svg.c deleted file mode 100644 index f2af535061..0000000000 --- a/src/bootchart/svg.c +++ /dev/null @@ -1,1375 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2009-2013 Intel Corporation - - Authors: - Auke Kok - - 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 . - ***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "architecture.h" -#include "bootchart.h" -#include "fd-util.h" -#include "fileio.h" -#include "list.h" -#include "macro.h" -#include "stdio-util.h" -#include "store.h" -#include "svg.h" -#include "utf8.h" -#include "util.h" - -#define time_to_graph(t) ((t) * arg_scale_x) -#define ps_to_graph(n) ((n) * arg_scale_y) -#define kb_to_graph(m) ((m) * arg_scale_y * 0.0001) -#define to_color(n) (192.0 - ((n) * 192.0)) - -static const char * const colorwheel[12] = { - "rgb(255,32,32)", // red - "rgb(32,192,192)", // cyan - "rgb(255,128,32)", // orange - "rgb(128,32,192)", // blue-violet - "rgb(255,255,32)", // yellow - "rgb(192,32,128)", // red-violet - "rgb(32,255,32)", // green - "rgb(255,64,32)", // red-orange - "rgb(32,32,255)", // blue - "rgb(255,192,32)", // yellow-orange - "rgb(192,32,192)", // violet - "rgb(32,192,32)" // yellow-green -}; - -static double idletime = -1.0; -static int pfiltered = 0; -static int pcount = 0; -static int kcount = 0; -static double psize = 0; -static double ksize = 0; -static double esize = 0; -static struct list_sample_data *sampledata; -static struct list_sample_data *prev_sampledata; - -static void svg_header(FILE *of, struct list_sample_data *head, double graph_start, int n_cpus) { - double w; - double h; - struct list_sample_data *sampledata_last; - - assert(head); - - sampledata_last = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - sampledata_last = sampledata; - } - - /* min width is about 1600px due to the label */ - w = 150.0 + 10.0 + time_to_graph(sampledata_last->sampletime - graph_start); - w = ((w < 1600.0) ? 1600.0 : w); - - /* height is variable based on pss, psize, ksize */ - h = 400.0 + (arg_scale_y * 30.0) /* base graphs and title */ - + (arg_pss ? (100.0 * arg_scale_y) + (arg_scale_y * 7.0) : 0.0) /* pss estimate */ - + psize + ksize + esize + (n_cpus * 15 * arg_scale_y); - - fprintf(of, "\n"); - fprintf(of, "\n"); - - //fprintf(of, "\n", 1000 + 150 + (pcount * 20)); - fprintf(of, "\n\n"); - - /* write some basic info as a comment, including some help */ - fprintf(of, "\n"); - fprintf(of, "\n"); - fprintf(of, "\n"); - fprintf(of, "\n"); - fprintf(of, "\n\n"); - - fprintf(of, "\n", VERSION); - fprintf(of, "\n", arg_hz, arg_samples_len); - fprintf(of, "\n", arg_scale_x, arg_scale_y); - fprintf(of, "\n", arg_relative, arg_filter); - fprintf(of, "\n", arg_pss, arg_entropy); - fprintf(of, "\n\n", arg_output_path, arg_init_path); - - /* style sheet */ - fprintf(of, "\n \n\n\n"); -} - -static int svg_title(FILE *of, const char *build, int pscount, double log_start, int overrun) { - _cleanup_free_ char *cmdline = NULL; - _cleanup_free_ char *model = NULL; - _cleanup_free_ char *buf = NULL; - char date[256] = "Unknown"; - const char *cpu; - char *c; - time_t t; - int r; - struct utsname uts; - - r = read_one_line_file("/proc/cmdline", &cmdline); - if (r < 0) { - log_error_errno(r, "Unable to read cmdline: %m"); - return r; - } - - /* extract root fs so we can find disk model name in sysfs */ - /* FIXME: this works only in the simple case */ - c = strstr(cmdline, "root=/dev/"); - if (c) { - char rootbdev[4]; - char filename[32]; - - strncpy(rootbdev, &c[10], sizeof(rootbdev) - 1); - rootbdev[3] = '\0'; - xsprintf(filename, "/sys/block/%s/device/model", rootbdev); - - r = read_one_line_file(filename, &model); - if (r < 0) - log_info("Error reading disk model for %s: %m\n", rootbdev); - } - - /* various utsname parameters */ - r = uname(&uts); - if (r < 0) { - log_error("Error getting uname info\n"); - return -errno; - } - - /* date */ - t = time(NULL); - r = strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", localtime(&t)); - assert_se(r > 0); - - /* CPU type */ - r = get_proc_field("/proc/cpuinfo", PROC_CPUINFO_MODEL, "\n", &buf); - if (r < 0) - cpu = "Unknown"; - else - cpu = buf; - - fprintf(of, "Bootchart for %s - %s\n", - uts.nodename, date); - fprintf(of, "System: %s %s %s %s\n", - uts.sysname, uts.release, uts.version, uts.machine); - fprintf(of, "CPU: %s\n", cpu); - if (model) - fprintf(of, "Disk: %s\n", model); - fprintf(of, "Boot options: %s\n", cmdline); - fprintf(of, "Build: %s\n", build); - fprintf(of, "Log start time: %.03fs\n", log_start); - fprintf(of, "Idle time: "); - - if (idletime >= 0.0) - fprintf(of, "%.03fs", idletime); - else - fprintf(of, "Not detected"); - - fprintf(of, "\n"); - fprintf(of, "Graph data: %.03f samples/sec, recorded %i total, dropped %i samples, %i processes, %i filtered\n", - arg_hz, arg_samples_len, overrun, pscount, pfiltered); - - return 0; -} - -static void svg_graph_box(FILE *of, struct list_sample_data *head, int height, double graph_start) { - double d = 0.0; - int i = 0; - double finalsample = 0.0; - struct list_sample_data *sampledata_last; - - sampledata_last = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - sampledata_last = sampledata; - } - - finalsample = sampledata_last->sampletime; - - /* outside box, fill */ - fprintf(of, "\n", - time_to_graph(0.0), - time_to_graph(finalsample - graph_start), - ps_to_graph(height)); - - for (d = graph_start; d <= finalsample; - d += (arg_scale_x < 2.0 ? 60.0 : arg_scale_x < 10.0 ? 1.0 : 0.1)) { - /* lines for each second */ - if (i % 50 == 0) - fprintf(of, " \n", - time_to_graph(d - graph_start), - time_to_graph(d - graph_start), - ps_to_graph(height)); - else if (i % 10 == 0) - fprintf(of, " \n", - time_to_graph(d - graph_start), - time_to_graph(d - graph_start), - ps_to_graph(height)); - else - fprintf(of, " \n", - time_to_graph(d - graph_start), - time_to_graph(d - graph_start), - ps_to_graph(height)); - - /* time label */ - if (i % 10 == 0) - fprintf(of, " %.01fs\n", - time_to_graph(d - graph_start), - -5.0, d - graph_start); - - i++; - } -} - -/* xml comments must not contain "--" */ -static char* xml_comment_encode(const char* name) { - char *enc_name, *p; - - enc_name = strdup(name); - if (!enc_name) - return NULL; - - for (p = enc_name; *p; p++) - if (p[0] == '-' && p[1] == '-') - p[1] = '_'; - - return enc_name; -} - -static void svg_pss_graph(FILE *of, - struct list_sample_data *head, - struct ps_struct *ps_first, - double graph_start) { - struct ps_struct *ps; - int i; - struct list_sample_data *sampledata_last; - - sampledata_last = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - sampledata_last = sampledata; - } - - - fprintf(of, "\n\n\n"); - - fprintf(of, "\n Memory allocation - Pss\n"); - - /* vsize 1000 == 1000mb */ - svg_graph_box(of, head, 100, graph_start); - /* draw some hlines for usable memory sizes */ - for (i = 100000; i < 1000000; i += 100000) { - fprintf(of, " \n", - time_to_graph(.0), - kb_to_graph(i), - time_to_graph(sampledata_last->sampletime - graph_start), - kb_to_graph(i)); - fprintf(of, " %dM\n", - time_to_graph(sampledata_last->sampletime - graph_start) + 5, - kb_to_graph(i), (1000000 - i) / 1000); - } - fprintf(of, "\n"); - - /* now plot the graph itself */ - i = 1; - prev_sampledata = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int bottom; - int top; - struct ps_sched_struct *cross_place; - - bottom = 0; - top = 0; - - /* put all the small pss blocks into the bottom */ - ps = ps_first; - while (ps->next_ps) { - ps = ps->next_ps; - if (!ps) - continue; - ps->sample = ps->first; - while (ps->sample->next) { - ps->sample = ps->sample->next; - if (ps->sample->sampledata == sampledata) - break; - } - if (ps->sample->sampledata == sampledata) { - if (ps->sample->pss <= (100 * arg_scale_y)) - top += ps->sample->pss; - break; - } - } - while (ps->sample->cross) { - cross_place = ps->sample->cross; - ps = ps->sample->cross->ps_new; - ps->sample = cross_place; - if (ps->sample->pss <= (100 * arg_scale_y)) - top += ps->sample->pss; - } - - fprintf(of, " \n", - "rgb(64,64,64)", - time_to_graph(prev_sampledata->sampletime - graph_start), - kb_to_graph(1000000.0 - top), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - kb_to_graph(top - bottom)); - bottom = top; - - /* now plot the ones that are of significant size */ - ps = ps_first; - while (ps->next_ps) { - ps = ps->next_ps; - if (!ps) - continue; - ps->sample = ps->first; - while (ps->sample->next) { - ps->sample = ps->sample->next; - if (ps->sample->sampledata == sampledata) - break; - } - /* don't draw anything smaller than 2mb */ - if (ps->sample->sampledata != sampledata) - continue; - if (ps->sample->pss > (100 * arg_scale_y)) { - top = bottom + ps->sample->pss; - fprintf(of, " \n", - colorwheel[ps->pid % 12], - time_to_graph(prev_sampledata->sampletime - graph_start), - kb_to_graph(1000000.0 - top), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - kb_to_graph(top - bottom)); - bottom = top; - } - break; - } - - while ((cross_place = ps->sample->cross)) { - ps = ps->sample->cross->ps_new; - ps->sample = cross_place; - if (ps->sample->pss > (100 * arg_scale_y)) { - top = bottom + ps->sample->pss; - fprintf(of, " \n", - colorwheel[ps->pid % 12], - time_to_graph(prev_sampledata->sampletime - graph_start), - kb_to_graph(1000000.0 - top), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - kb_to_graph(top - bottom)); - bottom = top; - } - } - - prev_sampledata = sampledata; - i++; - } - - /* overlay all the text labels */ - i = 1; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int bottom; - int top = 0; - struct ps_sched_struct *prev_sample; - struct ps_sched_struct *cross_place; - - /* put all the small pss blocks into the bottom */ - ps = ps_first->next_ps; - while (ps->next_ps) { - ps = ps->next_ps; - if (!ps) - continue; - - ps->sample = ps->first; - while (ps->sample->next) { - ps->sample = ps->sample->next; - if (ps->sample->sampledata == sampledata) - break; - } - - if (ps->sample->sampledata == sampledata) { - if (ps->sample->pss <= (100 * arg_scale_y)) - top += ps->sample->pss; - - break; - } - } - - while ((cross_place = ps->sample->cross)) { - ps = ps->sample->cross->ps_new; - ps->sample = cross_place; - if (ps->sample->pss <= (100 * arg_scale_y)) - top += ps->sample->pss; - } - bottom = top; - - /* now plot the ones that are of significant size */ - ps = ps_first; - while (ps->next_ps) { - prev_sample = ps->sample; - ps = ps->next_ps; - if (!ps) - continue; - ps->sample = ps->first; - while (ps->sample->next) { - prev_sample = ps->sample; - ps->sample = ps->sample->next; - if (ps->sample->sampledata == sampledata) - break; - } - /* don't draw anything smaller than 2mb */ - if (ps->sample->sampledata == sampledata) { - if (ps->sample->pss > (100 * arg_scale_y)) { - top = bottom + ps->sample->pss; - /* draw a label with the process / PID */ - if ((i == 1) || (prev_sample->pss <= (100 * arg_scale_y))) - fprintf(of, " %s [%i]\n", - time_to_graph(sampledata->sampletime - graph_start), - kb_to_graph(1000000.0 - bottom - ((top - bottom) / 2)), - ps->name, ps->pid); - bottom = top; - } - break; - } - } - while ((cross_place = ps->sample->cross)) { - ps = ps->sample->cross->ps_new; - ps->sample = cross_place; - prev_sample = ps->sample->prev; - if (ps->sample->pss > (100 * arg_scale_y)) { - top = bottom + ps->sample->pss; - /* draw a label with the process / PID */ - if ((i == 1) || (prev_sample->pss <= (100 * arg_scale_y))) - fprintf(of, " %s [%i]\n", - time_to_graph(sampledata->sampletime - graph_start), - kb_to_graph(1000000.0 - bottom - ((top - bottom) / 2)), - ps->name, ps->pid); - bottom = top; - } - } - - i++; - } - - /* debug output - full data dump */ - fprintf(of, "\n\n\n"); - ps = ps_first; - while (ps->next_ps) { - _cleanup_free_ char *enc_name = NULL; - ps = ps->next_ps; - if (!ps) - continue; - - enc_name = xml_comment_encode(ps->name); - if (!enc_name) - continue; - - fprintf(of, "\n"); - } - -} - -static void svg_io_bi_bar(FILE *of, - struct list_sample_data *head, - int n_samples, - double graph_start, - double interval) { - - double max = 0.0; - double range; - int max_here = 0; - int i; - int k; - struct list_sample_data *start_sampledata; - struct list_sample_data *stop_sampledata; - - fprintf(of, "\n"); - fprintf(of, "IO utilization - read\n"); - - /* - * calculate rounding range - * - * We need to round IO data since IO block data is not updated on - * each poll. Applying a smoothing function loses some burst data, - * so keep the smoothing range short. - */ - range = 0.25 / (1.0 / arg_hz); - if (range < 2.0) - range = 2.0; /* no smoothing */ - - /* surrounding box */ - svg_graph_box(of, head, 5, graph_start); - - /* find the max IO first */ - i = 1; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int start; - int stop; - int diff; - double tot; - - start = MAX(i - ((range / 2) - 1), 0); - stop = MIN(i + (range / 2), n_samples - 1); - diff = (stop - start); - - start_sampledata = sampledata; - stop_sampledata = sampledata; - - for (k = 0; k < ((range/2) - 1) && start_sampledata->link_next; k++) - start_sampledata = start_sampledata->link_next; - - for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) - stop_sampledata = stop_sampledata->link_prev; - - tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi) / diff; - - if (tot > max) { - max = tot; - max_here = i; - } - - tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo) / diff; - - if (tot > max) - max = tot; - - i++; - } - - /* plot bi */ - i = 1; - prev_sampledata = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int start; - int stop; - int diff; - double tot; - double pbi = 0; - - start = MAX(i - ((range / 2) - 1), 0); - stop = MIN(i + (range / 2), n_samples); - diff = (stop - start); - - start_sampledata = sampledata; - stop_sampledata = sampledata; - - for (k = 0; k < ((range/2)-1) && start_sampledata->link_next; k++) - start_sampledata = start_sampledata->link_next; - - for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) - stop_sampledata = stop_sampledata->link_prev; - - tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi) / diff; - - if (max > 0) - pbi = tot / max; - - if (pbi > 0.001) - fprintf(of, "\n", - time_to_graph(prev_sampledata->sampletime - graph_start), - (arg_scale_y * 5) - (pbi * (arg_scale_y * 5)), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - pbi * (arg_scale_y * 5)); - - /* labels around highest value */ - if (i == max_here) - fprintf(of, " %0.2fmb/sec\n", - time_to_graph(sampledata->sampletime - graph_start) + 5, - ((arg_scale_y * 5) - (pbi * (arg_scale_y * 5))) + 15, - max / 1024.0 / (interval / 1000000000.0)); - - i++; - prev_sampledata = sampledata; - } -} - -static void svg_io_bo_bar(FILE *of, - struct list_sample_data *head, - int n_samples, - double graph_start, - double interval) { - double max = 0.0; - double range; - int max_here = 0; - int i; - int k; - struct list_sample_data *start_sampledata; - struct list_sample_data *stop_sampledata; - - fprintf(of, "\n"); - fprintf(of, "IO utilization - write\n"); - - /* - * calculate rounding range - * - * We need to round IO data since IO block data is not updated on - * each poll. Applying a smoothing function loses some burst data, - * so keep the smoothing range short. - */ - range = 0.25 / (1.0 / arg_hz); - if (range < 2.0) - range = 2.0; /* no smoothing */ - - /* surrounding box */ - svg_graph_box(of, head, 5, graph_start); - - /* find the max IO first */ - i = 0; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int start; - int stop; - int diff; - double tot; - - start = MAX(i - ((range / 2) - 1), 0); - stop = MIN(i + (range / 2), n_samples - 1); - diff = (stop - start); - - start_sampledata = sampledata; - stop_sampledata = sampledata; - - for (k = 0; k < (range/2) - 1 && start_sampledata->link_next; k++) - start_sampledata = start_sampledata->link_next; - - for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) - stop_sampledata = stop_sampledata->link_prev; - - tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi) / diff; - if (tot > max) - max = tot; - - tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo) / diff; - if (tot > max) { - max = tot; - max_here = i; - } - - i++; - } - - /* plot bo */ - prev_sampledata = head; - i = 1; - - LIST_FOREACH_BEFORE(link, sampledata, head) { - int start, stop, diff; - double tot, pbo; - - pbo = 0; - - start = MAX(i - ((range / 2) - 1), 0); - stop = MIN(i + (range / 2), n_samples); - diff = (stop - start); - - start_sampledata = sampledata; - stop_sampledata = sampledata; - - for (k = 0; k < ((range/2)-1) && start_sampledata->link_next; k++) - start_sampledata = start_sampledata->link_next; - - for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) - stop_sampledata = stop_sampledata->link_prev; - - tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo) - / diff; - - if (max > 0) - pbo = tot / max; - - if (pbo > 0.001) - fprintf(of, "\n", - time_to_graph(prev_sampledata->sampletime - graph_start), - (arg_scale_y * 5) - (pbo * (arg_scale_y * 5)), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - pbo * (arg_scale_y * 5)); - - /* labels around highest bo value */ - if (i == max_here) - fprintf(of, " %0.2fmb/sec\n", - time_to_graph(sampledata->sampletime - graph_start) + 5, - ((arg_scale_y * 5) - (pbo * (arg_scale_y * 5))), - max / 1024.0 / (interval / 1000000000.0)); - - i++; - prev_sampledata = sampledata; - } -} - -static void svg_cpu_bar(FILE *of, struct list_sample_data *head, int n_cpus, int cpu_num, double graph_start) { - - fprintf(of, "\n"); - - if (cpu_num < 0) - fprintf(of, "CPU[overall] utilization\n"); - else - fprintf(of, "CPU[%d] utilization\n", cpu_num); - - /* surrounding box */ - svg_graph_box(of, head, 5, graph_start); - - /* bars for each sample, proportional to the CPU util. */ - prev_sampledata = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int c; - double trt; - double ptrt; - - ptrt = trt = 0.0; - - if (cpu_num < 0) - for (c = 0; c < n_cpus; c++) - trt += sampledata->runtime[c] - prev_sampledata->runtime[c]; - else - trt = sampledata->runtime[cpu_num] - prev_sampledata->runtime[cpu_num]; - - trt = trt / 1000000000.0; - - if (cpu_num < 0) - trt = trt / (double)n_cpus; - - if (trt > 0.0) - ptrt = trt / (sampledata->sampletime - prev_sampledata->sampletime); - - if (ptrt > 1.0) - ptrt = 1.0; - - if (ptrt > 0.001) - fprintf(of, "\n", - time_to_graph(prev_sampledata->sampletime - graph_start), - (arg_scale_y * 5) - (ptrt * (arg_scale_y * 5)), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - ptrt * (arg_scale_y * 5)); - - prev_sampledata = sampledata; - } -} - -static void svg_wait_bar(FILE *of, struct list_sample_data *head, int n_cpus, int cpu_num, double graph_start) { - - fprintf(of, "\n"); - - if (cpu_num < 0) - fprintf(of, "CPU[overall] wait\n"); - else - fprintf(of, "CPU[%d] wait\n", cpu_num); - - /* surrounding box */ - svg_graph_box(of, head, 5, graph_start); - - /* bars for each sample, proportional to the CPU util. */ - prev_sampledata = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int c; - double twt; - double ptwt; - - ptwt = twt = 0.0; - - if (cpu_num < 0) - for (c = 0; c < n_cpus; c++) - twt += sampledata->waittime[c] - prev_sampledata->waittime[c]; - else - twt = sampledata->waittime[cpu_num] - prev_sampledata->waittime[cpu_num]; - - twt = twt / 1000000000.0; - - if (cpu_num < 0) - twt = twt / (double)n_cpus; - - if (twt > 0.0) - ptwt = twt / (sampledata->sampletime - prev_sampledata->sampletime); - - if (ptwt > 1.0) - ptwt = 1.0; - - if (ptwt > 0.001) - fprintf(of, "\n", - time_to_graph(prev_sampledata->sampletime - graph_start), - ((arg_scale_y * 5) - (ptwt * (arg_scale_y * 5))), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - ptwt * (arg_scale_y * 5)); - - prev_sampledata = sampledata; - } -} - -static void svg_entropy_bar(FILE *of, struct list_sample_data *head, double graph_start) { - - fprintf(of, "\n"); - - fprintf(of, "Entropy pool size\n"); - /* surrounding box */ - svg_graph_box(of, head, 5, graph_start); - - /* bars for each sample, scale 0-4096 */ - prev_sampledata = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - fprintf(of, "\n", - time_to_graph(prev_sampledata->sampletime - graph_start), - ((arg_scale_y * 5) - ((sampledata->entropy_avail / 4096.) * (arg_scale_y * 5))), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - (sampledata->entropy_avail / 4096.) * (arg_scale_y * 5)); - prev_sampledata = sampledata; - } -} - -static struct ps_struct *get_next_ps(struct ps_struct *ps, struct ps_struct *ps_first) { - /* - * walk the list of processes and return the next one to be - * painted - */ - if (ps == ps_first) - return ps->next_ps; - - /* go deep */ - if (ps->children) - return ps->children; - - /* find siblings */ - if (ps->next) - return ps->next; - - /* go back for parent siblings */ - for (;;) { - if (ps->parent && ps->parent->next) - return ps->parent->next; - - ps = ps->parent; - if (!ps) - return ps; - } - - return NULL; -} - -static bool ps_filter(struct ps_struct *ps) { - if (!arg_filter) - return false; - - /* can't draw data when there is only 1 sample (need start + stop) */ - if (ps->first == ps->last) - return true; - - /* don't filter kthreadd */ - if (ps->pid == 2) - return false; - - /* drop stuff that doesn't use any real CPU time */ - if (ps->total <= 0.001) - return true; - - return 0; -} - -static void svg_do_initcall(FILE *of, struct list_sample_data *head, int count_only, double graph_start) { - _cleanup_pclose_ FILE *f = NULL; - double t; - char func[256]; - int ret; - int usecs; - - /* can't plot initcall when disabled or in relative mode */ - if (!arg_initcall || arg_relative) { - kcount = 0; - return; - } - - if (!count_only) { - fprintf(of, "\n"); - fprintf(of, "Kernel init threads\n"); - /* surrounding box */ - svg_graph_box(of, head, kcount, graph_start); - } - - kcount = 0; - - /* - * Initcall graphing - parses dmesg buffer and displays kernel threads - * This somewhat uses the same methods and scaling to show processes - * but looks a lot simpler. It's overlaid entirely onto the PS graph - * when appropriate. - */ - - f = popen("dmesg", "r"); - if (!f) - return; - - while (!feof(f)) { - int c; - int z = 0; - char l[256]; - - if (fgets(l, sizeof(l) - 1, f) == NULL) - continue; - - c = sscanf(l, "[%lf] initcall %s %*s %d %*s %d %*s", - &t, func, &ret, &usecs); - if (c != 4) { - /* also parse initcalls done by module loading */ - c = sscanf(l, "[%lf] initcall %s %*s %*s %d %*s %d %*s", - &t, func, &ret, &usecs); - if (c != 4) - continue; - } - - /* chop the +0xXX/0xXX stuff */ - while(func[z] != '+') - z++; - func[z] = 0; - - if (count_only) { - /* filter out irrelevant stuff */ - if (usecs >= 1000) - kcount++; - continue; - } - - fprintf(of, "\n", - func, t, usecs, ret); - - if (usecs < 1000) - continue; - - /* rect */ - fprintf(of, " \n", - time_to_graph(t - (usecs / 1000000.0)), - ps_to_graph(kcount), - time_to_graph(usecs / 1000000.0), - ps_to_graph(1)); - - /* label */ - fprintf(of, " %s %.03fs\n", - time_to_graph(t - (usecs / 1000000.0)) + 5, - ps_to_graph(kcount) + 15, - func, usecs / 1000000.0); - - kcount++; - } -} - -static void svg_ps_bars(FILE *of, - struct list_sample_data *head, - int n_samples, - int n_cpus, - struct ps_struct *ps_first, - double graph_start, - double interval) { - - struct ps_struct *ps; - int i = 0; - int j = 0; - int pid; - double w = 0.0; - - fprintf(of, "\n"); - fprintf(of, "Processes\n"); - - /* surrounding box */ - svg_graph_box(of, head, pcount, graph_start); - - /* pass 2 - ps boxes */ - ps = ps_first; - while ((ps = get_next_ps(ps, ps_first))) { - _cleanup_free_ char *enc_name = NULL, *escaped = NULL; - double endtime; - double starttime; - int t; - - if (!utf8_is_printable(ps->name, strlen(ps->name))) - escaped = utf8_escape_non_printable(ps->name); - - enc_name = xml_comment_encode(escaped ? escaped : ps->name); - if (!enc_name) - continue; - - /* leave some trace of what we actually filtered etc. */ - fprintf(of, "\n", enc_name, ps->pid, - ps->ppid, ps->total); - - starttime = ps->first->sampledata->sampletime; - - if (!ps_filter(ps)) { - /* remember where _to_ our children need to draw a line */ - ps->pos_x = time_to_graph(starttime - graph_start); - ps->pos_y = ps_to_graph(j+1); /* bottom left corner */ - } else if (ps->parent){ - /* hook children to our parent coords instead */ - ps->pos_x = ps->parent->pos_x; - ps->pos_y = ps->parent->pos_y; - - /* if this is the last child, we might still need to draw a connecting line */ - if ((!ps->next) && (ps->parent)) - fprintf(of, " \n", - ps->parent->pos_x, - ps_to_graph(j-1) + 10.0, /* whee, use the last value here */ - ps->parent->pos_x, - ps->parent->pos_y); - continue; - } - - endtime = ps->last->sampledata->sampletime; - fprintf(of, " \n", - time_to_graph(starttime - graph_start), - ps_to_graph(j), - time_to_graph(ps->last->sampledata->sampletime - starttime), - ps_to_graph(1)); - - /* paint cpu load over these */ - ps->sample = ps->first; - t = 1; - while (ps->sample->next) { - double rt, prt; - double wt, wrt; - struct ps_sched_struct *prev; - - prev = ps->sample; - ps->sample = ps->sample->next; - - /* calculate over interval */ - rt = ps->sample->runtime - prev->runtime; - wt = ps->sample->waittime - prev->waittime; - - prt = (rt / 1000000000) / (ps->sample->sampledata->sampletime - prev->sampledata->sampletime); - wrt = (wt / 1000000000) / (ps->sample->sampledata->sampletime - prev->sampledata->sampletime); - - /* this can happen if timekeeping isn't accurate enough */ - if (prt > 1.0) - prt = 1.0; - if (wrt > 1.0) - wrt = 1.0; - - if ((prt < 0.1) && (wrt < 0.1)) /* =~ 26 (color threshold) */ - continue; - - fprintf(of, " \n", - time_to_graph(prev->sampledata->sampletime - graph_start), - ps_to_graph(j), - time_to_graph(ps->sample->sampledata->sampletime - prev->sampledata->sampletime), - ps_to_graph(wrt)); - - /* draw cpu over wait - TODO figure out how/why run + wait > interval */ - fprintf(of, " \n", - time_to_graph(prev->sampledata->sampletime - graph_start), - ps_to_graph(j + (1.0 - prt)), - time_to_graph(ps->sample->sampledata->sampletime - prev->sampledata->sampletime), - ps_to_graph(prt)); - t++; - } - - /* determine where to display the process name */ - if ((endtime - starttime) < 1.5) - /* too small to fit label inside the box */ - w = endtime; - else - w = starttime; - - /* text label of process name */ - fprintf(of, " %s [%i]%.03fs %s\n", - time_to_graph(w - graph_start) + 5.0, - ps_to_graph(j) + 14.0, - escaped ? escaped : ps->name, - ps->pid, - (ps->last->runtime - ps->first->runtime) / 1000000000.0, - arg_show_cgroup ? ps->cgroup : ""); - /* paint lines to the parent process */ - if (ps->parent) { - /* horizontal part */ - fprintf(of, " \n", - time_to_graph(starttime - graph_start), - ps_to_graph(j) + 10.0, - ps->parent->pos_x, - ps_to_graph(j) + 10.0); - - /* one vertical line connecting all the horizontal ones up */ - if (!ps->next) - fprintf(of, " \n", - ps->parent->pos_x, - ps_to_graph(j) + 10.0, - ps->parent->pos_x, - ps->parent->pos_y); - } - - j++; /* count boxes */ - - fprintf(of, "\n"); - } - - /* last pass - determine when idle */ - pid = getpid(); - /* make sure we start counting from the point where we actually have - * data: assume that bootchart's first sample is when data started - */ - - ps = ps_first; - while (ps->next_ps) { - ps = ps->next_ps; - if (ps->pid == pid) - break; - } - - /* need to know last node first */ - ps->sample = ps->first; - i = ps->sample->next->sampledata->counter; - - while (ps->sample->next && i<(n_samples-(arg_hz/2))) { - double crt; - double brt; - int c; - int ii; - struct ps_sched_struct *sample_hz; - - ps->sample = ps->sample->next; - sample_hz = ps->sample; - for (ii = 0; (ii < (int)arg_hz/2) && sample_hz->next; ii++) - sample_hz = sample_hz->next; - - /* subtract bootchart cpu utilization from total */ - crt = 0.0; - for (c = 0; c < n_cpus; c++) - crt += sample_hz->sampledata->runtime[c] - ps->sample->sampledata->runtime[c]; - - brt = sample_hz->runtime - ps->sample->runtime; - /* - * our definition of "idle": - * - * if for (hz / 2) we've used less CPU than (interval / 2) ... - * defaults to 4.0%, which experimentally, is where atom idles - */ - if ((crt - brt) < (interval / 2.0)) { - idletime = ps->sample->sampledata->sampletime - graph_start; - fprintf(of, "\n\n", idletime); - fprintf(of, "\n", - time_to_graph(idletime), - -arg_scale_y, - time_to_graph(idletime), - ps_to_graph(pcount) + arg_scale_y); - fprintf(of, "%.01fs\n", - time_to_graph(idletime) + 5.0, - ps_to_graph(pcount) + arg_scale_y, - idletime); - break; - } - - i++; - } -} - -static void svg_top_ten_cpu(FILE *of, struct ps_struct *ps_first) { - struct ps_struct *top[10]; - struct ps_struct emptyps = {}; - struct ps_struct *ps; - int n, m; - - for (n = 0; n < (int) ELEMENTSOF(top); n++) - top[n] = &emptyps; - - /* walk all ps's and setup ptrs */ - ps = ps_first; - while ((ps = get_next_ps(ps, ps_first))) { - for (n = 0; n < 10; n++) { - if (ps->total <= top[n]->total) - continue; - /* cascade insert */ - for (m = 9; m > n; m--) - top[m] = top[m-1]; - top[n] = ps; - break; - } - } - - fprintf(of, "Top CPU consumers:\n"); - for (n = 0; n < 10; n++) - fprintf(of, "%3.03fs - %s [%d]\n", - 20 + (n * 13), - top[n]->total, - top[n]->name, - top[n]->pid); -} - -static void svg_top_ten_pss(FILE *of, struct ps_struct *ps_first) { - struct ps_struct *top[10]; - struct ps_struct emptyps = {}; - struct ps_struct *ps; - int n, m; - - for (n = 0; n < (int) ELEMENTSOF(top); n++) - top[n] = &emptyps; - - /* walk all ps's and setup ptrs */ - ps = ps_first; - while ((ps = get_next_ps(ps, ps_first))) { - for (n = 0; n < 10; n++) { - if (ps->pss_max <= top[n]->pss_max) - continue; - - /* cascade insert */ - for (m = 9; m > n; m--) - top[m] = top[m-1]; - top[n] = ps; - break; - } - } - - fprintf(of, "Top PSS consumers:\n"); - for (n = 0; n < 10; n++) - fprintf(of, "%dK - %s [%d]\n", - 20 + (n * 13), - top[n]->pss_max, - top[n]->name, - top[n]->pid); -} - -int svg_do(FILE *of, - const char *build, - struct list_sample_data *head, - struct ps_struct *ps_first, - int n_samples, - int pscount, - int n_cpus, - double graph_start, - double log_start, - double interval, - int overrun) { - - struct ps_struct *ps; - double offset = 7; - int r, c; - - sampledata = head; - LIST_FIND_TAIL(link, sampledata, head); - ps = ps_first; - - /* count initcall thread count first */ - svg_do_initcall(of, head, 1, graph_start); - ksize = kcount ? ps_to_graph(kcount) + (arg_scale_y * 2) : 0; - - /* then count processes */ - while ((ps = get_next_ps(ps, ps_first))) { - if (!ps_filter(ps)) - pcount++; - else - pfiltered++; - } - psize = ps_to_graph(pcount) + (arg_scale_y * 2); - - esize = (arg_entropy ? arg_scale_y * 7 : 0); - - /* after this, we can draw the header with proper sizing */ - svg_header(of, head, graph_start, arg_percpu ? n_cpus : 0); - fprintf(of, "\n\n"); - - fprintf(of, "\n"); - svg_io_bi_bar(of, head, n_samples, graph_start, interval); - fprintf(of, "\n\n"); - - fprintf(of, "\n", 400.0 + (arg_scale_y * offset)); - svg_io_bo_bar(of, head, n_samples, graph_start, interval); - fprintf(of, "\n\n"); - - for (c = -1; c < (arg_percpu ? n_cpus : 0); c++) { - offset += 7; - fprintf(of, "\n", 400.0 + (arg_scale_y * offset)); - svg_cpu_bar(of, head, n_cpus, c, graph_start); - fprintf(of, "\n\n"); - - offset += 7; - fprintf(of, "\n", 400.0 + (arg_scale_y * offset)); - svg_wait_bar(of, head, n_cpus, c, graph_start); - fprintf(of, "\n\n"); - } - - if (kcount) { - offset += 7; - fprintf(of, "\n", 400.0 + (arg_scale_y * offset)); - svg_do_initcall(of, head, 0, graph_start); - fprintf(of, "\n\n"); - } - - offset += 7; - fprintf(of, "\n", 400.0 + (arg_scale_y * offset) + ksize); - svg_ps_bars(of, head, n_samples, n_cpus, ps_first, graph_start, interval); - fprintf(of, "\n\n"); - - fprintf(of, "\n"); - r = svg_title(of, build, pscount, log_start, overrun); - fprintf(of, "\n\n"); - - if (r < 0) - return r; - - fprintf(of, "\n"); - svg_top_ten_cpu(of, ps_first); - fprintf(of, "\n\n"); - - if (arg_entropy) { - fprintf(of, "\n", 400.0 + (arg_scale_y * offset) + ksize + psize); - svg_entropy_bar(of, head, graph_start); - fprintf(of, "\n\n"); - } - - if (arg_pss) { - fprintf(of, "\n", 400.0 + (arg_scale_y * offset) + ksize + psize + esize); - svg_pss_graph(of, head, ps_first, graph_start); - fprintf(of, "\n\n"); - - fprintf(of, "\n"); - svg_top_ten_pss(of, ps_first); - fprintf(of, "\n\n"); - } - - /* fprintf footer */ - fprintf(of, "\n\n"); - - return 0; -} diff --git a/src/bootchart/svg.h b/src/bootchart/svg.h deleted file mode 100644 index 6e06b5ad97..0000000000 --- a/src/bootchart/svg.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright (C) 2009-2013 Intel Corporation - - Authors: - Auke Kok - - 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 . -***/ - -int svg_do(FILE *of, - const char *build, - struct list_sample_data *head, - struct ps_struct *ps_first, - int n_samples, - int pscount, - int n_cpus, - double graph_start, - double log_start, - double interval, - int overrun); diff --git a/src/libfirewall/firewall-util.c b/src/libfirewall/firewall-util.c new file mode 100644 index 0000000000..0d3da2e6d2 --- /dev/null +++ b/src/libfirewall/firewall-util.c @@ -0,0 +1,349 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "firewall-util.h" +#include "in-addr-util.h" +#include "macro.h" + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct xtc_handle*, iptc_free); + +static int entry_fill_basics( + struct ipt_entry *entry, + int protocol, + const char *in_interface, + const union in_addr_union *source, + unsigned source_prefixlen, + const char *out_interface, + const union in_addr_union *destination, + unsigned destination_prefixlen) { + + assert(entry); + + if (out_interface && strlen(out_interface) >= IFNAMSIZ) + return -EINVAL; + + if (in_interface && strlen(in_interface) >= IFNAMSIZ) + return -EINVAL; + + entry->ip.proto = protocol; + + if (in_interface) { + strcpy(entry->ip.iniface, in_interface); + memset(entry->ip.iniface_mask, 0xFF, strlen(in_interface)+1); + } + if (source) { + entry->ip.src = source->in; + in_addr_prefixlen_to_netmask(&entry->ip.smsk, source_prefixlen); + } + + if (out_interface) { + strcpy(entry->ip.outiface, out_interface); + memset(entry->ip.outiface_mask, 0xFF, strlen(out_interface)+1); + } + if (destination) { + entry->ip.dst = destination->in; + in_addr_prefixlen_to_netmask(&entry->ip.dmsk, destination_prefixlen); + } + + return 0; +} + +int fw_add_masquerade( + bool add, + int af, + int protocol, + const union in_addr_union *source, + unsigned source_prefixlen, + const char *out_interface, + const union in_addr_union *destination, + unsigned destination_prefixlen) { + + _cleanup_(iptc_freep) struct xtc_handle *h = NULL; + struct ipt_entry *entry, *mask; + struct ipt_entry_target *t; + size_t sz; + struct nf_nat_ipv4_multi_range_compat *mr; + int r; + + if (af != AF_INET) + return -EOPNOTSUPP; + + if (protocol != 0 && protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) + return -EOPNOTSUPP; + + h = iptc_init("nat"); + if (!h) + return -errno; + + sz = XT_ALIGN(sizeof(struct ipt_entry)) + + XT_ALIGN(sizeof(struct ipt_entry_target)) + + XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); + + /* Put together the entry we want to add or remove */ + entry = alloca0(sz); + entry->next_offset = sz; + entry->target_offset = XT_ALIGN(sizeof(struct ipt_entry)); + r = entry_fill_basics(entry, protocol, NULL, source, source_prefixlen, out_interface, destination, destination_prefixlen); + if (r < 0) + return r; + + /* Fill in target part */ + t = ipt_get_target(entry); + t->u.target_size = + XT_ALIGN(sizeof(struct ipt_entry_target)) + + XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); + strncpy(t->u.user.name, "MASQUERADE", sizeof(t->u.user.name)); + mr = (struct nf_nat_ipv4_multi_range_compat*) t->data; + mr->rangesize = 1; + + /* Create a search mask entry */ + mask = alloca(sz); + memset(mask, 0xFF, sz); + + if (add) { + if (iptc_check_entry("POSTROUTING", entry, (unsigned char*) mask, h)) + return 0; + if (errno != ENOENT) /* if other error than not existing yet, fail */ + return -errno; + + if (!iptc_insert_entry("POSTROUTING", entry, 0, h)) + return -errno; + } else { + if (!iptc_delete_entry("POSTROUTING", entry, (unsigned char*) mask, h)) { + if (errno == ENOENT) /* if it's already gone, all is good! */ + return 0; + + return -errno; + } + } + + if (!iptc_commit(h)) + return -errno; + + return 0; +} + +int fw_add_local_dnat( + bool add, + int af, + int protocol, + const char *in_interface, + const union in_addr_union *source, + unsigned source_prefixlen, + const union in_addr_union *destination, + unsigned destination_prefixlen, + uint16_t local_port, + const union in_addr_union *remote, + uint16_t remote_port, + const union in_addr_union *previous_remote) { + + + _cleanup_(iptc_freep) struct xtc_handle *h = NULL; + struct ipt_entry *entry, *mask; + struct ipt_entry_target *t; + struct ipt_entry_match *m; + struct xt_addrtype_info_v1 *at; + struct nf_nat_ipv4_multi_range_compat *mr; + size_t sz, msz; + int r; + + assert(add || !previous_remote); + + if (af != AF_INET) + return -EOPNOTSUPP; + + if (protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) + return -EOPNOTSUPP; + + if (local_port <= 0) + return -EINVAL; + + if (remote_port <= 0) + return -EINVAL; + + h = iptc_init("nat"); + if (!h) + return -errno; + + sz = XT_ALIGN(sizeof(struct ipt_entry)) + + XT_ALIGN(sizeof(struct ipt_entry_match)) + + XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) + + XT_ALIGN(sizeof(struct ipt_entry_target)) + + XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); + + if (protocol == IPPROTO_TCP) + msz = XT_ALIGN(sizeof(struct ipt_entry_match)) + + XT_ALIGN(sizeof(struct xt_tcp)); + else + msz = XT_ALIGN(sizeof(struct ipt_entry_match)) + + XT_ALIGN(sizeof(struct xt_udp)); + + sz += msz; + + /* Fill in basic part */ + entry = alloca0(sz); + entry->next_offset = sz; + entry->target_offset = + XT_ALIGN(sizeof(struct ipt_entry)) + + XT_ALIGN(sizeof(struct ipt_entry_match)) + + XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) + + msz; + r = entry_fill_basics(entry, protocol, in_interface, source, source_prefixlen, NULL, destination, destination_prefixlen); + if (r < 0) + return r; + + /* Fill in first match */ + m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry))); + m->u.match_size = msz; + if (protocol == IPPROTO_TCP) { + struct xt_tcp *tcp; + + strncpy(m->u.user.name, "tcp", sizeof(m->u.user.name)); + tcp = (struct xt_tcp*) m->data; + tcp->dpts[0] = tcp->dpts[1] = local_port; + tcp->spts[0] = 0; + tcp->spts[1] = 0xFFFF; + + } else { + struct xt_udp *udp; + + strncpy(m->u.user.name, "udp", sizeof(m->u.user.name)); + udp = (struct xt_udp*) m->data; + udp->dpts[0] = udp->dpts[1] = local_port; + udp->spts[0] = 0; + udp->spts[1] = 0xFFFF; + } + + /* Fill in second match */ + m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry)) + msz); + m->u.match_size = + XT_ALIGN(sizeof(struct ipt_entry_match)) + + XT_ALIGN(sizeof(struct xt_addrtype_info_v1)); + strncpy(m->u.user.name, "addrtype", sizeof(m->u.user.name)); + m->u.user.revision = 1; + at = (struct xt_addrtype_info_v1*) m->data; + at->dest = XT_ADDRTYPE_LOCAL; + + /* Fill in target part */ + t = ipt_get_target(entry); + t->u.target_size = + XT_ALIGN(sizeof(struct ipt_entry_target)) + + XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); + strncpy(t->u.user.name, "DNAT", sizeof(t->u.user.name)); + mr = (struct nf_nat_ipv4_multi_range_compat*) t->data; + mr->rangesize = 1; + mr->range[0].flags = NF_NAT_RANGE_PROTO_SPECIFIED|NF_NAT_RANGE_MAP_IPS; + mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr; + if (protocol == IPPROTO_TCP) + mr->range[0].min.tcp.port = mr->range[0].max.tcp.port = htons(remote_port); + else + mr->range[0].min.udp.port = mr->range[0].max.udp.port = htons(remote_port); + + mask = alloca0(sz); + memset(mask, 0xFF, sz); + + if (add) { + /* Add the PREROUTING rule, if it is missing so far */ + if (!iptc_check_entry("PREROUTING", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -EINVAL; + + if (!iptc_insert_entry("PREROUTING", entry, 0, h)) + return -errno; + } + + /* If a previous remote is set, remove its entry */ + if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) { + mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr; + + if (!iptc_delete_entry("PREROUTING", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -errno; + } + + mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr; + } + + /* Add the OUTPUT rule, if it is missing so far */ + if (!in_interface) { + + /* Don't apply onto loopback addresses */ + if (!destination) { + entry->ip.dst.s_addr = htobe32(0x7F000000); + entry->ip.dmsk.s_addr = htobe32(0xFF000000); + entry->ip.invflags = IPT_INV_DSTIP; + } + + if (!iptc_check_entry("OUTPUT", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -errno; + + if (!iptc_insert_entry("OUTPUT", entry, 0, h)) + return -errno; + } + + /* If a previous remote is set, remove its entry */ + if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) { + mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr; + + if (!iptc_delete_entry("OUTPUT", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -errno; + } + } + } + } else { + if (!iptc_delete_entry("PREROUTING", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -errno; + } + + if (!in_interface) { + if (!destination) { + entry->ip.dst.s_addr = htobe32(0x7F000000); + entry->ip.dmsk.s_addr = htobe32(0xFF000000); + entry->ip.invflags = IPT_INV_DSTIP; + } + + if (!iptc_delete_entry("OUTPUT", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -errno; + } + } + } + + if (!iptc_commit(h)) + return -errno; + + return 0; +} diff --git a/src/libfirewall/firewall-util.h b/src/libfirewall/firewall-util.h new file mode 100644 index 0000000000..c39b34cf8f --- /dev/null +++ b/src/libfirewall/firewall-util.h @@ -0,0 +1,83 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include + +#include "in-addr-util.h" + +#ifdef HAVE_LIBIPTC + +int fw_add_masquerade( + bool add, + int af, + int protocol, + const union in_addr_union *source, + unsigned source_prefixlen, + const char *out_interface, + const union in_addr_union *destination, + unsigned destination_prefixlen); + +int fw_add_local_dnat( + bool add, + int af, + int protocol, + const char *in_interface, + const union in_addr_union *source, + unsigned source_prefixlen, + const union in_addr_union *destination, + unsigned destination_prefixlen, + uint16_t local_port, + const union in_addr_union *remote, + uint16_t remote_port, + const union in_addr_union *previous_remote); + +#else + +static inline int fw_add_masquerade( + bool add, + int af, + int protocol, + const union in_addr_union *source, + unsigned source_prefixlen, + const char *out_interface, + const union in_addr_union *destination, + unsigned destination_prefixlen) { + return -EOPNOTSUPP; +} + +static inline int fw_add_local_dnat( + bool add, + int af, + int protocol, + const char *in_interface, + const union in_addr_union *source, + unsigned source_prefixlen, + const union in_addr_union *destination, + unsigned destination_prefixlen, + uint16_t local_port, + const union in_addr_union *remote, + uint16_t remote_port, + const union in_addr_union *previous_remote) { + return -EOPNOTSUPP; +} + +#endif diff --git a/src/libshared/firewall-util.c b/src/libshared/firewall-util.c deleted file mode 100644 index 0d3da2e6d2..0000000000 --- a/src/libshared/firewall-util.c +++ /dev/null @@ -1,349 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "firewall-util.h" -#include "in-addr-util.h" -#include "macro.h" - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct xtc_handle*, iptc_free); - -static int entry_fill_basics( - struct ipt_entry *entry, - int protocol, - const char *in_interface, - const union in_addr_union *source, - unsigned source_prefixlen, - const char *out_interface, - const union in_addr_union *destination, - unsigned destination_prefixlen) { - - assert(entry); - - if (out_interface && strlen(out_interface) >= IFNAMSIZ) - return -EINVAL; - - if (in_interface && strlen(in_interface) >= IFNAMSIZ) - return -EINVAL; - - entry->ip.proto = protocol; - - if (in_interface) { - strcpy(entry->ip.iniface, in_interface); - memset(entry->ip.iniface_mask, 0xFF, strlen(in_interface)+1); - } - if (source) { - entry->ip.src = source->in; - in_addr_prefixlen_to_netmask(&entry->ip.smsk, source_prefixlen); - } - - if (out_interface) { - strcpy(entry->ip.outiface, out_interface); - memset(entry->ip.outiface_mask, 0xFF, strlen(out_interface)+1); - } - if (destination) { - entry->ip.dst = destination->in; - in_addr_prefixlen_to_netmask(&entry->ip.dmsk, destination_prefixlen); - } - - return 0; -} - -int fw_add_masquerade( - bool add, - int af, - int protocol, - const union in_addr_union *source, - unsigned source_prefixlen, - const char *out_interface, - const union in_addr_union *destination, - unsigned destination_prefixlen) { - - _cleanup_(iptc_freep) struct xtc_handle *h = NULL; - struct ipt_entry *entry, *mask; - struct ipt_entry_target *t; - size_t sz; - struct nf_nat_ipv4_multi_range_compat *mr; - int r; - - if (af != AF_INET) - return -EOPNOTSUPP; - - if (protocol != 0 && protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) - return -EOPNOTSUPP; - - h = iptc_init("nat"); - if (!h) - return -errno; - - sz = XT_ALIGN(sizeof(struct ipt_entry)) + - XT_ALIGN(sizeof(struct ipt_entry_target)) + - XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); - - /* Put together the entry we want to add or remove */ - entry = alloca0(sz); - entry->next_offset = sz; - entry->target_offset = XT_ALIGN(sizeof(struct ipt_entry)); - r = entry_fill_basics(entry, protocol, NULL, source, source_prefixlen, out_interface, destination, destination_prefixlen); - if (r < 0) - return r; - - /* Fill in target part */ - t = ipt_get_target(entry); - t->u.target_size = - XT_ALIGN(sizeof(struct ipt_entry_target)) + - XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); - strncpy(t->u.user.name, "MASQUERADE", sizeof(t->u.user.name)); - mr = (struct nf_nat_ipv4_multi_range_compat*) t->data; - mr->rangesize = 1; - - /* Create a search mask entry */ - mask = alloca(sz); - memset(mask, 0xFF, sz); - - if (add) { - if (iptc_check_entry("POSTROUTING", entry, (unsigned char*) mask, h)) - return 0; - if (errno != ENOENT) /* if other error than not existing yet, fail */ - return -errno; - - if (!iptc_insert_entry("POSTROUTING", entry, 0, h)) - return -errno; - } else { - if (!iptc_delete_entry("POSTROUTING", entry, (unsigned char*) mask, h)) { - if (errno == ENOENT) /* if it's already gone, all is good! */ - return 0; - - return -errno; - } - } - - if (!iptc_commit(h)) - return -errno; - - return 0; -} - -int fw_add_local_dnat( - bool add, - int af, - int protocol, - const char *in_interface, - const union in_addr_union *source, - unsigned source_prefixlen, - const union in_addr_union *destination, - unsigned destination_prefixlen, - uint16_t local_port, - const union in_addr_union *remote, - uint16_t remote_port, - const union in_addr_union *previous_remote) { - - - _cleanup_(iptc_freep) struct xtc_handle *h = NULL; - struct ipt_entry *entry, *mask; - struct ipt_entry_target *t; - struct ipt_entry_match *m; - struct xt_addrtype_info_v1 *at; - struct nf_nat_ipv4_multi_range_compat *mr; - size_t sz, msz; - int r; - - assert(add || !previous_remote); - - if (af != AF_INET) - return -EOPNOTSUPP; - - if (protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) - return -EOPNOTSUPP; - - if (local_port <= 0) - return -EINVAL; - - if (remote_port <= 0) - return -EINVAL; - - h = iptc_init("nat"); - if (!h) - return -errno; - - sz = XT_ALIGN(sizeof(struct ipt_entry)) + - XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) + - XT_ALIGN(sizeof(struct ipt_entry_target)) + - XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); - - if (protocol == IPPROTO_TCP) - msz = XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_tcp)); - else - msz = XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_udp)); - - sz += msz; - - /* Fill in basic part */ - entry = alloca0(sz); - entry->next_offset = sz; - entry->target_offset = - XT_ALIGN(sizeof(struct ipt_entry)) + - XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) + - msz; - r = entry_fill_basics(entry, protocol, in_interface, source, source_prefixlen, NULL, destination, destination_prefixlen); - if (r < 0) - return r; - - /* Fill in first match */ - m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry))); - m->u.match_size = msz; - if (protocol == IPPROTO_TCP) { - struct xt_tcp *tcp; - - strncpy(m->u.user.name, "tcp", sizeof(m->u.user.name)); - tcp = (struct xt_tcp*) m->data; - tcp->dpts[0] = tcp->dpts[1] = local_port; - tcp->spts[0] = 0; - tcp->spts[1] = 0xFFFF; - - } else { - struct xt_udp *udp; - - strncpy(m->u.user.name, "udp", sizeof(m->u.user.name)); - udp = (struct xt_udp*) m->data; - udp->dpts[0] = udp->dpts[1] = local_port; - udp->spts[0] = 0; - udp->spts[1] = 0xFFFF; - } - - /* Fill in second match */ - m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry)) + msz); - m->u.match_size = - XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_addrtype_info_v1)); - strncpy(m->u.user.name, "addrtype", sizeof(m->u.user.name)); - m->u.user.revision = 1; - at = (struct xt_addrtype_info_v1*) m->data; - at->dest = XT_ADDRTYPE_LOCAL; - - /* Fill in target part */ - t = ipt_get_target(entry); - t->u.target_size = - XT_ALIGN(sizeof(struct ipt_entry_target)) + - XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); - strncpy(t->u.user.name, "DNAT", sizeof(t->u.user.name)); - mr = (struct nf_nat_ipv4_multi_range_compat*) t->data; - mr->rangesize = 1; - mr->range[0].flags = NF_NAT_RANGE_PROTO_SPECIFIED|NF_NAT_RANGE_MAP_IPS; - mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr; - if (protocol == IPPROTO_TCP) - mr->range[0].min.tcp.port = mr->range[0].max.tcp.port = htons(remote_port); - else - mr->range[0].min.udp.port = mr->range[0].max.udp.port = htons(remote_port); - - mask = alloca0(sz); - memset(mask, 0xFF, sz); - - if (add) { - /* Add the PREROUTING rule, if it is missing so far */ - if (!iptc_check_entry("PREROUTING", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -EINVAL; - - if (!iptc_insert_entry("PREROUTING", entry, 0, h)) - return -errno; - } - - /* If a previous remote is set, remove its entry */ - if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) { - mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr; - - if (!iptc_delete_entry("PREROUTING", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - } - - mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr; - } - - /* Add the OUTPUT rule, if it is missing so far */ - if (!in_interface) { - - /* Don't apply onto loopback addresses */ - if (!destination) { - entry->ip.dst.s_addr = htobe32(0x7F000000); - entry->ip.dmsk.s_addr = htobe32(0xFF000000); - entry->ip.invflags = IPT_INV_DSTIP; - } - - if (!iptc_check_entry("OUTPUT", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - - if (!iptc_insert_entry("OUTPUT", entry, 0, h)) - return -errno; - } - - /* If a previous remote is set, remove its entry */ - if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) { - mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr; - - if (!iptc_delete_entry("OUTPUT", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - } - } - } - } else { - if (!iptc_delete_entry("PREROUTING", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - } - - if (!in_interface) { - if (!destination) { - entry->ip.dst.s_addr = htobe32(0x7F000000); - entry->ip.dmsk.s_addr = htobe32(0xFF000000); - entry->ip.invflags = IPT_INV_DSTIP; - } - - if (!iptc_delete_entry("OUTPUT", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - } - } - } - - if (!iptc_commit(h)) - return -errno; - - return 0; -} diff --git a/src/libshared/firewall-util.h b/src/libshared/firewall-util.h deleted file mode 100644 index c39b34cf8f..0000000000 --- a/src/libshared/firewall-util.h +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include - -#include "in-addr-util.h" - -#ifdef HAVE_LIBIPTC - -int fw_add_masquerade( - bool add, - int af, - int protocol, - const union in_addr_union *source, - unsigned source_prefixlen, - const char *out_interface, - const union in_addr_union *destination, - unsigned destination_prefixlen); - -int fw_add_local_dnat( - bool add, - int af, - int protocol, - const char *in_interface, - const union in_addr_union *source, - unsigned source_prefixlen, - const union in_addr_union *destination, - unsigned destination_prefixlen, - uint16_t local_port, - const union in_addr_union *remote, - uint16_t remote_port, - const union in_addr_union *previous_remote); - -#else - -static inline int fw_add_masquerade( - bool add, - int af, - int protocol, - const union in_addr_union *source, - unsigned source_prefixlen, - const char *out_interface, - const union in_addr_union *destination, - unsigned destination_prefixlen) { - return -EOPNOTSUPP; -} - -static inline int fw_add_local_dnat( - bool add, - int af, - int protocol, - const char *in_interface, - const union in_addr_union *source, - unsigned source_prefixlen, - const union in_addr_union *destination, - unsigned destination_prefixlen, - uint16_t local_port, - const union in_addr_union *remote, - uint16_t remote_port, - const union in_addr_union *previous_remote) { - return -EOPNOTSUPP; -} - -#endif diff --git a/src/machine/.gitignore b/src/machine/.gitignore deleted file mode 100644 index e1065b5894..0000000000 --- a/src/machine/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/org.freedesktop.machine1.policy diff --git a/src/machine/Makefile b/src/machine/Makefile deleted file mode 100644 index 4586d1d89b..0000000000 --- a/src/machine/Makefile +++ /dev/null @@ -1,124 +0,0 @@ -# -*- Mode: makefile; indent-tabs-mode: t -*- -# -# This file is part of systemd. -# -# Copyright 2010-2012 Lennart Poettering -# Copyright 2010-2012 Kay Sievers -# Copyright 2013 Zbigniew Jędrzejewski-Szmek -# Copyright 2013 David Strauss -# Copyright 2016 Luke Shumaker -# -# 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 . -if ENABLE_MACHINED -systemd_machined_SOURCES = \ - src/machine/machined.c \ - src/machine/machined.h - -systemd_machined_LDADD = \ - libmachine-core.la - -rootlibexec_PROGRAMS += \ - systemd-machined - -libmachine_core_la_SOURCES = \ - src/machine/machine.c \ - src/machine/machine.h \ - src/machine/machined-dbus.c \ - src/machine/machine-dbus.c \ - src/machine/machine-dbus.h \ - src/machine/image-dbus.c \ - src/machine/image-dbus.h - -libmachine_core_la_LIBADD = \ - libshared.la - -noinst_LTLIBRARIES += \ - libmachine-core.la - -machinectl_SOURCES = \ - src/machine/machinectl.c - -machinectl_LDADD = \ - libshared.la - -rootbin_PROGRAMS += \ - machinectl - -test_machine_tables_SOURCES = \ - src/machine/test-machine-tables.c - -test_machine_tables_LDADD = \ - libmachine-core.la - -tests += \ - test-machine-tables - -nodist_systemunit_DATA += \ - units/systemd-machined.service - -dist_systemunit_DATA += \ - units/machine.slice - -dist_systemunit_DATA_busnames += \ - units/org.freedesktop.machine1.busname - -dist_dbussystemservice_DATA += \ - src/machine/org.freedesktop.machine1.service - -dist_dbuspolicy_DATA += \ - src/machine/org.freedesktop.machine1.conf - -polkitpolicy_files += \ - src/machine/org.freedesktop.machine1.policy - -dist_bashcompletion_data += \ - shell-completion/bash/machinectl - -dist_zshcompletion_data += \ - shell-completion/zsh/_machinectl \ - shell-completion/zsh/_sd_machines - -SYSTEM_UNIT_ALIASES += \ - systemd-machined.service dbus-org.freedesktop.machine1.service - -BUSNAMES_TARGET_WANTS += \ - org.freedesktop.machine1.busname - -libnss_mymachines_la_SOURCES = \ - src/nss-mymachines/nss-mymachines.sym \ - src/nss-mymachines/nss-mymachines.c - -libnss_mymachines_la_LDFLAGS = \ - $(AM_LDFLAGS) \ - -module \ - -export-dynamic \ - -avoid-version \ - -shared \ - -shrext .so.2 \ - -Wl,--version-script=$(top_srcdir)/src/nss-mymachines/nss-mymachines.sym - -libnss_mymachines_la_LIBADD = \ - libsystemd-internal.la - -lib_LTLIBRARIES += \ - libnss_mymachines.la - -endif - -polkitpolicy_in_files += \ - src/machine/org.freedesktop.machine1.policy.in - -EXTRA_DIST += \ - units/systemd-machined.service.in - diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c deleted file mode 100644 index 73f5112c4d..0000000000 --- a/src/machine/image-dbus.c +++ /dev/null @@ -1,361 +0,0 @@ -/*** - 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 . -***/ - -#include "alloc-util.h" -#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" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, image_type, ImageType); - -int bus_image_method_remove( - sd_bus_message *message, - void *userdata, - sd_bus_error *error) { - - Image *image = userdata; - Manager *m = image->userdata; - int r; - - assert(message); - assert(image); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-images", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = image_remove(image); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_image_method_rename( - sd_bus_message *message, - void *userdata, - sd_bus_error *error) { - - Image *image = userdata; - Manager *m = image->userdata; - const char *new_name; - int r; - - assert(message); - assert(image); - - r = sd_bus_message_read(message, "s", &new_name); - if (r < 0) - return r; - - if (!image_name_is_valid(new_name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-images", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = image_rename(image, new_name); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_image_method_clone( - sd_bus_message *message, - void *userdata, - sd_bus_error *error) { - - Image *image = userdata; - Manager *m = image->userdata; - const char *new_name; - int r, read_only; - - assert(message); - assert(image); - - r = sd_bus_message_read(message, "sb", &new_name, &read_only); - if (r < 0) - return r; - - if (!image_name_is_valid(new_name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-images", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = image_clone(image, new_name, read_only); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_image_method_mark_read_only( - sd_bus_message *message, - void *userdata, - sd_bus_error *error) { - - Image *image = userdata; - Manager *m = image->userdata; - int r, read_only; - - assert(message); - - r = sd_bus_message_read(message, "b", &read_only); - if (r < 0) - return r; - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-images", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = image_read_only(image, read_only); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_image_method_set_limit( - sd_bus_message *message, - void *userdata, - sd_bus_error *error) { - - Image *image = userdata; - Manager *m = image->userdata; - uint64_t limit; - int r; - - assert(message); - - 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, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-images", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = image_set_limit(image, limit); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -const sd_bus_vtable image_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0), - SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Image, path), 0), - SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Image, type), 0), - SD_BUS_PROPERTY("ReadOnly", "b", bus_property_get_bool, offsetof(Image, read_only), 0), - SD_BUS_PROPERTY("CreationTimestamp", "t", NULL, offsetof(Image, crtime), 0), - SD_BUS_PROPERTY("ModificationTimestamp", "t", NULL, offsetof(Image, mtime), 0), - SD_BUS_PROPERTY("Usage", "t", NULL, offsetof(Image, usage), 0), - SD_BUS_PROPERTY("Limit", "t", NULL, offsetof(Image, limit), 0), - SD_BUS_PROPERTY("UsageExclusive", "t", NULL, offsetof(Image, usage_exclusive), 0), - SD_BUS_PROPERTY("LimitExclusive", "t", NULL, offsetof(Image, limit_exclusive), 0), - SD_BUS_METHOD("Remove", NULL, NULL, bus_image_method_remove, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Rename", "s", NULL, bus_image_method_rename, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Clone", "sb", NULL, bus_image_method_clone, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MarkReadOnly", "b", NULL, bus_image_method_mark_read_only, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetLimit", "t", NULL, bus_image_method_set_limit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_VTABLE_END -}; - -static int image_flush_cache(sd_event_source *s, void *userdata) { - Manager *m = userdata; - Image *i; - - assert(s); - assert(m); - - while ((i = hashmap_steal_first(m->image_cache))) - image_unref(i); - - return 0; -} - -int image_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; - Image *image = NULL; - const char *p; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - - p = startswith(path, "/org/freedesktop/machine1/image/"); - if (!p) - return 0; - - e = bus_label_unescape(p); - if (!e) - return -ENOMEM; - - image = hashmap_get(m->image_cache, e); - if (image) { - *found = image; - return 1; - } - - r = hashmap_ensure_allocated(&m->image_cache, &string_hash_ops); - if (r < 0) - return r; - - if (!m->image_cache_defer_event) { - r = sd_event_add_defer(m->event, &m->image_cache_defer_event, image_flush_cache, m); - if (r < 0) - return r; - - r = sd_event_source_set_priority(m->image_cache_defer_event, SD_EVENT_PRIORITY_IDLE); - if (r < 0) - return r; - } - - r = sd_event_source_set_enabled(m->image_cache_defer_event, SD_EVENT_ONESHOT); - if (r < 0) - return r; - - r = image_find(e, &image); - if (r <= 0) - return r; - - image->userdata = m; - - r = hashmap_put(m->image_cache, image->name, image); - if (r < 0) { - image_unref(image); - return r; - } - - *found = image; - return 1; -} - -char *image_bus_path(const char *name) { - _cleanup_free_ char *e = NULL; - - assert(name); - - e = bus_label_escape(name); - if (!e) - return NULL; - - return strappend("/org/freedesktop/machine1/image/", e); -} - -int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { - _cleanup_(image_hashmap_freep) Hashmap *images = NULL; - _cleanup_strv_free_ char **l = NULL; - Image *image; - Iterator i; - int r; - - assert(bus); - assert(path); - assert(nodes); - - images = hashmap_new(&string_hash_ops); - if (!images) - return -ENOMEM; - - r = image_discover(images); - if (r < 0) - return r; - - HASHMAP_FOREACH(image, images, i) { - char *p; - - p = image_bus_path(image->name); - if (!p) - return -ENOMEM; - - r = strv_consume(&l, p); - if (r < 0) - return r; - } - - *nodes = l; - l = NULL; - - return 1; -} diff --git a/src/machine/image-dbus.h b/src/machine/image-dbus.h deleted file mode 100644 index b62da996c6..0000000000 --- a/src/machine/image-dbus.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "machined.h" - -extern const sd_bus_vtable image_vtable[]; - -char *image_bus_path(const char *name); - -int image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); -int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); - -int bus_image_method_remove(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_image_method_rename(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_image_method_clone(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_image_method_mark_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_image_method_set_limit(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c deleted file mode 100644 index 71f20b3f07..0000000000 --- a/src/machine/machine-dbus.c +++ /dev/null @@ -1,1435 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include -#include - -/* When we include libgen.h because we need dirname() we immediately - * undefine basename() since libgen.h defines it as a macro to the POSIX - * version which is really broken. We prefer GNU basename(). */ -#include -#undef basename - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-internal.h" -#include "bus-label.h" -#include "bus-util.h" -#include "copy.h" -#include "env-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "in-addr-util.h" -#include "local-addresses.h" -#include "machine-dbus.h" -#include "machine.h" -#include "mkdir.h" -#include "path-util.h" -#include "process-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "user-util.h" - -static int property_get_id( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Machine *m = userdata; - - assert(bus); - assert(reply); - assert(m); - - return sd_bus_message_append_array(reply, 'y', &m->id, 16); -} - -static int property_get_state( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Machine *m = userdata; - const char *state; - int r; - - assert(bus); - assert(reply); - assert(m); - - state = machine_state_to_string(machine_get_state(m)); - - r = sd_bus_message_append_basic(reply, 's', state); - if (r < 0) - return r; - - return 1; -} - -static int property_get_netif( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Machine *m = userdata; - - assert(bus); - assert(reply); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - return sd_bus_message_append_array(reply, 'i', m->netif, m->n_netif * sizeof(int)); -} - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_class, machine_class, MachineClass); - -int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Machine *m = userdata; - int r; - - assert(message); - assert(m); - - r = bus_verify_polkit_async( - message, - CAP_KILL, - "org.freedesktop.machine1.manage-machines", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = machine_stop(m); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Machine *m = userdata; - const char *swho; - int32_t signo; - KillWho who; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "si", &swho, &signo); - if (r < 0) - return r; - - if (isempty(swho)) - who = KILL_ALL; - else { - who = kill_who_from_string(swho); - if (who < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid kill parameter '%s'", swho); - } - - if (signo <= 0 || signo >= _NSIG) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid signal %i", signo); - - r = bus_verify_polkit_async( - message, - CAP_KILL, - "org.freedesktop.machine1.manage-machines", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = machine_kill(m, who, signo); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Machine *m = userdata; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(iay)"); - if (r < 0) - return r; - - switch (m->class) { - - case MACHINE_HOST: { - _cleanup_free_ struct local_address *addresses = NULL; - struct local_address *a; - int n, i; - - n = local_addresses(NULL, 0, AF_UNSPEC, &addresses); - if (n < 0) - return n; - - for (a = addresses, i = 0; i < n; a++, i++) { - - r = sd_bus_message_open_container(reply, 'r', "iay"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "i", addresses[i].family); - if (r < 0) - return r; - - r = sd_bus_message_append_array(reply, 'y', &addresses[i].address, FAMILY_ADDRESS_SIZE(addresses[i].family)); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - } - - break; - } - - case MACHINE_CONTAINER: { - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - _cleanup_free_ char *us = NULL, *them = NULL; - _cleanup_close_ int netns_fd = -1; - const char *p; - siginfo_t si; - pid_t child; - - r = readlink_malloc("/proc/self/ns/net", &us); - if (r < 0) - return r; - - p = procfs_file_alloca(m->leader, "ns/net"); - r = readlink_malloc(p, &them); - if (r < 0) - return r; - - if (streq(us, them)) - return sd_bus_error_setf(error, BUS_ERROR_NO_PRIVATE_NETWORKING, "Machine %s does not use private networking", m->name); - - r = namespace_open(m->leader, NULL, NULL, &netns_fd, NULL, NULL); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) - return -errno; - - child = fork(); - if (child < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); - - if (child == 0) { - _cleanup_free_ struct local_address *addresses = NULL; - struct local_address *a; - int i, n; - - pair[0] = safe_close(pair[0]); - - r = namespace_enter(-1, -1, netns_fd, -1, -1); - if (r < 0) - _exit(EXIT_FAILURE); - - n = local_addresses(NULL, 0, AF_UNSPEC, &addresses); - if (n < 0) - _exit(EXIT_FAILURE); - - for (a = addresses, i = 0; i < n; a++, i++) { - struct iovec iov[2] = { - { .iov_base = &a->family, .iov_len = sizeof(a->family) }, - { .iov_base = &a->address, .iov_len = FAMILY_ADDRESS_SIZE(a->family) }, - }; - - r = writev(pair[1], iov, 2); - if (r < 0) - _exit(EXIT_FAILURE); - } - - pair[1] = safe_close(pair[1]); - - _exit(EXIT_SUCCESS); - } - - pair[1] = safe_close(pair[1]); - - for (;;) { - int family; - ssize_t n; - union in_addr_union in_addr; - struct iovec iov[2]; - struct msghdr mh = { - .msg_iov = iov, - .msg_iovlen = 2, - }; - - iov[0] = (struct iovec) { .iov_base = &family, .iov_len = sizeof(family) }; - iov[1] = (struct iovec) { .iov_base = &in_addr, .iov_len = sizeof(in_addr) }; - - n = recvmsg(pair[0], &mh, 0); - if (n < 0) - return -errno; - if ((size_t) n < sizeof(family)) - break; - - r = sd_bus_message_open_container(reply, 'r', "iay"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "i", family); - if (r < 0) - return r; - - switch (family) { - - case AF_INET: - if (n != sizeof(struct in_addr) + sizeof(family)) - return -EIO; - - r = sd_bus_message_append_array(reply, 'y', &in_addr.in, sizeof(in_addr.in)); - break; - - case AF_INET6: - if (n != sizeof(struct in6_addr) + sizeof(family)) - return -EIO; - - r = sd_bus_message_append_array(reply, 'y', &in_addr.in6, sizeof(in_addr.in6)); - break; - } - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - } - - r = wait_for_terminate(child, &si); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); - if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) - return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); - break; - } - - default: - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting IP address data is only supported on container machines."); - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_strv_free_ char **l = NULL; - Machine *m = userdata; - char **k, **v; - int r; - - assert(message); - assert(m); - - switch (m->class) { - - case MACHINE_HOST: - r = load_env_file_pairs(NULL, "/etc/os-release", NULL, &l); - if (r < 0) - return r; - - break; - - case MACHINE_CONTAINER: { - _cleanup_close_ int mntns_fd = -1, root_fd = -1; - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - _cleanup_fclose_ FILE *f = NULL; - siginfo_t si; - pid_t child; - - r = namespace_open(m->leader, NULL, &mntns_fd, NULL, NULL, &root_fd); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) - return -errno; - - child = fork(); - if (child < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); - - if (child == 0) { - _cleanup_close_ int fd = -1; - - pair[0] = safe_close(pair[0]); - - r = namespace_enter(-1, mntns_fd, -1, -1, root_fd); - if (r < 0) - _exit(EXIT_FAILURE); - - fd = open("/etc/os-release", O_RDONLY|O_CLOEXEC); - if (fd < 0) { - fd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC); - if (fd < 0) - _exit(EXIT_FAILURE); - } - - r = copy_bytes(fd, pair[1], (uint64_t) -1, false); - if (r < 0) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - pair[1] = safe_close(pair[1]); - - f = fdopen(pair[0], "re"); - if (!f) - return -errno; - - pair[0] = -1; - - r = load_env_file_pairs(f, "/etc/os-release", NULL, &l); - if (r < 0) - return r; - - r = wait_for_terminate(child, &si); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); - if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) - return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); - - break; - } - - default: - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting OS release data is only supported on container machines."); - } - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "{ss}"); - if (r < 0) - return r; - - STRV_FOREACH_PAIR(k, v, l) { - r = sd_bus_message_append(reply, "{ss}", *k, *v); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *pty_name = NULL; - _cleanup_close_ int master = -1; - Machine *m = userdata; - int r; - - assert(message); - assert(m); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-open-pty" : "org.freedesktop.machine1.open-pty", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (master < 0) - return master; - - r = ptsname_namespace(master, &pty_name); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "hs", master, pty_name); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -static int container_bus_new(Machine *m, sd_bus_error *error, sd_bus **ret) { - int r; - - assert(m); - assert(ret); - - switch (m->class) { - - case MACHINE_HOST: - *ret = NULL; - break; - - case MACHINE_CONTAINER: { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - char *address; - - r = sd_bus_new(&bus); - if (r < 0) - return r; - - if (asprintf(&address, "x-machine-kernel:pid=%1$" PID_PRI ";x-machine-unix:pid=%1$" PID_PRI, m->leader) < 0) - return -ENOMEM; - - bus->address = address; - bus->bus_client = true; - bus->trusted = false; - bus->is_system = true; - - r = sd_bus_start(bus); - if (r == -ENOENT) - return sd_bus_error_set_errnof(error, r, "There is no system bus in container %s.", m->name); - if (r < 0) - return r; - - *ret = bus; - bus = NULL; - break; - } - - default: - return -EOPNOTSUPP; - } - - return 0; -} - -int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *pty_name = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL; - _cleanup_close_ int master = -1; - sd_bus *container_bus = NULL; - Machine *m = userdata; - const char *p, *getty; - int r; - - assert(message); - assert(m); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-login" : "org.freedesktop.machine1.login", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (master < 0) - return master; - - r = ptsname_namespace(master, &pty_name); - if (r < 0) - return r; - - p = path_startswith(pty_name, "/dev/pts/"); - if (!p) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "PTS name %s is invalid", pty_name); - - r = container_bus_new(m, error, &allocated_bus); - if (r < 0) - return r; - - container_bus = allocated_bus ?: m->manager->bus; - - getty = strjoina("container-getty@", p, ".service"); - - r = sd_bus_call_method( - container_bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartUnit", - error, NULL, - "ss", getty, "replace"); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "hs", master, pty_name); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *tm = NULL; - _cleanup_free_ char *pty_name = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL; - sd_bus *container_bus = NULL; - _cleanup_close_ int master = -1, slave = -1; - _cleanup_strv_free_ char **env = NULL, **args = NULL; - Machine *m = userdata; - const char *p, *unit, *user, *path, *description, *utmp_id; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "ss", &user, &path); - if (r < 0) - return r; - if (isempty(user)) - user = NULL; - if (isempty(path)) - path = "/bin/sh"; - if (!path_is_absolute(path)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified path '%s' is not absolute", path); - - r = sd_bus_message_read_strv(message, &args); - if (r < 0) - return r; - if (strv_isempty(args)) { - args = strv_free(args); - - args = strv_new(path, NULL); - if (!args) - return -ENOMEM; - - args[0][0] = '-'; /* Tell /bin/sh that this shall be a login shell */ - } - - r = sd_bus_message_read_strv(message, &env); - if (r < 0) - return r; - if (!strv_env_is_valid(env)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-shell" : "org.freedesktop.machine1.shell", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (master < 0) - return master; - - r = ptsname_namespace(master, &pty_name); - if (r < 0) - return r; - - p = path_startswith(pty_name, "/dev/pts/"); - assert(p); - - slave = machine_open_terminal(m, pty_name, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (slave < 0) - return slave; - - utmp_id = path_startswith(pty_name, "/dev/"); - assert(utmp_id); - - r = container_bus_new(m, error, &allocated_bus); - if (r < 0) - return r; - - container_bus = allocated_bus ?: m->manager->bus; - - r = sd_bus_message_new_method_call( - container_bus, - &tm, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartTransientUnit"); - if (r < 0) - return r; - - /* Name and mode */ - unit = strjoina("container-shell@", p, ".service", NULL); - r = sd_bus_message_append(tm, "ss", unit, "fail"); - if (r < 0) - return r; - - /* Properties */ - r = sd_bus_message_open_container(tm, 'a', "(sv)"); - if (r < 0) - return r; - - description = strjoina("Shell for User ", isempty(user) ? "root" : user); - r = sd_bus_message_append(tm, - "(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)", - "Description", "s", description, - "StandardInputFileDescriptor", "h", slave, - "StandardOutputFileDescriptor", "h", slave, - "StandardErrorFileDescriptor", "h", slave, - "SendSIGHUP", "b", true, - "IgnoreSIGPIPE", "b", false, - "KillMode", "s", "mixed", - "TTYReset", "b", true, - "UtmpIdentifier", "s", utmp_id, - "UtmpMode", "s", "user", - "PAMName", "s", "login", - "WorkingDirectory", "s", "-~"); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "(sv)", "User", "s", isempty(user) ? "root" : user); - if (r < 0) - return r; - - if (!strv_isempty(env)) { - r = sd_bus_message_open_container(tm, 'r', "sv"); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "s", "Environment"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(tm, 'v', "as"); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(tm, env); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - } - - /* Exec container */ - r = sd_bus_message_open_container(tm, 'r', "sv"); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "s", "ExecStart"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(tm, 'v', "a(sasb)"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(tm, 'a', "(sasb)"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(tm, 'r', "sasb"); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "s", path); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(tm, args); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "b", true); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - /* Auxiliary units */ - r = sd_bus_message_append(tm, "a(sa(sv))", 0); - if (r < 0) - return r; - - r = sd_bus_call(container_bus, tm, 0, error, NULL); - if (r < 0) - return r; - - slave = safe_close(slave); - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "hs", master, pty_name); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; - char mount_slave[] = "/tmp/propagate.XXXXXX", *mount_tmp, *mount_outside, *p; - bool mount_slave_created = false, mount_slave_mounted = false, - mount_tmp_created = false, mount_tmp_mounted = false, - mount_outside_created = false, mount_outside_mounted = false; - const char *dest, *src; - Machine *m = userdata; - int read_only, make_directory; - pid_t child; - siginfo_t si; - int r; - - assert(message); - assert(m); - - if (m->class != MACHINE_CONTAINER) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Bind mounting is only supported on container machines."); - - r = sd_bus_message_read(message, "ssbb", &src, &dest, &read_only, &make_directory); - if (r < 0) - return r; - - if (!path_is_absolute(src) || !path_is_safe(src)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and not contain ../."); - - if (isempty(dest)) - dest = src; - else if (!path_is_absolute(dest) || !path_is_safe(dest)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and not contain ../."); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-machines", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - /* One day, when bind mounting /proc/self/fd/n works across - * namespace boundaries we should rework this logic to make - * use of it... */ - - p = strjoina("/run/systemd/nspawn/propagate/", m->name, "/"); - if (laccess(p, F_OK) < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Container does not allow propagation of mount points."); - - /* Our goal is to install a new bind mount into the container, - possibly read-only. This is irritatingly complex - unfortunately, currently. - - First, we start by creating a private playground in /tmp, - that we can mount MS_SLAVE. (Which is necessary, since - MS_MOVE cannot be applied to mounts with MS_SHARED parent - mounts.) */ - - if (!mkdtemp(mount_slave)) - return sd_bus_error_set_errnof(error, errno, "Failed to create playground %s: %m", mount_slave); - - mount_slave_created = true; - - if (mount(mount_slave, mount_slave, NULL, MS_BIND, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to make bind mount %s: %m", mount_slave); - goto finish; - } - - mount_slave_mounted = true; - - if (mount(NULL, mount_slave, NULL, MS_SLAVE, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to remount slave %s: %m", mount_slave); - goto finish; - } - - /* Second, we mount the source directory to a directory inside - of our MS_SLAVE playground. */ - mount_tmp = strjoina(mount_slave, "/mount"); - if (mkdir(mount_tmp, 0700) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount point %s: %m", mount_tmp); - goto finish; - } - - mount_tmp_created = true; - - if (mount(src, mount_tmp, NULL, MS_BIND, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to overmount %s: %m", mount_tmp); - goto finish; - } - - mount_tmp_mounted = true; - - /* Third, we remount the new bind mount read-only if requested. */ - if (read_only) - if (mount(NULL, mount_tmp, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to remount read-only %s: %m", mount_tmp); - goto finish; - } - - /* Fourth, we move the new bind mount into the propagation - * directory. This way it will appear there read-only - * right-away. */ - - mount_outside = strjoina("/run/systemd/nspawn/propagate/", m->name, "/XXXXXX"); - if (!mkdtemp(mount_outside)) { - r = sd_bus_error_set_errnof(error, errno, "Cannot create propagation directory %s: %m", mount_outside); - goto finish; - } - - mount_outside_created = true; - - if (mount(mount_tmp, mount_outside, NULL, MS_MOVE, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to move %s to %s: %m", mount_tmp, mount_outside); - goto finish; - } - - mount_outside_mounted = true; - mount_tmp_mounted = false; - - (void) rmdir(mount_tmp); - mount_tmp_created = false; - - (void) umount(mount_slave); - mount_slave_mounted = false; - - (void) rmdir(mount_slave); - mount_slave_created = false; - - if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); - goto finish; - } - - child = fork(); - if (child < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); - goto finish; - } - - if (child == 0) { - const char *mount_inside; - int mntfd; - const char *q; - - errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); - - q = procfs_file_alloca(m->leader, "ns/mnt"); - mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (mntfd < 0) { - r = log_error_errno(errno, "Failed to open mount namespace of leader: %m"); - goto child_fail; - } - - if (setns(mntfd, CLONE_NEWNS) < 0) { - r = log_error_errno(errno, "Failed to join namespace of leader: %m"); - goto child_fail; - } - - if (make_directory) - (void) mkdir_p(dest, 0755); - - /* Fifth, move the mount to the right place inside */ - mount_inside = strjoina("/run/systemd/nspawn/incoming/", basename(mount_outside)); - if (mount(mount_inside, dest, NULL, MS_MOVE, NULL) < 0) { - r = log_error_errno(errno, "Failed to mount: %m"); - goto child_fail; - } - - _exit(EXIT_SUCCESS); - - child_fail: - (void) write(errno_pipe_fd[1], &r, sizeof(r)); - errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); - - _exit(EXIT_FAILURE); - } - - errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); - - r = wait_for_terminate(child, &si); - if (r < 0) { - r = sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); - goto finish; - } - if (si.si_code != CLD_EXITED) { - r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); - goto finish; - } - if (si.si_status != EXIT_SUCCESS) { - - if (read(errno_pipe_fd[0], &r, sizeof(r)) == sizeof(r)) - r = sd_bus_error_set_errnof(error, r, "Failed to mount: %m"); - else - r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child failed."); - goto finish; - } - - r = sd_bus_reply_method_return(message, NULL); - -finish: - if (mount_outside_mounted) - umount(mount_outside); - if (mount_outside_created) - rmdir(mount_outside); - - if (mount_tmp_mounted) - umount(mount_tmp); - if (mount_tmp_created) - rmdir(mount_tmp); - - if (mount_slave_mounted) - umount(mount_slave); - if (mount_slave_created) - rmdir(mount_slave); - - return r; -} - -static int machine_operation_done(sd_event_source *s, const siginfo_t *si, void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - MachineOperation *o = userdata; - int r; - - assert(o); - assert(si); - - o->pid = 0; - - if (si->si_code != CLD_EXITED) { - r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child died abnormally."); - goto fail; - } - - if (si->si_status != EXIT_SUCCESS) { - if (read(o->errno_fd, &r, sizeof(r)) == sizeof(r)) - r = sd_bus_error_set_errnof(&error, r, "%m"); - else - r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child failed."); - - goto fail; - } - - r = sd_bus_reply_method_return(o->message, NULL); - if (r < 0) - log_error_errno(r, "Failed to reply to message: %m"); - - machine_operation_unref(o); - return 0; - -fail: - r = sd_bus_reply_method_error(o->message, &error); - if (r < 0) - log_error_errno(r, "Failed to reply to message: %m"); - - machine_operation_unref(o); - return 0; -} - -int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error) { - const char *src, *dest, *host_path, *container_path, *host_basename, *host_dirname, *container_basename, *container_dirname; - _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; - _cleanup_close_ int hostfd = -1; - Machine *m = userdata; - MachineOperation *o; - bool copy_from; - pid_t child; - char *t; - int r; - - assert(message); - assert(m); - - if (m->n_operations >= MACHINE_OPERATIONS_MAX) - return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing copies."); - - if (m->class != MACHINE_CONTAINER) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Copying files is only supported on container machines."); - - r = sd_bus_message_read(message, "ss", &src, &dest); - if (r < 0) - return r; - - if (!path_is_absolute(src)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute."); - - if (isempty(dest)) - dest = src; - else if (!path_is_absolute(dest)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute."); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-machines", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - copy_from = strstr(sd_bus_message_get_member(message), "CopyFrom"); - - if (copy_from) { - container_path = src; - host_path = dest; - } else { - host_path = src; - container_path = dest; - } - - host_basename = basename(host_path); - t = strdupa(host_path); - host_dirname = dirname(t); - - container_basename = basename(container_path); - t = strdupa(container_path); - container_dirname = dirname(t); - - hostfd = open(host_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY); - if (hostfd < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to open host directory %s: %m", host_dirname); - - if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); - - child = fork(); - if (child < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); - - if (child == 0) { - int containerfd; - const char *q; - int mntfd; - - errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); - - q = procfs_file_alloca(m->leader, "ns/mnt"); - mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (mntfd < 0) { - r = log_error_errno(errno, "Failed to open mount namespace of leader: %m"); - goto child_fail; - } - - if (setns(mntfd, CLONE_NEWNS) < 0) { - r = log_error_errno(errno, "Failed to join namespace of leader: %m"); - goto child_fail; - } - - containerfd = open(container_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY); - if (containerfd < 0) { - r = log_error_errno(errno, "Failed top open destination directory: %m"); - goto child_fail; - } - - if (copy_from) - r = copy_tree_at(containerfd, container_basename, hostfd, host_basename, true); - else - r = copy_tree_at(hostfd, host_basename, containerfd, container_basename, true); - - hostfd = safe_close(hostfd); - containerfd = safe_close(containerfd); - - if (r < 0) { - r = log_error_errno(r, "Failed to copy tree: %m"); - goto child_fail; - } - - _exit(EXIT_SUCCESS); - - child_fail: - (void) write(errno_pipe_fd[1], &r, sizeof(r)); - errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); - - _exit(EXIT_FAILURE); - } - - errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); - - /* Copying might take a while, hence install a watch the - * child, and return */ - - o = new0(MachineOperation, 1); - if (!o) - return log_oom(); - - o->pid = child; - o->message = sd_bus_message_ref(message); - o->errno_fd = errno_pipe_fd[0]; - errno_pipe_fd[0] = -1; - - r = sd_event_add_child(m->manager->event, &o->event_source, child, WEXITED, machine_operation_done, o); - if (r < 0) { - machine_operation_unref(o); - return log_oom(); - } - - LIST_PREPEND(operations, m->operations, o); - m->n_operations++; - o->machine = m; - - return 1; -} - -const sd_bus_vtable machine_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Machine, name), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Id", "ay", property_get_id, 0, SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("Timestamp", offsetof(Machine, timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Service", "s", NULL, offsetof(Machine, service), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Unit", "s", NULL, offsetof(Machine, unit), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Scope", "s", NULL, offsetof(Machine, unit), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("Leader", "u", NULL, offsetof(Machine, leader), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Class", "s", property_get_class, offsetof(Machine, class), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(Machine, root_directory), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("NetworkInterfaces", "ai", property_get_netif, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("State", "s", property_get_state, 0, 0), - SD_BUS_METHOD("Terminate", NULL, NULL, bus_machine_method_terminate, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Kill", "si", NULL, bus_machine_method_kill, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetAddresses", NULL, "a(iay)", bus_machine_method_get_addresses, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetOSRelease", NULL, "a{ss}", bus_machine_method_get_os_release, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("OpenPTY", NULL, "hs", bus_machine_method_open_pty, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("OpenLogin", NULL, "hs", bus_machine_method_open_login, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("OpenShell", "ssasas", "hs", bus_machine_method_open_shell, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("BindMount", "ssbb", NULL, bus_machine_method_bind_mount, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CopyFrom", "ss", NULL, bus_machine_method_copy, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CopyTo", "ss", NULL, bus_machine_method_copy, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_VTABLE_END -}; - -int machine_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - if (streq(path, "/org/freedesktop/machine1/machine/self")) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - sd_bus_message *message; - pid_t pid; - - message = sd_bus_get_current_message(bus); - if (!message) - return 0; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r < 0) - return r; - - r = manager_get_machine_by_pid(m, pid, &machine); - if (r <= 0) - return 0; - } else { - _cleanup_free_ char *e = NULL; - const char *p; - - p = startswith(path, "/org/freedesktop/machine1/machine/"); - if (!p) - return 0; - - e = bus_label_unescape(p); - if (!e) - return -ENOMEM; - - machine = hashmap_get(m->machines, e); - if (!machine) - return 0; - } - - *found = machine; - return 1; -} - -char *machine_bus_path(Machine *m) { - _cleanup_free_ char *e = NULL; - - assert(m); - - e = bus_label_escape(m->name); - if (!e) - return NULL; - - return strappend("/org/freedesktop/machine1/machine/", e); -} - -int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { - _cleanup_strv_free_ char **l = NULL; - Machine *machine = NULL; - Manager *m = userdata; - Iterator i; - int r; - - assert(bus); - assert(path); - assert(nodes); - - HASHMAP_FOREACH(machine, m->machines, i) { - char *p; - - p = machine_bus_path(machine); - if (!p) - return -ENOMEM; - - r = strv_consume(&l, p); - if (r < 0) - return r; - } - - *nodes = l; - l = NULL; - - return 1; -} - -int machine_send_signal(Machine *m, bool new_machine) { - _cleanup_free_ char *p = NULL; - - assert(m); - - p = machine_bus_path(m); - if (!p) - return -ENOMEM; - - return sd_bus_emit_signal( - m->manager->bus, - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - new_machine ? "MachineNew" : "MachineRemoved", - "so", m->name, p); -} - -int machine_send_create_reply(Machine *m, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL; - _cleanup_free_ char *p = NULL; - - assert(m); - - if (!m->create_message) - return 0; - - c = m->create_message; - m->create_message = NULL; - - if (error) - return sd_bus_reply_method_error(c, error); - - /* Update the machine state file before we notify the client - * about the result. */ - machine_save(m); - - p = machine_bus_path(m); - if (!p) - return -ENOMEM; - - return sd_bus_reply_method_return(c, "o", p); -} diff --git a/src/machine/machine-dbus.h b/src/machine/machine-dbus.h deleted file mode 100644 index 3a8162b171..0000000000 --- a/src/machine/machine-dbus.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -#include "sd-bus.h" - -#include "machine.h" - -extern const sd_bus_vtable machine_vtable[]; - -char *machine_bus_path(Machine *s); -int machine_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); -int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); - -int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error); - -int machine_send_signal(Machine *m, bool new_machine); -int machine_send_create_reply(Machine *m, sd_bus_error *error); diff --git a/src/machine/machine.c b/src/machine/machine.c deleted file mode 100644 index 406d5a4b85..0000000000 --- a/src/machine/machine.c +++ /dev/null @@ -1,659 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include -#include - -#include "sd-messages.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "escape.h" -#include "extract-word.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "hashmap.h" -#include "machine-dbus.h" -#include "machine.h" -#include "mkdir.h" -#include "parse-util.h" -#include "process-util.h" -#include "special.h" -#include "string-table.h" -#include "terminal-util.h" -#include "unit-name.h" -#include "util.h" - -Machine* machine_new(Manager *manager, MachineClass class, const char *name) { - Machine *m; - - assert(manager); - assert(class < _MACHINE_CLASS_MAX); - assert(name); - - /* Passing class == _MACHINE_CLASS_INVALID here is fine. It - * means as much as "we don't know yet", and that we'll figure - * it out later when loading the state file. */ - - m = new0(Machine, 1); - if (!m) - return NULL; - - m->name = strdup(name); - if (!m->name) - goto fail; - - if (class != MACHINE_HOST) { - m->state_file = strappend("/run/systemd/machines/", m->name); - if (!m->state_file) - goto fail; - } - - m->class = class; - - if (hashmap_put(manager->machines, m->name, m) < 0) - goto fail; - - m->manager = manager; - - return m; - -fail: - free(m->state_file); - free(m->name); - free(m); - - return NULL; -} - -void machine_free(Machine *m) { - assert(m); - - while (m->operations) - machine_operation_unref(m->operations); - - if (m->in_gc_queue) - LIST_REMOVE(gc_queue, m->manager->machine_gc_queue, m); - - machine_release_unit(m); - - free(m->scope_job); - - (void) hashmap_remove(m->manager->machines, m->name); - - if (m->manager->host_machine == m) - m->manager->host_machine = NULL; - - if (m->leader > 0) - (void) hashmap_remove_value(m->manager->machine_leaders, PID_TO_PTR(m->leader), m); - - sd_bus_message_unref(m->create_message); - - free(m->name); - free(m->state_file); - free(m->service); - free(m->root_directory); - free(m->netif); - free(m); -} - -int machine_save(Machine *m) { - _cleanup_free_ char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(m); - - if (!m->state_file) - return 0; - - if (!m->started) - return 0; - - r = mkdir_safe_label("/run/systemd/machines", 0755, 0, 0); - if (r < 0) - goto fail; - - r = fopen_temporary(m->state_file, &f, &temp_path); - if (r < 0) - goto fail; - - (void) fchmod(fileno(f), 0644); - - fprintf(f, - "# This is private data. Do not parse.\n" - "NAME=%s\n", - m->name); - - if (m->unit) { - _cleanup_free_ char *escaped; - - escaped = cescape(m->unit); - if (!escaped) { - r = -ENOMEM; - goto fail; - } - - fprintf(f, "SCOPE=%s\n", escaped); /* We continue to call this "SCOPE=" because it is internal only, and we want to stay compatible with old files */ - } - - if (m->scope_job) - fprintf(f, "SCOPE_JOB=%s\n", m->scope_job); - - if (m->service) { - _cleanup_free_ char *escaped; - - escaped = cescape(m->service); - if (!escaped) { - r = -ENOMEM; - goto fail; - } - fprintf(f, "SERVICE=%s\n", escaped); - } - - if (m->root_directory) { - _cleanup_free_ char *escaped; - - escaped = cescape(m->root_directory); - if (!escaped) { - r = -ENOMEM; - goto fail; - } - fprintf(f, "ROOT=%s\n", escaped); - } - - if (!sd_id128_equal(m->id, SD_ID128_NULL)) - fprintf(f, "ID=" SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->id)); - - if (m->leader != 0) - fprintf(f, "LEADER="PID_FMT"\n", m->leader); - - if (m->class != _MACHINE_CLASS_INVALID) - fprintf(f, "CLASS=%s\n", machine_class_to_string(m->class)); - - if (dual_timestamp_is_set(&m->timestamp)) - fprintf(f, - "REALTIME="USEC_FMT"\n" - "MONOTONIC="USEC_FMT"\n", - m->timestamp.realtime, - m->timestamp.monotonic); - - if (m->n_netif > 0) { - unsigned i; - - fputs("NETIF=", f); - - for (i = 0; i < m->n_netif; i++) { - if (i != 0) - fputc(' ', f); - - fprintf(f, "%i", m->netif[i]); - } - - fputc('\n', f); - } - - r = fflush_and_check(f); - if (r < 0) - goto fail; - - if (rename(temp_path, m->state_file) < 0) { - r = -errno; - goto fail; - } - - if (m->unit) { - char *sl; - - /* Create a symlink from the unit name to the machine - * name, so that we can quickly find the machine for - * each given unit. Ignore error. */ - sl = strjoina("/run/systemd/machines/unit:", m->unit); - (void) symlink(m->name, sl); - } - - return 0; - -fail: - (void) unlink(m->state_file); - - if (temp_path) - (void) unlink(temp_path); - - return log_error_errno(r, "Failed to save machine data %s: %m", m->state_file); -} - -static void machine_unlink(Machine *m) { - assert(m); - - if (m->unit) { - - char *sl; - - sl = strjoina("/run/systemd/machines/unit:", m->unit); - (void) unlink(sl); - } - - if (m->state_file) - (void) unlink(m->state_file); -} - -int machine_load(Machine *m) { - _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *id = NULL, *leader = NULL, *class = NULL, *netif = NULL; - int r; - - assert(m); - - if (!m->state_file) - return 0; - - r = parse_env_file(m->state_file, NEWLINE, - "SCOPE", &m->unit, - "SCOPE_JOB", &m->scope_job, - "SERVICE", &m->service, - "ROOT", &m->root_directory, - "ID", &id, - "LEADER", &leader, - "CLASS", &class, - "REALTIME", &realtime, - "MONOTONIC", &monotonic, - "NETIF", &netif, - NULL); - if (r < 0) { - if (r == -ENOENT) - return 0; - - return log_error_errno(r, "Failed to read %s: %m", m->state_file); - } - - if (id) - sd_id128_from_string(id, &m->id); - - if (leader) - parse_pid(leader, &m->leader); - - if (class) { - MachineClass c; - - c = machine_class_from_string(class); - if (c >= 0) - m->class = c; - } - - if (realtime) { - unsigned long long l; - if (sscanf(realtime, "%llu", &l) > 0) - m->timestamp.realtime = l; - } - - if (monotonic) { - unsigned long long l; - if (sscanf(monotonic, "%llu", &l) > 0) - m->timestamp.monotonic = l; - } - - if (netif) { - size_t allocated = 0, nr = 0; - const char *p; - int *ni = NULL; - - p = netif; - for(;;) { - _cleanup_free_ char *word = NULL; - int ifi; - - r = extract_first_word(&p, &word, NULL, 0); - if (r == 0) - break; - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_warning_errno(r, "Failed to parse NETIF: %s", netif); - break; - } - - if (parse_ifindex(word, &ifi) < 0) - continue; - - if (!GREEDY_REALLOC(ni, allocated, nr+1)) { - free(ni); - return log_oom(); - } - - ni[nr++] = ifi; - } - - free(m->netif); - m->netif = ni; - m->n_netif = nr; - } - - return r; -} - -static int machine_start_scope(Machine *m, sd_bus_message *properties, sd_bus_error *error) { - int r = 0; - - assert(m); - assert(m->class != MACHINE_HOST); - - if (!m->unit) { - _cleanup_free_ char *escaped = NULL; - char *scope, *description, *job = NULL; - - escaped = unit_name_escape(m->name); - if (!escaped) - return log_oom(); - - scope = strjoin("machine-", escaped, ".scope", NULL); - if (!scope) - return log_oom(); - - description = strjoina(m->class == MACHINE_VM ? "Virtual Machine " : "Container ", m->name); - - r = manager_start_scope(m->manager, scope, m->leader, SPECIAL_MACHINE_SLICE, description, properties, error, &job); - if (r < 0) { - log_error("Failed to start machine scope: %s", bus_error_message(error, r)); - free(scope); - return r; - } else { - m->unit = scope; - - free(m->scope_job); - m->scope_job = job; - } - } - - if (m->unit) - hashmap_put(m->manager->machine_units, m->unit, m); - - return r; -} - -int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error) { - int r; - - assert(m); - - if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM)) - return -EOPNOTSUPP; - - if (m->started) - return 0; - - r = hashmap_put(m->manager->machine_leaders, PID_TO_PTR(m->leader), m); - if (r < 0) - return r; - - /* Create cgroup */ - r = machine_start_scope(m, properties, error); - if (r < 0) - return r; - - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_START), - "NAME=%s", m->name, - "LEADER="PID_FMT, m->leader, - LOG_MESSAGE("New machine %s.", m->name), - NULL); - - if (!dual_timestamp_is_set(&m->timestamp)) - dual_timestamp_get(&m->timestamp); - - m->started = true; - - /* Save new machine data */ - machine_save(m); - - machine_send_signal(m, true); - - return 0; -} - -static int machine_stop_scope(Machine *m) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char *job = NULL; - int r; - - assert(m); - assert(m->class != MACHINE_HOST); - - if (!m->unit) - return 0; - - r = manager_stop_unit(m->manager, m->unit, &error, &job); - if (r < 0) { - log_error("Failed to stop machine scope: %s", bus_error_message(&error, r)); - return r; - } - - free(m->scope_job); - m->scope_job = job; - - return 0; -} - -int machine_stop(Machine *m) { - int r; - assert(m); - - if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM)) - return -EOPNOTSUPP; - - r = machine_stop_scope(m); - - m->stopping = true; - - machine_save(m); - - return r; -} - -int machine_finalize(Machine *m) { - assert(m); - - if (m->started) - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_STOP), - "NAME=%s", m->name, - "LEADER="PID_FMT, m->leader, - LOG_MESSAGE("Machine %s terminated.", m->name), - NULL); - - machine_unlink(m); - machine_add_to_gc_queue(m); - - if (m->started) { - machine_send_signal(m, false); - m->started = false; - } - - return 0; -} - -bool machine_check_gc(Machine *m, bool drop_not_started) { - assert(m); - - if (m->class == MACHINE_HOST) - return true; - - if (drop_not_started && !m->started) - return false; - - if (m->scope_job && manager_job_is_active(m->manager, m->scope_job)) - return true; - - if (m->unit && manager_unit_is_active(m->manager, m->unit)) - return true; - - return false; -} - -void machine_add_to_gc_queue(Machine *m) { - assert(m); - - if (m->in_gc_queue) - return; - - LIST_PREPEND(gc_queue, m->manager->machine_gc_queue, m); - m->in_gc_queue = true; -} - -MachineState machine_get_state(Machine *s) { - assert(s); - - if (s->class == MACHINE_HOST) - return MACHINE_RUNNING; - - if (s->stopping) - return MACHINE_CLOSING; - - if (s->scope_job) - return MACHINE_OPENING; - - return MACHINE_RUNNING; -} - -int machine_kill(Machine *m, KillWho who, int signo) { - assert(m); - - if (!IN_SET(m->class, MACHINE_VM, MACHINE_CONTAINER)) - return -EOPNOTSUPP; - - if (!m->unit) - return -ESRCH; - - if (who == KILL_LEADER) { - /* If we shall simply kill the leader, do so directly */ - - if (kill(m->leader, signo) < 0) - return -errno; - - return 0; - } - - /* Otherwise, make PID 1 do it for us, for the entire cgroup */ - return manager_kill_unit(m->manager, m->unit, signo, NULL); -} - -int machine_openpt(Machine *m, int flags) { - assert(m); - - switch (m->class) { - - case MACHINE_HOST: { - int fd; - - fd = posix_openpt(flags); - if (fd < 0) - return -errno; - - if (unlockpt(fd) < 0) - return -errno; - - return fd; - } - - case MACHINE_CONTAINER: - if (m->leader <= 0) - return -EINVAL; - - return openpt_in_namespace(m->leader, flags); - - default: - return -EOPNOTSUPP; - } -} - -int machine_open_terminal(Machine *m, const char *path, int mode) { - assert(m); - - switch (m->class) { - - case MACHINE_HOST: - return open_terminal(path, mode); - - case MACHINE_CONTAINER: - if (m->leader <= 0) - return -EINVAL; - - return open_terminal_in_namespace(m->leader, path, mode); - - default: - return -EOPNOTSUPP; - } -} - -MachineOperation *machine_operation_unref(MachineOperation *o) { - if (!o) - return NULL; - - sd_event_source_unref(o->event_source); - - safe_close(o->errno_fd); - - if (o->pid > 1) - (void) kill(o->pid, SIGKILL); - - sd_bus_message_unref(o->message); - - if (o->machine) { - LIST_REMOVE(operations, o->machine->operations, o); - o->machine->n_operations--; - } - - free(o); - return NULL; -} - -void machine_release_unit(Machine *m) { - assert(m); - - if (!m->unit) - return; - - (void) hashmap_remove(m->manager->machine_units, m->unit); - m->unit = mfree(m->unit); -} - -static const char* const machine_class_table[_MACHINE_CLASS_MAX] = { - [MACHINE_CONTAINER] = "container", - [MACHINE_VM] = "vm", - [MACHINE_HOST] = "host", -}; - -DEFINE_STRING_TABLE_LOOKUP(machine_class, MachineClass); - -static const char* const machine_state_table[_MACHINE_STATE_MAX] = { - [MACHINE_OPENING] = "opening", - [MACHINE_RUNNING] = "running", - [MACHINE_CLOSING] = "closing" -}; - -DEFINE_STRING_TABLE_LOOKUP(machine_state, MachineState); - -static const char* const kill_who_table[_KILL_WHO_MAX] = { - [KILL_LEADER] = "leader", - [KILL_ALL] = "all" -}; - -DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho); diff --git a/src/machine/machine.h b/src/machine/machine.h deleted file mode 100644 index 1d8cc5911a..0000000000 --- a/src/machine/machine.h +++ /dev/null @@ -1,124 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -typedef struct Machine Machine; -typedef struct MachineOperation MachineOperation; -typedef enum KillWho KillWho; - -#include "list.h" -#include "machined.h" - -typedef enum MachineState { - MACHINE_OPENING, /* Machine is being registered */ - MACHINE_RUNNING, /* Machine is running */ - MACHINE_CLOSING, /* Machine is terminating */ - _MACHINE_STATE_MAX, - _MACHINE_STATE_INVALID = -1 -} MachineState; - -typedef enum MachineClass { - MACHINE_CONTAINER, - MACHINE_VM, - MACHINE_HOST, - _MACHINE_CLASS_MAX, - _MACHINE_CLASS_INVALID = -1 -} MachineClass; - -enum KillWho { - KILL_LEADER, - KILL_ALL, - _KILL_WHO_MAX, - _KILL_WHO_INVALID = -1 -}; - -#define MACHINE_OPERATIONS_MAX 64 - -struct MachineOperation { - Machine *machine; - pid_t pid; - sd_bus_message *message; - int errno_fd; - sd_event_source *event_source; - LIST_FIELDS(MachineOperation, operations); -}; - -struct Machine { - Manager *manager; - - char *name; - sd_id128_t id; - - MachineClass class; - - char *state_file; - char *service; - char *root_directory; - - char *unit; - char *scope_job; - - pid_t leader; - - dual_timestamp timestamp; - - bool in_gc_queue:1; - bool started:1; - bool stopping:1; - - sd_bus_message *create_message; - - int *netif; - unsigned n_netif; - - LIST_FIELDS(Machine, gc_queue); - - MachineOperation *operations; - unsigned n_operations; -}; - -Machine* machine_new(Manager *manager, MachineClass class, const char *name); -void machine_free(Machine *m); -bool machine_check_gc(Machine *m, bool drop_not_started); -void machine_add_to_gc_queue(Machine *m); -int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error); -int machine_stop(Machine *m); -int machine_finalize(Machine *m); -int machine_save(Machine *m); -int machine_load(Machine *m); -int machine_kill(Machine *m, KillWho who, int signo); - -void machine_release_unit(Machine *m); - -MachineState machine_get_state(Machine *u); - -MachineOperation *machine_operation_unref(MachineOperation *o); - -const char* machine_class_to_string(MachineClass t) _const_; -MachineClass machine_class_from_string(const char *s) _pure_; - -const char* machine_state_to_string(MachineState t) _const_; -MachineState machine_state_from_string(const char *s) _pure_; - -const char *kill_who_to_string(KillWho k) _const_; -KillWho kill_who_from_string(const char *s) _pure_; - -int machine_openpt(Machine *m, int flags); -int machine_open_terminal(Machine *m, const char *path, int mode); diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c deleted file mode 100644 index 4853139321..0000000000 --- a/src/machine/machinectl.c +++ /dev/null @@ -1,2678 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "cgroup-show.h" -#include "cgroup-util.h" -#include "copy.h" -#include "env-util.h" -#include "fd-util.h" -#include "hostname-util.h" -#include "import-util.h" -#include "log.h" -#include "logs-show.h" -#include "macro.h" -#include "mkdir.h" -#include "pager.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "ptyfwd.h" -#include "signal-util.h" -#include "spawn-polkit-agent.h" -#include "strv.h" -#include "terminal-util.h" -#include "unit-name.h" -#include "util.h" -#include "verbs.h" -#include "web-util.h" - -static char **arg_property = NULL; -static bool arg_all = false; -static bool arg_full = false; -static bool arg_no_pager = false; -static bool arg_legend = true; -static const char *arg_kill_who = NULL; -static int arg_signal = SIGTERM; -static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; -static char *arg_host = NULL; -static bool arg_read_only = false; -static bool arg_mkdir = false; -static bool arg_quiet = false; -static bool arg_ask_password = true; -static unsigned arg_lines = 10; -static OutputMode arg_output = OUTPUT_SHORT; -static bool arg_force = false; -static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; -static const char* arg_format = NULL; -static const char *arg_uid = NULL; -static char **arg_setenv = NULL; - -static void pager_open_if_enabled(void) { - - if (arg_no_pager) - return; - - pager_open(false); -} - -static void polkit_agent_open_if_enabled(void) { - - /* Open the polkit agent as a child process if necessary */ - - if (!arg_ask_password) - return; - - if (arg_transport != BUS_TRANSPORT_LOCAL) - return; - - polkit_agent_open(); -} - -static OutputFlags get_output_flags(void) { - return - arg_all * OUTPUT_SHOW_ALL | - arg_full * OUTPUT_FULL_WIDTH | - (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | - colors_enabled() * OUTPUT_COLOR | - !arg_quiet * OUTPUT_WARN_CUTOFF; -} - -typedef struct MachineInfo { - const char *name; - const char *class; - const char *service; -} MachineInfo; - -static int compare_machine_info(const void *a, const void *b) { - const MachineInfo *x = a, *y = b; - - return strcmp(x->name, y->name); -} - -static int list_machines(int argc, char *argv[], void *userdata) { - - size_t max_name = strlen("MACHINE"), max_class = strlen("CLASS"), max_service = strlen("SERVICE"); - _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_ MachineInfo *machines = NULL; - const char *name, *class, *service, *object; - size_t n_machines = 0, n_allocated = 0, j; - sd_bus *bus = userdata; - int r; - - assert(bus); - - pager_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "ListMachines", - &error, - &reply, - NULL); - if (r < 0) { - log_error("Could not get machines: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_enter_container(reply, 'a', "(ssso)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) { - size_t l; - - if (name[0] == '.' && !arg_all) - continue; - - if (!GREEDY_REALLOC(machines, n_allocated, n_machines + 1)) - return log_oom(); - - machines[n_machines].name = name; - machines[n_machines].class = class; - machines[n_machines].service = service; - - l = strlen(name); - if (l > max_name) - max_name = l; - - l = strlen(class); - if (l > max_class) - max_class = l; - - l = strlen(service); - if (l > max_service) - max_service = l; - - n_machines ++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - qsort_safe(machines, n_machines, sizeof(MachineInfo), compare_machine_info); - - if (arg_legend) - printf("%-*s %-*s %-*s\n", - (int) max_name, "MACHINE", - (int) max_class, "CLASS", - (int) max_service, "SERVICE"); - - for (j = 0; j < n_machines; j++) - printf("%-*s %-*s %-*s\n", - (int) max_name, machines[j].name, - (int) max_class, machines[j].class, - (int) max_service, machines[j].service); - - if (arg_legend) - printf("\n%zu machines listed.\n", n_machines); - - return 0; -} - -typedef struct ImageInfo { - const char *name; - const char *type; - bool read_only; - usec_t crtime; - usec_t mtime; - uint64_t size; -} ImageInfo; - -static int compare_image_info(const void *a, const void *b) { - const ImageInfo *x = a, *y = b; - - return strcmp(x->name, y->name); -} - -static int list_images(int argc, char *argv[], void *userdata) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - size_t max_name = strlen("NAME"), max_type = strlen("TYPE"), max_size = strlen("USAGE"), max_crtime = strlen("CREATED"), max_mtime = strlen("MODIFIED"); - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ ImageInfo *images = NULL; - size_t n_images = 0, n_allocated = 0, j; - const char *name, *type, *object; - sd_bus *bus = userdata; - uint64_t crtime, mtime, size; - int read_only, r; - - assert(bus); - - pager_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "ListImages", - &error, - &reply, - ""); - if (r < 0) { - log_error("Could not get images: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &read_only, &crtime, &mtime, &size, &object)) > 0) { - char buf[MAX(FORMAT_TIMESTAMP_MAX, FORMAT_BYTES_MAX)]; - size_t l; - - if (name[0] == '.' && !arg_all) - continue; - - if (!GREEDY_REALLOC(images, n_allocated, n_images + 1)) - return log_oom(); - - images[n_images].name = name; - images[n_images].type = type; - images[n_images].read_only = read_only; - images[n_images].crtime = crtime; - images[n_images].mtime = mtime; - images[n_images].size = size; - - l = strlen(name); - if (l > max_name) - max_name = l; - - l = strlen(type); - if (l > max_type) - max_type = l; - - if (crtime != 0) { - l = strlen(strna(format_timestamp(buf, sizeof(buf), crtime))); - if (l > max_crtime) - max_crtime = l; - } - - if (mtime != 0) { - l = strlen(strna(format_timestamp(buf, sizeof(buf), mtime))); - if (l > max_mtime) - max_mtime = l; - } - - if (size != (uint64_t) -1) { - l = strlen(strna(format_bytes(buf, sizeof(buf), size))); - if (l > max_size) - max_size = l; - } - - n_images++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - qsort_safe(images, n_images, sizeof(ImageInfo), compare_image_info); - - if (arg_legend) - printf("%-*s %-*s %-3s %-*s %-*s %-*s\n", - (int) max_name, "NAME", - (int) max_type, "TYPE", - "RO", - (int) max_size, "USAGE", - (int) max_crtime, "CREATED", - (int) max_mtime, "MODIFIED"); - - for (j = 0; j < n_images; j++) { - char crtime_buf[FORMAT_TIMESTAMP_MAX], mtime_buf[FORMAT_TIMESTAMP_MAX], size_buf[FORMAT_BYTES_MAX]; - - printf("%-*s %-*s %s%-3s%s %-*s %-*s %-*s\n", - (int) max_name, images[j].name, - (int) max_type, images[j].type, - images[j].read_only ? ansi_highlight_red() : "", yes_no(images[j].read_only), images[j].read_only ? ansi_normal() : "", - (int) max_size, strna(format_bytes(size_buf, sizeof(size_buf), images[j].size)), - (int) max_crtime, strna(format_timestamp(crtime_buf, sizeof(crtime_buf), images[j].crtime)), - (int) max_mtime, strna(format_timestamp(mtime_buf, sizeof(mtime_buf), images[j].mtime))); - } - - if (arg_legend) - printf("\n%zu images listed.\n", n_images); - - return 0; -} - -static int show_unit_cgroup(sd_bus *bus, const char *unit, pid_t leader) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *path = NULL; - const char *cgroup; - int r; - unsigned c; - - assert(bus); - assert(unit); - - if (arg_transport == BUS_TRANSPORT_REMOTE) - return 0; - - path = unit_dbus_path_from_name(unit); - if (!path) - return log_oom(); - - r = sd_bus_get_property( - bus, - "org.freedesktop.systemd1", - path, - unit_dbus_interface_from_name(unit), - "ControlGroup", - &error, - &reply, - "s"); - if (r < 0) { - log_error("Failed to query ControlGroup: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "s", &cgroup); - if (r < 0) - return bus_log_parse_error(r); - - if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup) != 0 && leader <= 0) - return 0; - - c = columns(); - if (c > 18) - c -= 18; - else - c = 0; - - show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, get_output_flags()); - return 0; -} - -static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *prefix, const char *prefix2) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int r; - - assert(bus); - assert(name); - assert(prefix); - assert(prefix2); - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetMachineAddresses", - NULL, - &reply, - "s", name); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(reply, 'a', "(iay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) { - int family; - const void *a; - size_t sz; - char buffer[MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN)]; - - r = sd_bus_message_read(reply, "i", &family); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - return bus_log_parse_error(r); - - fputs(prefix, stdout); - fputs(inet_ntop(family, a, buffer, sizeof(buffer)), stdout); - if (family == AF_INET6 && ifi > 0) - printf("%%%i", ifi); - fputc('\n', stdout); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (prefix != prefix2) - prefix = prefix2; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - return 0; -} - -static int print_os_release(sd_bus *bus, const char *name, const char *prefix) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *k, *v, *pretty = NULL; - int r; - - assert(bus); - assert(name); - assert(prefix); - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetMachineOSRelease", - NULL, - &reply, - "s", name); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(reply, 'a', "{ss}"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "{ss}", &k, &v)) > 0) { - if (streq(k, "PRETTY_NAME")) - pretty = v; - - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (pretty) - printf("%s%s\n", prefix, pretty); - - return 0; -} - -typedef struct MachineStatusInfo { - char *name; - sd_id128_t id; - char *class; - char *service; - char *unit; - char *root_directory; - pid_t leader; - struct dual_timestamp timestamp; - int *netif; - unsigned n_netif; -} MachineStatusInfo; - -static void machine_status_info_clear(MachineStatusInfo *info) { - if (info) { - free(info->name); - free(info->class); - free(info->service); - free(info->unit); - free(info->root_directory); - free(info->netif); - zero(*info); - } -} - -static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) { - char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; - char since2[FORMAT_TIMESTAMP_MAX], *s2; - int ifi = -1; - - assert(bus); - assert(i); - - fputs(strna(i->name), stdout); - - if (!sd_id128_equal(i->id, SD_ID128_NULL)) - printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id)); - else - putchar('\n'); - - s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp.realtime); - s2 = format_timestamp(since2, sizeof(since2), i->timestamp.realtime); - - if (s1) - printf("\t Since: %s; %s\n", s2, s1); - else if (s2) - printf("\t Since: %s\n", s2); - - if (i->leader > 0) { - _cleanup_free_ char *t = NULL; - - printf("\t Leader: %u", (unsigned) i->leader); - - get_process_comm(i->leader, &t); - if (t) - printf(" (%s)", t); - - putchar('\n'); - } - - if (i->service) { - printf("\t Service: %s", i->service); - - if (i->class) - printf("; class %s", i->class); - - putchar('\n'); - } else if (i->class) - printf("\t Class: %s\n", i->class); - - if (i->root_directory) - printf("\t Root: %s\n", i->root_directory); - - if (i->n_netif > 0) { - unsigned c; - - fputs("\t Iface:", stdout); - - for (c = 0; c < i->n_netif; c++) { - char name[IF_NAMESIZE+1] = ""; - - if (if_indextoname(i->netif[c], name)) { - fputc(' ', stdout); - fputs(name, stdout); - - if (ifi < 0) - ifi = i->netif[c]; - else - ifi = 0; - } else - printf(" %i", i->netif[c]); - } - - fputc('\n', stdout); - } - - print_addresses(bus, i->name, ifi, - "\t Address: ", - "\t "); - - print_os_release(bus, i->name, "\t OS: "); - - if (i->unit) { - printf("\t Unit: %s\n", i->unit); - show_unit_cgroup(bus, i->unit, i->leader); - - if (arg_transport == BUS_TRANSPORT_LOCAL) - - show_journal_by_unit( - stdout, - i->unit, - arg_output, - 0, - i->timestamp.monotonic, - arg_lines, - 0, - get_output_flags() | OUTPUT_BEGIN_NEWLINE, - SD_JOURNAL_LOCAL_ONLY, - true, - NULL); - } -} - -static int map_netif(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { - MachineStatusInfo *i = userdata; - size_t l; - const void *v; - int r; - - assert_cc(sizeof(int32_t) == sizeof(int)); - r = sd_bus_message_read_array(m, SD_BUS_TYPE_INT32, &v, &l); - if (r < 0) - return r; - if (r == 0) - return -EBADMSG; - - i->n_netif = l / sizeof(int32_t); - i->netif = memdup(v, l); - if (!i->netif) - return -ENOMEM; - - return 0; -} - -static int show_machine_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) { - - static const struct bus_properties_map map[] = { - { "Name", "s", NULL, offsetof(MachineStatusInfo, name) }, - { "Class", "s", NULL, offsetof(MachineStatusInfo, class) }, - { "Service", "s", NULL, offsetof(MachineStatusInfo, service) }, - { "Unit", "s", NULL, offsetof(MachineStatusInfo, unit) }, - { "RootDirectory", "s", NULL, offsetof(MachineStatusInfo, root_directory) }, - { "Leader", "u", NULL, offsetof(MachineStatusInfo, leader) }, - { "Timestamp", "t", NULL, offsetof(MachineStatusInfo, timestamp.realtime) }, - { "TimestampMonotonic", "t", NULL, offsetof(MachineStatusInfo, timestamp.monotonic) }, - { "Id", "ay", bus_map_id128, offsetof(MachineStatusInfo, id) }, - { "NetworkInterfaces", "ai", map_netif, 0 }, - {} - }; - - _cleanup_(machine_status_info_clear) MachineStatusInfo info = {}; - int r; - - assert(verb); - assert(bus); - assert(path); - assert(new_line); - - r = bus_map_all_properties(bus, - "org.freedesktop.machine1", - path, - map, - &info); - if (r < 0) - return log_error_errno(r, "Could not get properties: %m"); - - if (*new_line) - printf("\n"); - *new_line = true; - - print_machine_status_info(bus, &info); - - return r; -} - -static int show_machine_properties(sd_bus *bus, const char *path, bool *new_line) { - int r; - - assert(bus); - assert(path); - assert(new_line); - - if (*new_line) - printf("\n"); - - *new_line = true; - - r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all); - if (r < 0) - log_error_errno(r, "Could not get properties: %m"); - - return r; -} - -static int show_machine(int argc, char *argv[], void *userdata) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - bool properties, new_line = false; - sd_bus *bus = userdata; - int r = 0, i; - - assert(bus); - - properties = !strstr(argv[0], "status"); - - pager_open_if_enabled(); - - if (properties && argc <= 1) { - - /* If no argument is specified, inspect the manager - * itself */ - r = show_machine_properties(bus, "/org/freedesktop/machine1", &new_line); - if (r < 0) - return r; - } - - for (i = 1; i < argc; i++) { - const char *path = NULL; - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetMachine", - &error, - &reply, - "s", argv[i]); - if (r < 0) { - log_error("Could not get path to machine: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); - - if (properties) - r = show_machine_properties(bus, path, &new_line); - else - r = show_machine_info(argv[0], bus, path, &new_line); - } - - return r; -} - -typedef struct ImageStatusInfo { - char *name; - char *path; - char *type; - int read_only; - usec_t crtime; - usec_t mtime; - uint64_t usage; - uint64_t limit; - uint64_t usage_exclusive; - uint64_t limit_exclusive; -} ImageStatusInfo; - -static void image_status_info_clear(ImageStatusInfo *info) { - if (info) { - free(info->name); - free(info->path); - free(info->type); - zero(*info); - } -} - -static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) { - char ts_relative[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; - char ts_absolute[FORMAT_TIMESTAMP_MAX], *s2; - char bs[FORMAT_BYTES_MAX], *s3; - char bs_exclusive[FORMAT_BYTES_MAX], *s4; - - assert(bus); - assert(i); - - if (i->name) { - fputs(i->name, stdout); - putchar('\n'); - } - - if (i->type) - printf("\t Type: %s\n", i->type); - - if (i->path) - printf("\t Path: %s\n", i->path); - - printf("\t RO: %s%s%s\n", - i->read_only ? ansi_highlight_red() : "", - i->read_only ? "read-only" : "writable", - i->read_only ? ansi_normal() : ""); - - s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->crtime); - s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->crtime); - if (s1 && s2) - printf("\t Created: %s; %s\n", s2, s1); - else if (s2) - printf("\t Created: %s\n", s2); - - s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->mtime); - s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->mtime); - if (s1 && s2) - printf("\tModified: %s; %s\n", s2, s1); - else if (s2) - printf("\tModified: %s\n", s2); - - s3 = format_bytes(bs, sizeof(bs), i->usage); - s4 = i->usage_exclusive != i->usage ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->usage_exclusive) : NULL; - if (s3 && s4) - printf("\t Usage: %s (exclusive: %s)\n", s3, s4); - else if (s3) - printf("\t Usage: %s\n", s3); - - s3 = format_bytes(bs, sizeof(bs), i->limit); - s4 = i->limit_exclusive != i->limit ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->limit_exclusive) : NULL; - if (s3 && s4) - printf("\t Limit: %s (exclusive: %s)\n", s3, s4); - else if (s3) - printf("\t Limit: %s\n", s3); -} - -static int show_image_info(sd_bus *bus, const char *path, bool *new_line) { - - static const struct bus_properties_map map[] = { - { "Name", "s", NULL, offsetof(ImageStatusInfo, name) }, - { "Path", "s", NULL, offsetof(ImageStatusInfo, path) }, - { "Type", "s", NULL, offsetof(ImageStatusInfo, type) }, - { "ReadOnly", "b", NULL, offsetof(ImageStatusInfo, read_only) }, - { "CreationTimestamp", "t", NULL, offsetof(ImageStatusInfo, crtime) }, - { "ModificationTimestamp", "t", NULL, offsetof(ImageStatusInfo, mtime) }, - { "Usage", "t", NULL, offsetof(ImageStatusInfo, usage) }, - { "Limit", "t", NULL, offsetof(ImageStatusInfo, limit) }, - { "UsageExclusive", "t", NULL, offsetof(ImageStatusInfo, usage_exclusive) }, - { "LimitExclusive", "t", NULL, offsetof(ImageStatusInfo, limit_exclusive) }, - {} - }; - - _cleanup_(image_status_info_clear) ImageStatusInfo info = {}; - int r; - - assert(bus); - assert(path); - assert(new_line); - - r = bus_map_all_properties(bus, - "org.freedesktop.machine1", - path, - map, - &info); - if (r < 0) - return log_error_errno(r, "Could not get properties: %m"); - - if (*new_line) - printf("\n"); - *new_line = true; - - print_image_status_info(bus, &info); - - return r; -} - -typedef struct PoolStatusInfo { - char *path; - uint64_t usage; - uint64_t limit; -} PoolStatusInfo; - -static void pool_status_info_clear(PoolStatusInfo *info) { - if (info) { - free(info->path); - zero(*info); - info->usage = -1; - info->limit = -1; - } -} - -static void print_pool_status_info(sd_bus *bus, PoolStatusInfo *i) { - char bs[FORMAT_BYTES_MAX], *s; - - if (i->path) - printf("\t Path: %s\n", i->path); - - s = format_bytes(bs, sizeof(bs), i->usage); - if (s) - printf("\t Usage: %s\n", s); - - s = format_bytes(bs, sizeof(bs), i->limit); - if (s) - printf("\t Limit: %s\n", s); -} - -static int show_pool_info(sd_bus *bus) { - - static const struct bus_properties_map map[] = { - { "PoolPath", "s", NULL, offsetof(PoolStatusInfo, path) }, - { "PoolUsage", "t", NULL, offsetof(PoolStatusInfo, usage) }, - { "PoolLimit", "t", NULL, offsetof(PoolStatusInfo, limit) }, - {} - }; - - _cleanup_(pool_status_info_clear) PoolStatusInfo info = { - .usage = (uint64_t) -1, - .limit = (uint64_t) -1, - }; - int r; - - assert(bus); - - r = bus_map_all_properties(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - map, - &info); - if (r < 0) - return log_error_errno(r, "Could not get properties: %m"); - - print_pool_status_info(bus, &info); - - return 0; -} - - -static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) { - int r; - - assert(bus); - assert(path); - assert(new_line); - - if (*new_line) - printf("\n"); - - *new_line = true; - - r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all); - if (r < 0) - log_error_errno(r, "Could not get properties: %m"); - - return r; -} - -static int show_image(int argc, char *argv[], void *userdata) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - bool properties, new_line = false; - sd_bus *bus = userdata; - int r = 0, i; - - assert(bus); - - properties = !strstr(argv[0], "status"); - - pager_open_if_enabled(); - - if (argc <= 1) { - - /* If no argument is specified, inspect the manager - * itself */ - - if (properties) - r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line); - else - r = show_pool_info(bus); - if (r < 0) - return r; - } - - for (i = 1; i < argc; i++) { - const char *path = NULL; - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetImage", - &error, - &reply, - "s", argv[i]); - if (r < 0) { - log_error("Could not get path to image: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); - - if (properties) - r = show_image_properties(bus, path, &new_line); - else - r = show_image_info(bus, path, &new_line); - } - - return r; -} - -static int kill_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - if (!arg_kill_who) - arg_kill_who = "all"; - - for (i = 1; i < argc; i++) { - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "KillMachine", - &error, - NULL, - "ssi", argv[i], arg_kill_who, arg_signal); - if (r < 0) { - log_error("Could not kill machine: %s", bus_error_message(&error, -r)); - return r; - } - } - - return 0; -} - -static int reboot_machine(int argc, char *argv[], void *userdata) { - arg_kill_who = "leader"; - arg_signal = SIGINT; /* sysvinit + systemd */ - - return kill_machine(argc, argv, userdata); -} - -static int poweroff_machine(int argc, char *argv[], void *userdata) { - arg_kill_who = "leader"; - arg_signal = SIGRTMIN+4; /* only systemd */ - - return kill_machine(argc, argv, userdata); -} - -static int terminate_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - for (i = 1; i < argc; i++) { - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "TerminateMachine", - &error, - NULL, - "s", argv[i]); - if (r < 0) { - log_error("Could not terminate machine: %s", bus_error_message(&error, -r)); - return r; - } - } - - return 0; -} - -static int copy_files(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *abs_host_path = NULL; - char *dest, *host_path, *container_path; - sd_bus *bus = userdata; - bool copy_from; - int r; - - assert(bus); - - polkit_agent_open_if_enabled(); - - copy_from = streq(argv[0], "copy-from"); - dest = argv[3] ?: argv[2]; - host_path = copy_from ? dest : argv[2]; - container_path = copy_from ? argv[2] : dest; - - if (!path_is_absolute(host_path)) { - r = path_make_absolute_cwd(host_path, &abs_host_path); - if (r < 0) - return log_error_errno(r, "Failed to make path absolute: %m"); - - host_path = abs_host_path; - } - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - copy_from ? "CopyFromMachine" : "CopyToMachine", - &error, - NULL, - "sss", - argv[1], - copy_from ? container_path : host_path, - copy_from ? host_path : container_path); - if (r < 0) - return log_error_errno(r, "Failed to copy: %s", bus_error_message(&error, r)); - - return 0; -} - -static int bind_mount(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r; - - assert(bus); - - polkit_agent_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "BindMountMachine", - &error, - NULL, - "sssbb", - argv[1], - argv[2], - argv[3], - arg_read_only, - arg_mkdir); - if (r < 0) { - log_error("Failed to bind mount: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int on_machine_removed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - PTYForward ** forward = (PTYForward**) userdata; - int r; - - assert(m); - assert(forward); - - if (*forward) { - /* If the forwarder is already initialized, tell it to - * exit on the next vhangup(), so that we still flush - * out what might be queued and exit then. */ - - r = pty_forward_set_ignore_vhangup(*forward, false); - if (r >= 0) - return 0; - - log_error_errno(r, "Failed to set ignore_vhangup flag: %m"); - } - - /* On error, or when the forwarder is not initialized yet, quit immediately */ - sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), EXIT_FAILURE); - return 0; -} - -static int process_forward(sd_event *event, PTYForward **forward, int master, PTYForwardFlags flags, const char *name) { - char last_char = 0; - bool machine_died; - int ret = 0, r; - - assert(event); - assert(master >= 0); - assert(name); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); - - if (streq(name, ".host")) - log_info("Connected to the local host. Press ^] three times within 1s to exit session."); - else - log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name); - - sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); - sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); - - r = pty_forward_new(event, master, flags, forward); - if (r < 0) - return log_error_errno(r, "Failed to create PTY forwarder: %m"); - - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - - pty_forward_get_last_char(*forward, &last_char); - - machine_died = - (flags & PTY_FORWARD_IGNORE_VHANGUP) && - pty_forward_get_ignore_vhangup(*forward) == 0; - - *forward = pty_forward_free(*forward); - - if (last_char != '\n') - fputc('\n', stdout); - - if (machine_died) - log_info("Machine %s terminated.", name); - else if (streq(name, ".host")) - log_info("Connection to the local host terminated."); - else - log_info("Connection to machine %s terminated.", name); - - sd_event_get_exit_code(event, &ret); - return ret; -} - -static int login_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(pty_forward_freep) PTYForward *forward = NULL; - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - int master = -1, r; - sd_bus *bus = userdata; - const char *pty, *match, *machine; - - assert(bus); - - if (!strv_isempty(arg_setenv) || arg_uid) { - log_error("--setenv= and --uid= are not supported for 'login'. Use 'shell' instead."); - return -EINVAL; - } - - if (arg_transport != BUS_TRANSPORT_LOCAL && - arg_transport != BUS_TRANSPORT_MACHINE) { - log_error("Login only supported on local machines."); - return -EOPNOTSUPP; - } - - polkit_agent_open_if_enabled(); - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); - - r = sd_bus_attach_event(bus, event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - machine = argc < 2 || isempty(argv[1]) ? ".host" : argv[1]; - - match = strjoina("type='signal'," - "sender='org.freedesktop.machine1'," - "path='/org/freedesktop/machine1',", - "interface='org.freedesktop.machine1.Manager'," - "member='MachineRemoved'," - "arg0='", machine, "'"); - - r = sd_bus_add_match(bus, &slot, match, on_machine_removed, &forward); - if (r < 0) - return log_error_errno(r, "Failed to add machine removal match: %m"); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "OpenMachineLogin", - &error, - &reply, - "s", machine); - if (r < 0) { - log_error("Failed to get login PTY: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "hs", &master, &pty); - if (r < 0) - return bus_log_parse_error(r); - - return process_forward(event, &forward, master, PTY_FORWARD_IGNORE_VHANGUP, machine); -} - -static int shell_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(pty_forward_freep) PTYForward *forward = NULL; - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - int master = -1, r; - sd_bus *bus = userdata; - const char *pty, *match, *machine, *path, *uid = NULL; - - assert(bus); - - if (arg_transport != BUS_TRANSPORT_LOCAL && - arg_transport != BUS_TRANSPORT_MACHINE) { - log_error("Shell only supported on local machines."); - return -EOPNOTSUPP; - } - - /* Pass $TERM to shell session, if not explicitly specified. */ - if (!strv_find_prefix(arg_setenv, "TERM=")) { - const char *t; - - t = strv_find_prefix(environ, "TERM="); - if (t) { - if (strv_extend(&arg_setenv, t) < 0) - return log_oom(); - } - } - - polkit_agent_open_if_enabled(); - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); - - r = sd_bus_attach_event(bus, event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - machine = argc < 2 || isempty(argv[1]) ? NULL : argv[1]; - - if (arg_uid) - uid = arg_uid; - else if (machine) { - const char *at; - - at = strchr(machine, '@'); - if (at) { - uid = strndupa(machine, at - machine); - machine = at + 1; - } - } - - if (isempty(machine)) - machine = ".host"; - - match = strjoina("type='signal'," - "sender='org.freedesktop.machine1'," - "path='/org/freedesktop/machine1',", - "interface='org.freedesktop.machine1.Manager'," - "member='MachineRemoved'," - "arg0='", machine, "'"); - - r = sd_bus_add_match(bus, &slot, match, on_machine_removed, &forward); - if (r < 0) - return log_error_errno(r, "Failed to add machine removal match: %m"); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "OpenMachineShell"); - if (r < 0) - return bus_log_create_error(r); - - path = argc < 3 || isempty(argv[2]) ? NULL : argv[2]; - - r = sd_bus_message_append(m, "sss", machine, uid, path); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, strv_length(argv) <= 3 ? NULL : argv + 2); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, arg_setenv); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to get shell PTY: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "hs", &master, &pty); - if (r < 0) - return bus_log_parse_error(r); - - return process_forward(event, &forward, master, 0, machine); -} - -static int remove_image(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - for (i = 1; i < argc; i++) { - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "RemoveImage", - &error, - NULL, - "s", argv[i]); - if (r < 0) { - log_error("Could not remove image: %s", bus_error_message(&error, -r)); - return r; - } - } - - return 0; -} - -static int rename_image(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r; - - polkit_agent_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "RenameImage", - &error, - NULL, - "ss", argv[1], argv[2]); - if (r < 0) { - log_error("Could not rename image: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int clone_image(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r; - - polkit_agent_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "CloneImage", - &error, - NULL, - "ssb", argv[1], argv[2], arg_read_only); - if (r < 0) { - log_error("Could not clone image: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int read_only_image(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int b = true, r; - - if (argc > 2) { - b = parse_boolean(argv[2]); - if (b < 0) { - log_error("Failed to parse boolean argument: %s", argv[2]); - return -EINVAL; - } - } - - polkit_agent_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "MarkImageReadOnly", - &error, - NULL, - "sb", argv[1], b); - if (r < 0) { - log_error("Could not mark image read-only: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int make_service_name(const char *name, char **ret) { - _cleanup_free_ char *e = NULL; - int r; - - assert(name); - assert(ret); - - if (!machine_name_is_valid(name)) { - log_error("Invalid machine name %s.", name); - return -EINVAL; - } - - e = unit_name_escape(name); - if (!e) - return log_oom(); - - r = unit_name_build("systemd-nspawn", e, ".service", ret); - if (r < 0) - return log_error_errno(r, "Failed to build unit name: %m"); - - return 0; -} - -static int start_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_oom(); - - for (i = 1; i < argc; i++) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *unit = NULL; - const char *object; - - r = make_service_name(argv[i], &unit); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartUnit", - &error, - &reply, - "ss", unit, "fail"); - if (r < 0) { - log_error("Failed to start unit: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "o", &object); - if (r < 0) - return bus_log_parse_error(r); - - r = bus_wait_for_jobs_add(w, object); - if (r < 0) - return log_oom(); - } - - r = bus_wait_for_jobs(w, arg_quiet, NULL); - if (r < 0) - return r; - - return 0; -} - -static int enable_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int carries_install_info = 0; - const char *method = NULL; - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - method = streq(argv[0], "enable") ? "EnableUnitFiles" : "DisableUnitFiles"; - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - method); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return bus_log_create_error(r); - - for (i = 1; i < argc; i++) { - _cleanup_free_ char *unit = NULL; - - r = make_service_name(argv[i], &unit); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "s", unit); - 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); - - if (streq(argv[0], "enable")) - r = sd_bus_message_append(m, "bb", false, false); - else - r = sd_bus_message_append(m, "b", false); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to enable or disable unit: %s", bus_error_message(&error, -r)); - return r; - } - - if (streq(argv[0], "enable")) { - r = sd_bus_message_read(reply, "b", carries_install_info); - if (r < 0) - return bus_log_parse_error(r); - } - - r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, NULL, NULL); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "Reload", - &error, - NULL, - NULL); - if (r < 0) { - log_error("Failed to reload daemon: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int match_log_message(sd_bus_message *m, void *userdata, sd_bus_error *error) { - const char **our_path = userdata, *line; - unsigned priority; - int r; - - assert(m); - assert(our_path); - - r = sd_bus_message_read(m, "us", &priority, &line); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - if (!streq_ptr(*our_path, sd_bus_message_get_path(m))) - return 0; - - if (arg_quiet && LOG_PRI(priority) >= LOG_INFO) - return 0; - - log_full(priority, "%s", line); - return 0; -} - -static int match_transfer_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { - const char **our_path = userdata, *path, *result; - uint32_t id; - int r; - - assert(m); - assert(our_path); - - r = sd_bus_message_read(m, "uos", &id, &path, &result); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - if (!streq_ptr(*our_path, path)) - return 0; - - sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), !streq_ptr(result, "done")); - return 0; -} - -static int transfer_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - assert(s); - assert(si); - - if (!arg_quiet) - log_info("Continuing download in the background. Use \"machinectl cancel-transfer %" PRIu32 "\" to abort transfer.", PTR_TO_UINT32(userdata)); - - sd_event_exit(sd_event_source_get_event(s), EINTR); - return 0; -} - -static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot_job_removed = NULL, *slot_log_message = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_event_unrefp) sd_event* event = NULL; - const char *path = NULL; - uint32_t id; - int r; - - assert(bus); - assert(m); - - polkit_agent_open_if_enabled(); - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); - - r = sd_bus_attach_event(bus, event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - r = sd_bus_add_match( - bus, - &slot_job_removed, - "type='signal'," - "sender='org.freedesktop.import1'," - "interface='org.freedesktop.import1.Manager'," - "member='TransferRemoved'," - "path='/org/freedesktop/import1'", - match_transfer_removed, &path); - if (r < 0) - return log_error_errno(r, "Failed to install match: %m"); - - r = sd_bus_add_match( - bus, - &slot_log_message, - "type='signal'," - "sender='org.freedesktop.import1'," - "interface='org.freedesktop.import1.Transfer'," - "member='LogMessage'", - match_log_message, &path); - if (r < 0) - return log_error_errno(r, "Failed to install match: %m"); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed transfer image: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "uo", &id, &path); - if (r < 0) - return bus_log_parse_error(r); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - - if (!arg_quiet) - log_info("Enqueued transfer job %u. Press C-c to continue download in background.", id); - - sd_event_add_signal(event, NULL, SIGINT, transfer_signal_handler, UINT32_TO_PTR(id)); - sd_event_add_signal(event, NULL, SIGTERM, transfer_signal_handler, UINT32_TO_PTR(id)); - - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - - return -r; -} - -static int import_tar(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *ll = NULL; - _cleanup_close_ int fd = -1; - const char *local = NULL, *path = NULL; - sd_bus *bus = userdata; - int r; - - assert(bus); - - if (argc >= 2) - path = argv[1]; - if (isempty(path) || streq(path, "-")) - path = NULL; - - if (argc >= 3) - local = argv[2]; - else if (path) - local = basename(path); - if (isempty(local) || streq(local, "-")) - local = NULL; - - if (!local) { - log_error("Need either path or local name."); - return -EINVAL; - } - - r = tar_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!machine_name_is_valid(local)) { - log_error("Local name %s is not a suitable machine name.", local); - return -EINVAL; - } - - if (path) { - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "ImportTar"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "hsbb", - fd >= 0 ? fd : STDIN_FILENO, - local, - arg_force, - arg_read_only); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int import_raw(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *ll = NULL; - _cleanup_close_ int fd = -1; - const char *local = NULL, *path = NULL; - sd_bus *bus = userdata; - int r; - - assert(bus); - - if (argc >= 2) - path = argv[1]; - if (isempty(path) || streq(path, "-")) - path = NULL; - - if (argc >= 3) - local = argv[2]; - else if (path) - local = basename(path); - if (isempty(local) || streq(local, "-")) - local = NULL; - - if (!local) { - log_error("Need either path or local name."); - return -EINVAL; - } - - r = raw_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!machine_name_is_valid(local)) { - log_error("Local name %s is not a suitable machine name.", local); - return -EINVAL; - } - - if (path) { - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "ImportRaw"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "hsbb", - fd >= 0 ? fd : STDIN_FILENO, - local, - arg_force, - arg_read_only); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static void determine_compression_from_filename(const char *p) { - if (arg_format) - return; - - if (!p) - return; - - if (endswith(p, ".xz")) - arg_format = "xz"; - else if (endswith(p, ".gz")) - arg_format = "gzip"; - else if (endswith(p, ".bz2")) - arg_format = "bzip2"; -} - -static int export_tar(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_close_ int fd = -1; - const char *local = NULL, *path = NULL; - sd_bus *bus = userdata; - int r; - - assert(bus); - - local = argv[1]; - if (!machine_name_is_valid(local)) { - log_error("Machine name %s is not valid.", local); - return -EINVAL; - } - - if (argc >= 3) - path = argv[2]; - if (isempty(path) || streq(path, "-")) - path = NULL; - - if (path) { - determine_compression_from_filename(path); - - fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "ExportTar"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "shs", - local, - fd >= 0 ? fd : STDOUT_FILENO, - arg_format); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int export_raw(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_close_ int fd = -1; - const char *local = NULL, *path = NULL; - sd_bus *bus = userdata; - int r; - - assert(bus); - - local = argv[1]; - if (!machine_name_is_valid(local)) { - log_error("Machine name %s is not valid.", local); - return -EINVAL; - } - - if (argc >= 3) - path = argv[2]; - if (isempty(path) || streq(path, "-")) - path = NULL; - - if (path) { - determine_compression_from_filename(path); - - fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "ExportRaw"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "shs", - local, - fd >= 0 ? fd : STDOUT_FILENO, - arg_format); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int pull_tar(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *l = NULL, *ll = NULL; - const char *local, *remote; - sd_bus *bus = userdata; - int r; - - assert(bus); - - remote = argv[1]; - if (!http_url_is_valid(remote)) { - log_error("URL '%s' is not valid.", remote); - return -EINVAL; - } - - if (argc >= 3) - local = argv[2]; - else { - r = import_url_last_component(remote, &l); - if (r < 0) - return log_error_errno(r, "Failed to get final component of URL: %m"); - - local = l; - } - - if (isempty(local) || streq(local, "-")) - local = NULL; - - if (local) { - r = tar_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!machine_name_is_valid(local)) { - log_error("Local name %s is not a suitable machine name.", local); - return -EINVAL; - } - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "PullTar"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sssb", - remote, - local, - import_verify_to_string(arg_verify), - arg_force); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int pull_raw(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *l = NULL, *ll = NULL; - const char *local, *remote; - sd_bus *bus = userdata; - int r; - - assert(bus); - - remote = argv[1]; - if (!http_url_is_valid(remote)) { - log_error("URL '%s' is not valid.", remote); - return -EINVAL; - } - - if (argc >= 3) - local = argv[2]; - else { - r = import_url_last_component(remote, &l); - if (r < 0) - return log_error_errno(r, "Failed to get final component of URL: %m"); - - local = l; - } - - if (isempty(local) || streq(local, "-")) - local = NULL; - - if (local) { - r = raw_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!machine_name_is_valid(local)) { - log_error("Local name %s is not a suitable machine name.", local); - return -EINVAL; - } - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "PullRaw"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sssb", - remote, - local, - import_verify_to_string(arg_verify), - arg_force); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -typedef struct TransferInfo { - uint32_t id; - const char *type; - const char *remote; - const char *local; - double progress; -} TransferInfo; - -static int compare_transfer_info(const void *a, const void *b) { - const TransferInfo *x = a, *y = b; - - return strcmp(x->local, y->local); -} - -static int list_transfers(int argc, char *argv[], void *userdata) { - size_t max_type = strlen("TYPE"), max_local = strlen("LOCAL"), max_remote = strlen("REMOTE"); - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ TransferInfo *transfers = NULL; - size_t n_transfers = 0, n_allocated = 0, j; - const char *type, *remote, *local, *object; - sd_bus *bus = userdata; - uint32_t id, max_id = 0; - double progress; - int r; - - pager_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "ListTransfers", - &error, - &reply, - NULL); - if (r < 0) { - log_error("Could not get transfers: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_enter_container(reply, 'a', "(usssdo)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(usssdo)", &id, &type, &remote, &local, &progress, &object)) > 0) { - size_t l; - - if (!GREEDY_REALLOC(transfers, n_allocated, n_transfers + 1)) - return log_oom(); - - transfers[n_transfers].id = id; - transfers[n_transfers].type = type; - transfers[n_transfers].remote = remote; - transfers[n_transfers].local = local; - transfers[n_transfers].progress = progress; - - l = strlen(type); - if (l > max_type) - max_type = l; - - l = strlen(remote); - if (l > max_remote) - max_remote = l; - - l = strlen(local); - if (l > max_local) - max_local = l; - - if (id > max_id) - max_id = id; - - n_transfers ++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - qsort_safe(transfers, n_transfers, sizeof(TransferInfo), compare_transfer_info); - - if (arg_legend) - printf("%-*s %-*s %-*s %-*s %-*s\n", - (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), "ID", - (int) 7, "PERCENT", - (int) max_type, "TYPE", - (int) max_local, "LOCAL", - (int) max_remote, "REMOTE"); - - for (j = 0; j < n_transfers; j++) - printf("%*" PRIu32 " %*u%% %-*s %-*s %-*s\n", - (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), transfers[j].id, - (int) 6, (unsigned) (transfers[j].progress * 100), - (int) max_type, transfers[j].type, - (int) max_local, transfers[j].local, - (int) max_remote, transfers[j].remote); - - if (arg_legend) - printf("\n%zu transfers listed.\n", n_transfers); - - return 0; -} - -static int cancel_transfer(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - for (i = 1; i < argc; i++) { - uint32_t id; - - r = safe_atou32(argv[i], &id); - if (r < 0) - return log_error_errno(r, "Failed to parse transfer id: %s", argv[i]); - - r = sd_bus_call_method( - bus, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "CancelTransfer", - &error, - NULL, - "u", id); - if (r < 0) { - log_error("Could not cancel transfer: %s", bus_error_message(&error, -r)); - return r; - } - } - - return 0; -} - -static int set_limit(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - uint64_t limit; - int r; - - if (STR_IN_SET(argv[argc-1], "-", "none", "infinity")) - limit = (uint64_t) -1; - else { - r = parse_size(argv[argc-1], 1024, &limit); - if (r < 0) - return log_error("Failed to parse size: %s", argv[argc-1]); - } - - if (argc > 2) - /* With two arguments changes the quota limit of the - * specified image */ - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "SetImageLimit", - &error, - NULL, - "st", argv[1], limit); - else - /* With one argument changes the pool quota limit */ - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "SetPoolLimit", - &error, - NULL, - "t", limit); - - if (r < 0) { - log_error("Could not set limit: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int help(int argc, char *argv[], void *userdata) { - - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Send control commands to or query the virtual machine and container\n" - "registration manager.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --no-ask-password Do not ask for system passwords\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " -p --property=NAME Show only properties by this name\n" - " -q --quiet Suppress output\n" - " -a --all Show all properties, including empty ones\n" - " -l --full Do not ellipsize output\n" - " --kill-who=WHO Who to send signal to\n" - " -s --signal=SIGNAL Which signal to send\n" - " --uid=USER Specify user ID to invoke shell as\n" - " --setenv=VAR=VALUE Add an environment variable for shell\n" - " --read-only Create read-only bind mount\n" - " --mkdir Create directory before bind mounting, if missing\n" - " -n --lines=INTEGER Number of journal entries to show\n" - " -o --output=STRING Change journal output mode (short,\n" - " short-monotonic, verbose, export, json,\n" - " json-pretty, json-sse, cat)\n" - " --verify=MODE Verification mode for downloaded images (no,\n" - " checksum, signature)\n" - " --force Download image even if already exists\n\n" - "Machine Commands:\n" - " list List running VMs and containers\n" - " status NAME... Show VM/container details\n" - " show [NAME...] Show properties of one or more VMs/containers\n" - " start NAME... Start container as a service\n" - " login [NAME] Get a login prompt in a container or on the\n" - " local host\n" - " shell [[USER@]NAME [COMMAND...]]\n" - " Invoke a shell (or other command) in a container\n" - " or on the local host\n" - " enable NAME... Enable automatic container start at boot\n" - " disable NAME... Disable automatic container start at boot\n" - " poweroff NAME... Power off one or more containers\n" - " reboot NAME... Reboot one or more containers\n" - " terminate NAME... Terminate one or more VMs/containers\n" - " kill NAME... Send signal to processes of a VM/container\n" - " copy-to NAME PATH [PATH] Copy files from the host to a container\n" - " copy-from NAME PATH [PATH] Copy files from a container to the host\n" - " bind NAME PATH [PATH] Bind mount a path from the host into a container\n\n" - "Image Commands:\n" - " list-images Show available container and VM images\n" - " image-status [NAME...] Show image details\n" - " show-image [NAME...] Show properties of image\n" - " clone NAME NAME Clone an image\n" - " rename NAME NAME Rename an image\n" - " read-only NAME [BOOL] Mark or unmark image read-only\n" - " remove NAME... Remove an image\n" - " set-limit [NAME] BYTES Set image or pool size limit (disk quota)\n\n" - "Image Transfer Commands:\n" - " pull-tar URL [NAME] Download a TAR container image\n" - " pull-raw URL [NAME] Download a RAW container or VM image\n" - " import-tar FILE [NAME] Import a local TAR container image\n" - " import-raw FILE [NAME] Import a local RAW container or VM image\n" - " export-tar NAME [FILE] Export a TAR container image locally\n" - " export-raw NAME [FILE] Export a RAW container or VM image locally\n" - " list-transfers Show list of downloads in progress\n" - " cancel-transfer Cancel a download\n" - , program_invocation_short_name); - - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_KILL_WHO, - ARG_READ_ONLY, - ARG_MKDIR, - ARG_NO_ASK_PASSWORD, - ARG_VERIFY, - ARG_FORCE, - ARG_FORMAT, - ARG_UID, - ARG_SETENV, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "property", required_argument, NULL, 'p' }, - { "all", no_argument, NULL, 'a' }, - { "full", no_argument, NULL, 'l' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "kill-who", required_argument, NULL, ARG_KILL_WHO }, - { "signal", required_argument, NULL, 's' }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "mkdir", no_argument, NULL, ARG_MKDIR }, - { "quiet", no_argument, NULL, 'q' }, - { "lines", required_argument, NULL, 'n' }, - { "output", required_argument, NULL, 'o' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "verify", required_argument, NULL, ARG_VERIFY }, - { "force", no_argument, NULL, ARG_FORCE }, - { "format", required_argument, NULL, ARG_FORMAT }, - { "uid", required_argument, NULL, ARG_UID }, - { "setenv", required_argument, NULL, ARG_SETENV }, - {} - }; - - int c, r; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "hp:als:H:M:qn:o:", options, NULL)) >= 0) - - switch (c) { - - case 'h': - return help(0, NULL, NULL); - - case ARG_VERSION: - return version(); - - case 'p': - r = strv_extend(&arg_property, optarg); - if (r < 0) - return log_oom(); - - /* If the user asked for a particular - * property, show it to him, even if it is - * empty. */ - arg_all = true; - break; - - case 'a': - arg_all = true; - break; - - case 'l': - arg_full = true; - break; - - case 'n': - if (safe_atou(optarg, &arg_lines) < 0) { - log_error("Failed to parse lines '%s'", optarg); - return -EINVAL; - } - break; - - case 'o': - arg_output = output_mode_from_string(optarg); - if (arg_output < 0) { - log_error("Unknown output '%s'.", optarg); - return -EINVAL; - } - break; - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case ARG_NO_LEGEND: - arg_legend = false; - break; - - case ARG_KILL_WHO: - arg_kill_who = optarg; - break; - - case 's': - arg_signal = signal_from_string_try_harder(optarg); - if (arg_signal < 0) { - log_error("Failed to parse signal string %s.", optarg); - return -EINVAL; - } - break; - - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; - - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; - - case 'M': - arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; - break; - - case ARG_READ_ONLY: - arg_read_only = true; - break; - - case ARG_MKDIR: - arg_mkdir = true; - break; - - case 'q': - arg_quiet = true; - break; - - case ARG_VERIFY: - arg_verify = import_verify_from_string(optarg); - if (arg_verify < 0) { - log_error("Failed to parse --verify= setting: %s", optarg); - return -EINVAL; - } - break; - - case ARG_FORCE: - arg_force = true; - break; - - case ARG_FORMAT: - if (!STR_IN_SET(optarg, "uncompressed", "xz", "gzip", "bzip2")) { - log_error("Unknown format: %s", optarg); - return -EINVAL; - } - - arg_format = optarg; - break; - - case ARG_UID: - arg_uid = optarg; - break; - - case ARG_SETENV: - if (!env_assignment_is_valid(optarg)) { - log_error("Environment assignment invalid: %s", optarg); - return -EINVAL; - } - - r = strv_extend(&arg_setenv, optarg); - if (r < 0) - return log_oom(); - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -static int machinectl_main(int argc, char *argv[], sd_bus *bus) { - - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "list", VERB_ANY, 1, VERB_DEFAULT, list_machines }, - { "list-images", VERB_ANY, 1, 0, list_images }, - { "status", 2, VERB_ANY, 0, show_machine }, - { "image-status", VERB_ANY, VERB_ANY, 0, show_image }, - { "show", VERB_ANY, VERB_ANY, 0, show_machine }, - { "show-image", VERB_ANY, VERB_ANY, 0, show_image }, - { "terminate", 2, VERB_ANY, 0, terminate_machine }, - { "reboot", 2, VERB_ANY, 0, reboot_machine }, - { "poweroff", 2, VERB_ANY, 0, poweroff_machine }, - { "kill", 2, VERB_ANY, 0, kill_machine }, - { "login", VERB_ANY, 2, 0, login_machine }, - { "shell", VERB_ANY, VERB_ANY, 0, shell_machine }, - { "bind", 3, 4, 0, bind_mount }, - { "copy-to", 3, 4, 0, copy_files }, - { "copy-from", 3, 4, 0, copy_files }, - { "remove", 2, VERB_ANY, 0, remove_image }, - { "rename", 3, 3, 0, rename_image }, - { "clone", 3, 3, 0, clone_image }, - { "read-only", 2, 3, 0, read_only_image }, - { "start", 2, VERB_ANY, 0, start_machine }, - { "enable", 2, VERB_ANY, 0, enable_machine }, - { "disable", 2, VERB_ANY, 0, enable_machine }, - { "import-tar", 2, 3, 0, import_tar }, - { "import-raw", 2, 3, 0, import_raw }, - { "export-tar", 2, 3, 0, export_tar }, - { "export-raw", 2, 3, 0, export_raw }, - { "pull-tar", 2, 3, 0, pull_tar }, - { "pull-raw", 2, 3, 0, pull_raw }, - { "list-transfers", VERB_ANY, 1, 0, list_transfers }, - { "cancel-transfer", 2, VERB_ANY, 0, cancel_transfer }, - { "set-limit", 2, 3, 0, set_limit }, - {} - }; - - return dispatch_verb(argc, argv, verbs, bus); -} - -int main(int argc, char*argv[]) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = bus_connect_transport(arg_transport, arg_host, false, &bus); - if (r < 0) { - log_error_errno(r, "Failed to create bus connection: %m"); - goto finish; - } - - sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - - r = machinectl_main(argc, argv, bus); - -finish: - pager_close(); - polkit_agent_close(); - - strv_free(arg_property); - strv_free(arg_setenv); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c deleted file mode 100644 index b933099330..0000000000 --- a/src/machine/machined-dbus.c +++ /dev/null @@ -1,1551 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include -#include - -#include "sd-id128.h" - -#include "alloc-util.h" -#include "btrfs-util.h" -#include "bus-common-errors.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "fd-util.h" -#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" -#include "machined.h" -#include "path-util.h" -#include "process-util.h" -#include "stdio-util.h" -#include "strv.h" -#include "unit-name.h" -#include "user-util.h" - -static int property_get_pool_path( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - assert(bus); - assert(reply); - - return sd_bus_message_append(reply, "s", "/var/lib/machines"); -} - -static int property_get_pool_usage( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - _cleanup_close_ int fd = -1; - uint64_t usage = (uint64_t) -1; - struct stat st; - - assert(bus); - assert(reply); - - /* We try to read the quota info from /var/lib/machines, as - * well as the usage of the loopback file - * /var/lib/machines.raw, and pick the larger value. */ - - fd = open("/var/lib/machines", O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (fd >= 0) { - BtrfsQuotaInfo q; - - if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0) - usage = q.referenced; - } - - if (stat("/var/lib/machines.raw", &st) >= 0) { - if (usage == (uint64_t) -1 || st.st_blocks * 512ULL > usage) - usage = st.st_blocks * 512ULL; - } - - return sd_bus_message_append(reply, "t", usage); -} - -static int property_get_pool_limit( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - _cleanup_close_ int fd = -1; - uint64_t size = (uint64_t) -1; - struct stat st; - - assert(bus); - assert(reply); - - /* We try to read the quota limit from /var/lib/machines, as - * well as the size of the loopback file - * /var/lib/machines.raw, and pick the smaller value. */ - - fd = open("/var/lib/machines", O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (fd >= 0) { - BtrfsQuotaInfo q; - - if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0) - size = q.referenced_max; - } - - if (stat("/var/lib/machines.raw", &st) >= 0) { - if (size == (uint64_t) -1 || (uint64_t) st.st_size < size) - size = st.st_size; - } - - return sd_bus_message_append(reply, "t", size); -} - -static int method_get_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *p = NULL; - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - p = machine_bus_path(machine); - if (!p) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", p); -} - -static int method_get_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *p = NULL; - Manager *m = userdata; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - r = image_find(name, NULL); - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); - if (r < 0) - return r; - - p = image_bus_path(name); - if (!p) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", p); -} - -static int method_get_machine_by_pid(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *p = NULL; - Manager *m = userdata; - Machine *machine = NULL; - pid_t pid; - int r; - - assert(message); - assert(m); - - assert_cc(sizeof(pid_t) == sizeof(uint32_t)); - - r = sd_bus_message_read(message, "u", &pid); - if (r < 0) - return r; - - if (pid < 0) - return -EINVAL; - - if (pid == 0) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r < 0) - return r; - } - - r = manager_get_machine_by_pid(m, pid, &machine); - if (r < 0) - return r; - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_MACHINE_FOR_PID, "PID "PID_FMT" does not belong to any known machine", pid); - - p = machine_bus_path(machine); - if (!p) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", p); -} - -static int method_list_machines(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Manager *m = userdata; - Machine *machine; - Iterator i; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - r = sd_bus_message_open_container(reply, 'a', "(ssso)"); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - HASHMAP_FOREACH(machine, m->machines, i) { - _cleanup_free_ char *p = NULL; - - p = machine_bus_path(machine); - if (!p) - return -ENOMEM; - - r = sd_bus_message_append(reply, "(ssso)", - machine->name, - strempty(machine_class_to_string(machine->class)), - machine->service, - p); - if (r < 0) - return sd_bus_error_set_errno(error, r); - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - return sd_bus_send(NULL, reply, NULL); -} - -static int method_create_or_register_machine(Manager *manager, sd_bus_message *message, bool read_network, Machine **_m, sd_bus_error *error) { - const char *name, *service, *class, *root_directory; - const int32_t *netif = NULL; - MachineClass c; - uint32_t leader; - sd_id128_t id; - const void *v; - Machine *m; - size_t n, n_netif = 0; - int r; - - assert(manager); - assert(message); - assert(_m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - if (!machine_name_is_valid(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine name"); - - r = sd_bus_message_read_array(message, 'y', &v, &n); - if (r < 0) - return r; - if (n == 0) - id = SD_ID128_NULL; - else if (n == 16) - memcpy(&id, v, n); - else - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine ID parameter"); - - r = sd_bus_message_read(message, "ssus", &service, &class, &leader, &root_directory); - if (r < 0) - return r; - - if (read_network) { - size_t i; - - r = sd_bus_message_read_array(message, 'i', (const void**) &netif, &n_netif); - if (r < 0) - return r; - - n_netif /= sizeof(int32_t); - - for (i = 0; i < n_netif; i++) { - if (netif[i] <= 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid network interface index %i", netif[i]); - } - } - - if (isempty(class)) - c = _MACHINE_CLASS_INVALID; - else { - c = machine_class_from_string(class); - if (c < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine class parameter"); - } - - if (leader == 1) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid leader PID"); - - if (!isempty(root_directory) && !path_is_absolute(root_directory)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Root directory must be empty or an absolute path"); - - if (leader == 0) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - assert_cc(sizeof(uint32_t) == sizeof(pid_t)); - - r = sd_bus_creds_get_pid(creds, (pid_t*) &leader); - if (r < 0) - return r; - } - - if (hashmap_get(manager->machines, name)) - return sd_bus_error_setf(error, BUS_ERROR_MACHINE_EXISTS, "Machine '%s' already exists", name); - - r = manager_add_machine(manager, name, &m); - if (r < 0) - return r; - - m->leader = leader; - m->class = c; - m->id = id; - - if (!isempty(service)) { - m->service = strdup(service); - if (!m->service) { - r = -ENOMEM; - goto fail; - } - } - - if (!isempty(root_directory)) { - m->root_directory = strdup(root_directory); - if (!m->root_directory) { - r = -ENOMEM; - goto fail; - } - } - - if (n_netif > 0) { - assert_cc(sizeof(int32_t) == sizeof(int)); - m->netif = memdup(netif, sizeof(int32_t) * n_netif); - if (!m->netif) { - r = -ENOMEM; - goto fail; - } - - m->n_netif = n_netif; - } - - *_m = m; - - return 1; - -fail: - machine_add_to_gc_queue(m); - return r; -} - -static int method_create_machine_internal(sd_bus_message *message, bool read_network, void *userdata, sd_bus_error *error) { - Manager *manager = userdata; - Machine *m = NULL; - int r; - - assert(message); - assert(manager); - - r = method_create_or_register_machine(manager, message, read_network, &m, error); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(message, 'a', "(sv)"); - if (r < 0) - goto fail; - - r = machine_start(m, message, error); - if (r < 0) - goto fail; - - m->create_message = sd_bus_message_ref(message); - return 1; - -fail: - machine_add_to_gc_queue(m); - return r; -} - -static int method_create_machine_with_network(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_create_machine_internal(message, true, userdata, error); -} - -static int method_create_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_create_machine_internal(message, false, userdata, error); -} - -static int method_register_machine_internal(sd_bus_message *message, bool read_network, void *userdata, sd_bus_error *error) { - Manager *manager = userdata; - _cleanup_free_ char *p = NULL; - Machine *m = NULL; - int r; - - assert(message); - assert(manager); - - r = method_create_or_register_machine(manager, message, read_network, &m, error); - if (r < 0) - return r; - - r = cg_pid_get_unit(m->leader, &m->unit); - if (r < 0) { - r = sd_bus_error_set_errnof(error, r, "Failed to determine unit of process "PID_FMT" : %s", m->leader, strerror(-r)); - goto fail; - } - - r = machine_start(m, NULL, error); - if (r < 0) - goto fail; - - p = machine_bus_path(m); - if (!p) { - r = -ENOMEM; - goto fail; - } - - return sd_bus_reply_method_return(message, "o", p); - -fail: - machine_add_to_gc_queue(m); - return r; -} - -static int method_register_machine_with_network(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_register_machine_internal(message, true, userdata, error); -} - -static int method_register_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_register_machine_internal(message, false, userdata, error); -} - -static int method_terminate_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_terminate(message, machine, error); -} - -static int method_kill_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_kill(message, machine, error); -} - -static int method_get_machine_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_get_addresses(message, machine, error); -} - -static int method_get_machine_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_get_os_release(message, machine, error); -} - -static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(image_hashmap_freep) Hashmap *images = NULL; - Manager *m = userdata; - Image *image; - Iterator i; - int r; - - assert(message); - assert(m); - - images = hashmap_new(&string_hash_ops); - if (!images) - return -ENOMEM; - - r = image_discover(images); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(ssbttto)"); - if (r < 0) - return r; - - HASHMAP_FOREACH(image, images, i) { - _cleanup_free_ char *p = NULL; - - p = image_bus_path(image->name); - if (!p) - return -ENOMEM; - - r = sd_bus_message_append(reply, "(ssbttto)", - image->name, - image_type_to_string(image->type), - image->read_only, - image->crtime, - image->mtime, - image->usage, - p); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -static int method_open_machine_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_open_pty(message, machine, error); -} - -static int method_open_machine_login(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_open_login(message, machine, error); -} - -static int method_open_machine_shell(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_open_shell(message, machine, error); -} - -static int method_bind_mount_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_bind_mount(message, machine, error); -} - -static int method_copy_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_copy(message, machine, error); -} - -static int method_remove_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(image_unrefp) Image* i = NULL; - const char *name; - int r; - - assert(message); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - if (!image_name_is_valid(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); - - r = image_find(name, &i); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); - - i->userdata = userdata; - return bus_image_method_remove(message, i, error); -} - -static int method_rename_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(image_unrefp) Image* i = NULL; - const char *old_name; - int r; - - assert(message); - - r = sd_bus_message_read(message, "s", &old_name); - if (r < 0) - return r; - - if (!image_name_is_valid(old_name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name); - - r = image_find(old_name, &i); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name); - - i->userdata = userdata; - return bus_image_method_rename(message, i, error); -} - -static int method_clone_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(image_unrefp) Image *i = NULL; - const char *old_name; - int r; - - assert(message); - - r = sd_bus_message_read(message, "s", &old_name); - if (r < 0) - return r; - - if (!image_name_is_valid(old_name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name); - - r = image_find(old_name, &i); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name); - - i->userdata = userdata; - return bus_image_method_clone(message, i, error); -} - -static int method_mark_image_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(image_unrefp) Image *i = NULL; - const char *name; - int r; - - assert(message); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - if (!image_name_is_valid(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); - - r = image_find(name, &i); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); - - i->userdata = userdata; - return bus_image_method_mark_read_only(message, i, error); -} - -static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - uint64_t limit; - int r; - - assert(message); - - 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, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-machines", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - /* Set up the machine directory if necessary */ - r = setup_machine_directory(limit, error); - if (r < 0) - return r; - - /* 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); - - r = btrfs_subvol_set_subtree_quota_limit("/var/lib/machines", 0, limit); - if (r == -ENOTTY) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs."); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to adjust quota limit: %m"); - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_set_image_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(image_unrefp) Image *i = NULL; - const char *name; - int r; - - assert(message); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - if (!image_name_is_valid(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); - - r = image_find(name, &i); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); - - i->userdata = userdata; - return bus_image_method_set_limit(message, i, error); -} - -static int method_map_from_machine_user(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_fclose_ FILE *f = NULL; - Manager *m = userdata; - const char *name, *p; - Machine *machine; - uint32_t uid; - int r; - - r = sd_bus_message_read(message, "su", &name, &uid); - if (r < 0) - return r; - - if (!uid_is_valid(uid)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - if (machine->class != MACHINE_CONTAINER) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not supported for non-container machines."); - - p = procfs_file_alloca(machine->leader, "uid_map"); - f = fopen(p, "re"); - if (!f) - return -errno; - - for (;;) { - uid_t uid_base, uid_shift, uid_range, converted; - int k; - - errno = 0; - k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range); - if (k < 0 && feof(f)) - break; - if (k != 3) { - if (ferror(f) && errno > 0) - return -errno; - - return -EIO; - } - - if (uid < uid_base || uid >= uid_base + uid_range) - continue; - - converted = uid - uid_base + uid_shift; - if (!uid_is_valid(converted)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); - - return sd_bus_reply_method_return(message, "u", (uint32_t) converted); - } - - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "Machine '%s' has no matching user mappings.", name); -} - -static int method_map_to_machine_user(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - uid_t uid; - Iterator i; - int r; - - r = sd_bus_message_read(message, "u", &uid); - if (r < 0) - return r; - if (!uid_is_valid(uid)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); - if (uid < 0x10000) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "User " UID_FMT " belongs to host UID range", uid); - - HASHMAP_FOREACH(machine, m->machines, i) { - _cleanup_fclose_ FILE *f = NULL; - char p[strlen("/proc//uid_map") + DECIMAL_STR_MAX(pid_t) + 1]; - - if (machine->class != MACHINE_CONTAINER) - continue; - - xsprintf(p, "/proc/" UID_FMT "/uid_map", machine->leader); - f = fopen(p, "re"); - if (!f) { - log_warning_errno(errno, "Failed top open %s, ignoring,", p); - continue; - } - - for (;;) { - _cleanup_free_ char *o = NULL; - uid_t uid_base, uid_shift, uid_range, converted; - int k; - - errno = 0; - k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range); - if (k < 0 && feof(f)) - break; - if (k != 3) { - if (ferror(f) && errno > 0) - return -errno; - - return -EIO; - } - - if (uid < uid_shift || uid >= uid_shift + uid_range) - continue; - - converted = (uid - uid_shift + uid_base); - if (!uid_is_valid(converted)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); - - o = machine_bus_path(machine); - if (!o) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted); - } - } - - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "No matching user mapping for " UID_FMT ".", uid); -} - -static int method_map_from_machine_group(sd_bus_message *message, void *groupdata, sd_bus_error *error) { - _cleanup_fclose_ FILE *f = NULL; - Manager *m = groupdata; - const char *name, *p; - Machine *machine; - uint32_t gid; - int r; - - r = sd_bus_message_read(message, "su", &name, &gid); - if (r < 0) - return r; - - if (!gid_is_valid(gid)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - if (machine->class != MACHINE_CONTAINER) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not supported for non-container machines."); - - p = procfs_file_alloca(machine->leader, "gid_map"); - f = fopen(p, "re"); - if (!f) - return -errno; - - for (;;) { - gid_t gid_base, gid_shift, gid_range, converted; - int k; - - errno = 0; - k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT, &gid_base, &gid_shift, &gid_range); - if (k < 0 && feof(f)) - break; - if (k != 3) { - if (ferror(f) && errno > 0) - return -errno; - - return -EIO; - } - - if (gid < gid_base || gid >= gid_base + gid_range) - continue; - - converted = gid - gid_base + gid_shift; - if (!gid_is_valid(converted)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); - - return sd_bus_reply_method_return(message, "u", (uint32_t) converted); - } - - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "Machine '%s' has no matching group mappings.", name); -} - -static int method_map_to_machine_group(sd_bus_message *message, void *groupdata, sd_bus_error *error) { - Manager *m = groupdata; - Machine *machine; - gid_t gid; - Iterator i; - int r; - - r = sd_bus_message_read(message, "u", &gid); - if (r < 0) - return r; - if (!gid_is_valid(gid)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); - if (gid < 0x10000) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "Group " GID_FMT " belongs to host GID range", gid); - - HASHMAP_FOREACH(machine, m->machines, i) { - _cleanup_fclose_ FILE *f = NULL; - char p[strlen("/proc//gid_map") + DECIMAL_STR_MAX(pid_t) + 1]; - - if (machine->class != MACHINE_CONTAINER) - continue; - - xsprintf(p, "/proc/" GID_FMT "/gid_map", machine->leader); - f = fopen(p, "re"); - if (!f) { - log_warning_errno(errno, "Failed top open %s, ignoring,", p); - continue; - } - - for (;;) { - _cleanup_free_ char *o = NULL; - gid_t gid_base, gid_shift, gid_range, converted; - int k; - - errno = 0; - k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT, &gid_base, &gid_shift, &gid_range); - if (k < 0 && feof(f)) - break; - if (k != 3) { - if (ferror(f) && errno > 0) - return -errno; - - return -EIO; - } - - if (gid < gid_shift || gid >= gid_shift + gid_range) - continue; - - converted = (gid - gid_shift + gid_base); - if (!gid_is_valid(converted)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); - - o = machine_bus_path(machine); - if (!o) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted); - } - } - - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "No matching group mapping for " GID_FMT ".", gid); -} - -const sd_bus_vtable manager_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("PoolPath", "s", property_get_pool_path, 0, 0), - SD_BUS_PROPERTY("PoolUsage", "t", property_get_pool_usage, 0, 0), - SD_BUS_PROPERTY("PoolLimit", "t", property_get_pool_limit, 0, 0), - SD_BUS_METHOD("GetMachine", "s", "o", method_get_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetImage", "s", "o", method_get_image, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetMachineByPID", "u", "o", method_get_machine_by_pid, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListMachines", NULL, "a(ssso)", method_list_machines, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListImages", NULL, "a(ssbttto)", method_list_images, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CreateMachine", "sayssusa(sv)", "o", method_create_machine, 0), - SD_BUS_METHOD("CreateMachineWithNetwork", "sayssusaia(sv)", "o", method_create_machine_with_network, 0), - SD_BUS_METHOD("RegisterMachine", "sayssus", "o", method_register_machine, 0), - SD_BUS_METHOD("RegisterMachineWithNetwork", "sayssusai", "o", method_register_machine_with_network, 0), - SD_BUS_METHOD("TerminateMachine", "s", NULL, method_terminate_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("KillMachine", "ssi", NULL, method_kill_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetMachineAddresses", "s", "a(iay)", method_get_machine_addresses, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetMachineOSRelease", "s", "a{ss}", method_get_machine_os_release, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("OpenMachinePTY", "s", "hs", method_open_machine_pty, 0), - SD_BUS_METHOD("OpenMachineLogin", "s", "hs", method_open_machine_login, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("OpenMachineShell", "sssasas", "hs", method_open_machine_shell, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("BindMountMachine", "sssbb", NULL, method_bind_mount_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CopyFromMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CopyToMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("RemoveImage", "s", NULL, method_remove_image, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("RenameImage", "ss", NULL, method_rename_image, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CloneImage", "ssb", NULL, method_clone_image, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MarkImageReadOnly", "sb", NULL, method_mark_image_read_only, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetPoolLimit", "t", NULL, method_set_pool_limit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetImageLimit", "st", NULL, method_set_image_limit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MapFromMachineUser", "su", "u", method_map_from_machine_user, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MapToMachineUser", "u", "sou", method_map_to_machine_user, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MapFromMachineGroup", "su", "u", method_map_from_machine_group, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MapToMachineGroup", "u", "sou", method_map_to_machine_group, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_SIGNAL("MachineNew", "so", 0), - SD_BUS_SIGNAL("MachineRemoved", "so", 0), - SD_BUS_VTABLE_END -}; - -int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - const char *path, *result, *unit; - Manager *m = userdata; - Machine *machine; - uint32_t id; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "uoss", &id, &path, &unit, &result); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - machine = hashmap_get(m->machine_units, unit); - if (!machine) - return 0; - - if (streq_ptr(path, machine->scope_job)) { - machine->scope_job = mfree(machine->scope_job); - - if (machine->started) { - if (streq(result, "done")) - machine_send_create_reply(machine, NULL); - else { - _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; - - sd_bus_error_setf(&e, BUS_ERROR_JOB_FAILED, "Start job for unit %s failed with '%s'", unit, result); - - machine_send_create_reply(machine, &e); - } - } - - machine_save(machine); - } - - machine_add_to_gc_queue(machine); - return 0; -} - -int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *unit = NULL; - const char *path; - Manager *m = userdata; - Machine *machine; - int r; - - assert(message); - assert(m); - - path = sd_bus_message_get_path(message); - if (!path) - return 0; - - r = unit_name_from_dbus_path(path, &unit); - if (r == -EINVAL) /* not for a unit */ - return 0; - if (r < 0){ - log_oom(); - return 0; - } - - machine = hashmap_get(m->machine_units, unit); - if (!machine) - return 0; - - machine_add_to_gc_queue(machine); - return 0; -} - -int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - const char *path, *unit; - Manager *m = userdata; - Machine *machine; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "so", &unit, &path); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - machine = hashmap_get(m->machine_units, unit); - if (!machine) - return 0; - - machine_add_to_gc_queue(machine); - return 0; -} - -int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - Iterator i; - int b, r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - if (b) - return 0; - - /* systemd finished reloading, let's recheck all our machines */ - log_debug("System manager has been reloaded, rechecking machines..."); - - HASHMAP_FOREACH(machine, m->machines, i) - machine_add_to_gc_queue(machine); - - return 0; -} - -int manager_start_scope( - Manager *manager, - const char *scope, - pid_t pid, - const char *slice, - const char *description, - sd_bus_message *more_properties, - sd_bus_error *error, - char **job) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - int r; - - assert(manager); - assert(scope); - assert(pid > 1); - - r = sd_bus_message_new_method_call( - manager->bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartTransientUnit"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "ss", strempty(scope), "fail"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'a', "(sv)"); - if (r < 0) - return r; - - if (!isempty(slice)) { - r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); - if (r < 0) - return r; - } - - if (!isempty(description)) { - r = sd_bus_message_append(m, "(sv)", "Description", "s", description); - if (r < 0) - return r; - } - - r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, pid); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "(sv)", "Delegate", "b", 1); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", UINT64_C(16384)); - if (r < 0) - return bus_log_create_error(r); - - if (more_properties) { - r = sd_bus_message_copy(m, more_properties, true); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "a(sa(sv))", 0); - if (r < 0) - return r; - - r = sd_bus_call(manager->bus, m, 0, error, &reply); - 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; -} - -int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int r; - - assert(manager); - assert(unit); - - r = sd_bus_call_method( - manager->bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StopUnit", - error, - &reply, - "ss", unit, "fail"); - if (r < 0) { - 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; - - sd_bus_error_free(error); - return 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; -} - -int manager_kill_unit(Manager *manager, const char *unit, int signo, sd_bus_error *error) { - assert(manager); - assert(unit); - - return sd_bus_call_method( - manager->bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "KillUnit", - error, - NULL, - "ssi", unit, "all", signo); -} - -int manager_unit_is_active(Manager *manager, const char *unit) { - _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 *path = NULL; - const char *state; - int r; - - assert(manager); - assert(unit); - - path = unit_dbus_path_from_name(unit); - if (!path) - return -ENOMEM; - - r = sd_bus_get_property( - manager->bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "ActiveState", - &error, - &reply, - "s"); - if (r < 0) { - if (sd_bus_error_has_name(&error, SD_BUS_ERROR_NO_REPLY) || - sd_bus_error_has_name(&error, SD_BUS_ERROR_DISCONNECTED)) - return true; - - if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT) || - sd_bus_error_has_name(&error, BUS_ERROR_LOAD_FAILED)) - return false; - - return r; - } - - r = sd_bus_message_read(reply, "s", &state); - if (r < 0) - return -EINVAL; - - return !STR_IN_SET(state, "inactive", "failed"); -} - -int manager_job_is_active(Manager *manager, const char *path) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int r; - - assert(manager); - assert(path); - - r = sd_bus_get_property( - manager->bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Job", - "State", - &error, - &reply, - "s"); - if (r < 0) { - if (sd_bus_error_has_name(&error, SD_BUS_ERROR_NO_REPLY) || - sd_bus_error_has_name(&error, SD_BUS_ERROR_DISCONNECTED)) - return true; - - if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_OBJECT)) - return false; - - return r; - } - - /* We don't actually care about the state really. The fact - * that we could read the job state is enough for us */ - - return true; -} - -int manager_get_machine_by_pid(Manager *m, pid_t pid, Machine **machine) { - Machine *mm; - int r; - - assert(m); - assert(pid >= 1); - assert(machine); - - mm = hashmap_get(m->machine_leaders, PID_TO_PTR(pid)); - if (!mm) { - _cleanup_free_ char *unit = NULL; - - r = cg_pid_get_unit(pid, &unit); - if (r >= 0) - mm = hashmap_get(m->machine_units, unit); - } - if (!mm) - return 0; - - *machine = mm; - return 1; -} - -int manager_add_machine(Manager *m, const char *name, Machine **_machine) { - Machine *machine; - - assert(m); - assert(name); - - machine = hashmap_get(m->machines, name); - if (!machine) { - machine = machine_new(m, _MACHINE_CLASS_INVALID, name); - if (!machine) - return -ENOMEM; - } - - if (_machine) - *_machine = machine; - - return 0; -} diff --git a/src/machine/machined.c b/src/machine/machined.c deleted file mode 100644 index f2c1966a6b..0000000000 --- a/src/machine/machined.c +++ /dev/null @@ -1,407 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -#include -#include -#include - -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "hostname-util.h" -#include "label.h" -#include "machine-image.h" -#include "machined.h" -#include "signal-util.h" - -Manager *manager_new(void) { - Manager *m; - int r; - - m = new0(Manager, 1); - if (!m) - return NULL; - - m->machines = hashmap_new(&string_hash_ops); - m->machine_units = hashmap_new(&string_hash_ops); - m->machine_leaders = hashmap_new(NULL); - - if (!m->machines || !m->machine_units || !m->machine_leaders) { - manager_free(m); - return NULL; - } - - r = sd_event_default(&m->event); - if (r < 0) { - manager_free(m); - return NULL; - } - - sd_event_set_watchdog(m->event, true); - - return m; -} - -void manager_free(Manager *m) { - Machine *machine; - Image *i; - - assert(m); - - while ((machine = hashmap_first(m->machines))) - machine_free(machine); - - hashmap_free(m->machines); - hashmap_free(m->machine_units); - hashmap_free(m->machine_leaders); - - while ((i = hashmap_steal_first(m->image_cache))) - image_unref(i); - - hashmap_free(m->image_cache); - - sd_event_source_unref(m->image_cache_defer_event); - - bus_verify_polkit_async_registry_free(m->polkit_registry); - - sd_bus_unref(m->bus); - sd_event_unref(m->event); - - free(m); -} - -static int manager_add_host_machine(Manager *m) { - _cleanup_free_ char *rd = NULL, *unit = NULL; - sd_id128_t mid; - Machine *t; - int r; - - if (m->host_machine) - return 0; - - r = sd_id128_get_machine(&mid); - if (r < 0) - return log_error_errno(r, "Failed to get machine ID: %m"); - - rd = strdup("/"); - if (!rd) - return log_oom(); - - unit = strdup("-.slice"); - if (!unit) - return log_oom(); - - t = machine_new(m, MACHINE_HOST, ".host"); - if (!t) - return log_oom(); - - t->leader = 1; - t->id = mid; - - t->root_directory = rd; - t->unit = unit; - rd = unit = NULL; - - dual_timestamp_from_boottime_or_monotonic(&t->timestamp, 0); - - m->host_machine = t; - - return 0; -} - -int manager_enumerate_machines(Manager *m) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - int r = 0; - - assert(m); - - r = manager_add_host_machine(m); - if (r < 0) - return r; - - /* Read in machine data stored on disk */ - d = opendir("/run/systemd/machines"); - if (!d) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open /run/systemd/machines: %m"); - } - - FOREACH_DIRENT(de, d, return -errno) { - struct Machine *machine; - int k; - - if (!dirent_is_file(de)) - continue; - - /* Ignore symlinks that map the unit name to the machine */ - if (startswith(de->d_name, "unit:")) - continue; - - if (!machine_name_is_valid(de->d_name)) - continue; - - k = manager_add_machine(m, de->d_name, &machine); - if (k < 0) { - r = log_error_errno(k, "Failed to add machine by file name %s: %m", de->d_name); - continue; - } - - machine_add_to_gc_queue(machine); - - k = machine_load(machine); - if (k < 0) - r = k; - } - - return r; -} - -static int manager_connect_bus(Manager *m) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(m); - assert(!m->bus); - - r = sd_bus_default_system(&m->bus); - if (r < 0) - return log_error_errno(r, "Failed to connect to system bus: %m"); - - r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", manager_vtable, m); - if (r < 0) - return log_error_errno(r, "Failed to add manager object vtable: %m"); - - r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/machine1/machine", "org.freedesktop.machine1.Machine", machine_vtable, machine_object_find, m); - if (r < 0) - return log_error_errno(r, "Failed to add machine object vtable: %m"); - - r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/machine1/machine", machine_node_enumerator, m); - if (r < 0) - return log_error_errno(r, "Failed to add machine enumerator: %m"); - - r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/machine1/image", "org.freedesktop.machine1.Image", image_vtable, image_object_find, m); - if (r < 0) - return log_error_errno(r, "Failed to add image object vtable: %m"); - - r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/machine1/image", image_node_enumerator, m); - if (r < 0) - return log_error_errno(r, "Failed to add image enumerator: %m"); - - r = sd_bus_add_match(m->bus, - NULL, - "type='signal'," - "sender='org.freedesktop.systemd1'," - "interface='org.freedesktop.systemd1.Manager'," - "member='JobRemoved'," - "path='/org/freedesktop/systemd1'", - match_job_removed, - m); - if (r < 0) - return log_error_errno(r, "Failed to add match for JobRemoved: %m"); - - r = sd_bus_add_match(m->bus, - NULL, - "type='signal'," - "sender='org.freedesktop.systemd1'," - "interface='org.freedesktop.systemd1.Manager'," - "member='UnitRemoved'," - "path='/org/freedesktop/systemd1'", - match_unit_removed, - m); - if (r < 0) - return log_error_errno(r, "Failed to add match for UnitRemoved: %m"); - - r = sd_bus_add_match(m->bus, - NULL, - "type='signal'," - "sender='org.freedesktop.systemd1'," - "interface='org.freedesktop.DBus.Properties'," - "member='PropertiesChanged'," - "arg0='org.freedesktop.systemd1.Unit'", - match_properties_changed, - m); - if (r < 0) - return log_error_errno(r, "Failed to add match for PropertiesChanged: %m"); - - r = sd_bus_add_match(m->bus, - NULL, - "type='signal'," - "sender='org.freedesktop.systemd1'," - "interface='org.freedesktop.systemd1.Manager'," - "member='Reloading'," - "path='/org/freedesktop/systemd1'", - match_reloading, - m); - if (r < 0) - return log_error_errno(r, "Failed to add match for Reloading: %m"); - - r = sd_bus_call_method( - m->bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "Subscribe", - &error, - NULL, NULL); - if (r < 0) { - log_error("Failed to enable subscription: %s", bus_error_message(&error, r)); - return r; - } - - r = sd_bus_request_name(m->bus, "org.freedesktop.machine1", 0); - if (r < 0) - return log_error_errno(r, "Failed to register name: %m"); - - r = sd_bus_attach_event(m->bus, m->event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - return 0; -} - -void manager_gc(Manager *m, bool drop_not_started) { - Machine *machine; - - assert(m); - - while ((machine = m->machine_gc_queue)) { - LIST_REMOVE(gc_queue, m->machine_gc_queue, machine); - machine->in_gc_queue = false; - - /* First, if we are not closing yet, initiate stopping */ - if (!machine_check_gc(machine, drop_not_started) && - machine_get_state(machine) != MACHINE_CLOSING) - machine_stop(machine); - - /* Now, the stop stop probably made this referenced - * again, but if it didn't, then it's time to let it - * go entirely. */ - if (!machine_check_gc(machine, drop_not_started)) { - machine_finalize(machine); - machine_free(machine); - } - } -} - -int manager_startup(Manager *m) { - Machine *machine; - Iterator i; - int r; - - assert(m); - - /* Connect to the bus */ - r = manager_connect_bus(m); - if (r < 0) - return r; - - /* Deserialize state */ - manager_enumerate_machines(m); - - /* Remove stale objects before we start them */ - manager_gc(m, false); - - /* And start everything */ - HASHMAP_FOREACH(machine, m->machines, i) - machine_start(machine, NULL, NULL); - - return 0; -} - -static bool check_idle(void *userdata) { - Manager *m = userdata; - - manager_gc(m, true); - - return hashmap_isempty(m->machines); -} - -int manager_run(Manager *m) { - assert(m); - - return bus_event_loop_with_idle( - m->event, - m->bus, - "org.freedesktop.machine1", - DEFAULT_EXIT_USEC, - check_idle, m); -} - -int main(int argc, char *argv[]) { - Manager *m = NULL; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_set_facility(LOG_AUTH); - log_parse_environment(); - log_open(); - - umask(0022); - - if (argc != 1) { - log_error("This program takes no arguments."); - r = -EINVAL; - goto finish; - } - - /* Always create the directories people can create inotify - * watches in. Note that some applications might check for the - * existence of /run/systemd/machines/ to determine whether - * machined is available, so please always make sure this - * check stays in. */ - mkdir_label("/run/systemd/machines", 0755); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); - - m = manager_new(); - if (!m) { - r = log_oom(); - goto finish; - } - - r = manager_startup(m); - if (r < 0) { - log_error_errno(r, "Failed to fully start up daemon: %m"); - goto finish; - } - - log_debug("systemd-machined running as pid "PID_FMT, getpid()); - - sd_notify(false, - "READY=1\n" - "STATUS=Processing requests..."); - - r = manager_run(m); - - log_debug("systemd-machined stopped as pid "PID_FMT, getpid()); - -finish: - manager_free(m); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/machine/machined.h b/src/machine/machined.h deleted file mode 100644 index e7d7dfdceb..0000000000 --- a/src/machine/machined.h +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -#include - -#include "sd-bus.h" -#include "sd-event.h" - -#include "hashmap.h" -#include "list.h" - -typedef struct Manager Manager; - -#include "image-dbus.h" -#include "machine-dbus.h" -#include "machine.h" - -struct Manager { - sd_event *event; - sd_bus *bus; - - Hashmap *machines; - Hashmap *machine_units; - Hashmap *machine_leaders; - - Hashmap *polkit_registry; - - Hashmap *image_cache; - sd_event_source *image_cache_defer_event; - - LIST_HEAD(Machine, machine_gc_queue); - - Machine *host_machine; -}; - -Manager *manager_new(void); -void manager_free(Manager *m); - -int manager_add_machine(Manager *m, const char *name, Machine **_machine); -int manager_enumerate_machines(Manager *m); - -int manager_startup(Manager *m); -int manager_run(Manager *m); - -void manager_gc(Manager *m, bool drop_not_started); - -int manager_get_machine_by_pid(Manager *m, pid_t pid, Machine **machine); - -extern const sd_bus_vtable manager_vtable[]; - -int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error); -int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error); -int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error); -int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error); - -int manager_start_scope(Manager *manager, const char *scope, pid_t pid, const char *slice, const char *description, sd_bus_message *more_properties, sd_bus_error *error, char **job); -int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job); -int manager_kill_unit(Manager *manager, const char *unit, int signo, sd_bus_error *error); -int manager_unit_is_active(Manager *manager, const char *unit); -int manager_job_is_active(Manager *manager, const char *path); diff --git a/src/machine/org.freedesktop.machine1.conf b/src/machine/org.freedesktop.machine1.conf deleted file mode 100644 index 9d40b90151..0000000000 --- a/src/machine/org.freedesktop.machine1.conf +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/machine/org.freedesktop.machine1.policy.in b/src/machine/org.freedesktop.machine1.policy.in deleted file mode 100644 index 69f78a5c25..0000000000 --- a/src/machine/org.freedesktop.machine1.policy.in +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - The systemd Project - http://www.freedesktop.org/wiki/Software/systemd - - - <_description>Log into a local container - <_message>Authentication is required to log into a local container. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Log into the local host - <_message>Authentication is required to log into the local host. - - auth_admin - auth_admin - yes - - - - - <_description>Acquire a shell in a local container - <_message>Authentication is required to acquire a shell in a local container. - - auth_admin - auth_admin - auth_admin_keep - - org.freedesktop.login1.login - - - - <_description>Acquire a shell on the local host - <_message>Authentication is required to acquire a shell on the local host. - - auth_admin - auth_admin - auth_admin_keep - - org.freedesktop.login1.host-login - - - - <_description>Acquire a pseudo TTY in a local container - <_message>Authentication is required to acquire a pseudo TTY in a local container. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Acquire a pseudo TTY on the local host - <_message>Authentication is required to acquire a pseudo TTY on the local host. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Manage local virtual machines and containers - <_message>Authentication is required to manage local virtual machines and containers. - - auth_admin - auth_admin - auth_admin_keep - - org.freedesktop.login1.shell org.freedesktop.login1.login - - - - <_description>Manage local virtual machine and container images - <_message>Authentication is required to manage local virtual machine and container images. - - auth_admin - auth_admin - auth_admin_keep - - - - diff --git a/src/machine/org.freedesktop.machine1.service b/src/machine/org.freedesktop.machine1.service deleted file mode 100644 index d3dc99852b..0000000000 --- a/src/machine/org.freedesktop.machine1.service +++ /dev/null @@ -1,12 +0,0 @@ -# This file is part of systemd. -# -# 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. - -[D-BUS Service] -Name=org.freedesktop.machine1 -Exec=/bin/false -User=root -SystemdService=dbus-org.freedesktop.machine1.service diff --git a/src/machine/src/.gitignore b/src/machine/src/.gitignore new file mode 100644 index 0000000000..e1065b5894 --- /dev/null +++ b/src/machine/src/.gitignore @@ -0,0 +1 @@ +/org.freedesktop.machine1.policy diff --git a/src/machine/src/Makefile b/src/machine/src/Makefile new file mode 100644 index 0000000000..4586d1d89b --- /dev/null +++ b/src/machine/src/Makefile @@ -0,0 +1,124 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +if ENABLE_MACHINED +systemd_machined_SOURCES = \ + src/machine/machined.c \ + src/machine/machined.h + +systemd_machined_LDADD = \ + libmachine-core.la + +rootlibexec_PROGRAMS += \ + systemd-machined + +libmachine_core_la_SOURCES = \ + src/machine/machine.c \ + src/machine/machine.h \ + src/machine/machined-dbus.c \ + src/machine/machine-dbus.c \ + src/machine/machine-dbus.h \ + src/machine/image-dbus.c \ + src/machine/image-dbus.h + +libmachine_core_la_LIBADD = \ + libshared.la + +noinst_LTLIBRARIES += \ + libmachine-core.la + +machinectl_SOURCES = \ + src/machine/machinectl.c + +machinectl_LDADD = \ + libshared.la + +rootbin_PROGRAMS += \ + machinectl + +test_machine_tables_SOURCES = \ + src/machine/test-machine-tables.c + +test_machine_tables_LDADD = \ + libmachine-core.la + +tests += \ + test-machine-tables + +nodist_systemunit_DATA += \ + units/systemd-machined.service + +dist_systemunit_DATA += \ + units/machine.slice + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.machine1.busname + +dist_dbussystemservice_DATA += \ + src/machine/org.freedesktop.machine1.service + +dist_dbuspolicy_DATA += \ + src/machine/org.freedesktop.machine1.conf + +polkitpolicy_files += \ + src/machine/org.freedesktop.machine1.policy + +dist_bashcompletion_data += \ + shell-completion/bash/machinectl + +dist_zshcompletion_data += \ + shell-completion/zsh/_machinectl \ + shell-completion/zsh/_sd_machines + +SYSTEM_UNIT_ALIASES += \ + systemd-machined.service dbus-org.freedesktop.machine1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.machine1.busname + +libnss_mymachines_la_SOURCES = \ + src/nss-mymachines/nss-mymachines.sym \ + src/nss-mymachines/nss-mymachines.c + +libnss_mymachines_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -module \ + -export-dynamic \ + -avoid-version \ + -shared \ + -shrext .so.2 \ + -Wl,--version-script=$(top_srcdir)/src/nss-mymachines/nss-mymachines.sym + +libnss_mymachines_la_LIBADD = \ + libsystemd-internal.la + +lib_LTLIBRARIES += \ + libnss_mymachines.la + +endif + +polkitpolicy_in_files += \ + src/machine/org.freedesktop.machine1.policy.in + +EXTRA_DIST += \ + units/systemd-machined.service.in + diff --git a/src/machine/src/image-dbus.c b/src/machine/src/image-dbus.c new file mode 100644 index 0000000000..73f5112c4d --- /dev/null +++ b/src/machine/src/image-dbus.c @@ -0,0 +1,361 @@ +/*** + 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 . +***/ + +#include "alloc-util.h" +#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" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, image_type, ImageType); + +int bus_image_method_remove( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + Image *image = userdata; + Manager *m = image->userdata; + int r; + + assert(message); + assert(image); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-images", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = image_remove(image); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_image_method_rename( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + Image *image = userdata; + Manager *m = image->userdata; + const char *new_name; + int r; + + assert(message); + assert(image); + + r = sd_bus_message_read(message, "s", &new_name); + if (r < 0) + return r; + + if (!image_name_is_valid(new_name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-images", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = image_rename(image, new_name); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_image_method_clone( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + Image *image = userdata; + Manager *m = image->userdata; + const char *new_name; + int r, read_only; + + assert(message); + assert(image); + + r = sd_bus_message_read(message, "sb", &new_name, &read_only); + if (r < 0) + return r; + + if (!image_name_is_valid(new_name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-images", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = image_clone(image, new_name, read_only); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_image_method_mark_read_only( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + Image *image = userdata; + Manager *m = image->userdata; + int r, read_only; + + assert(message); + + r = sd_bus_message_read(message, "b", &read_only); + if (r < 0) + return r; + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-images", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = image_read_only(image, read_only); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_image_method_set_limit( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + Image *image = userdata; + Manager *m = image->userdata; + uint64_t limit; + int r; + + assert(message); + + 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, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-images", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = image_set_limit(image, limit); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +const sd_bus_vtable image_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0), + SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Image, path), 0), + SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Image, type), 0), + SD_BUS_PROPERTY("ReadOnly", "b", bus_property_get_bool, offsetof(Image, read_only), 0), + SD_BUS_PROPERTY("CreationTimestamp", "t", NULL, offsetof(Image, crtime), 0), + SD_BUS_PROPERTY("ModificationTimestamp", "t", NULL, offsetof(Image, mtime), 0), + SD_BUS_PROPERTY("Usage", "t", NULL, offsetof(Image, usage), 0), + SD_BUS_PROPERTY("Limit", "t", NULL, offsetof(Image, limit), 0), + SD_BUS_PROPERTY("UsageExclusive", "t", NULL, offsetof(Image, usage_exclusive), 0), + SD_BUS_PROPERTY("LimitExclusive", "t", NULL, offsetof(Image, limit_exclusive), 0), + SD_BUS_METHOD("Remove", NULL, NULL, bus_image_method_remove, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Rename", "s", NULL, bus_image_method_rename, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Clone", "sb", NULL, bus_image_method_clone, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MarkReadOnly", "b", NULL, bus_image_method_mark_read_only, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLimit", "t", NULL, bus_image_method_set_limit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END +}; + +static int image_flush_cache(sd_event_source *s, void *userdata) { + Manager *m = userdata; + Image *i; + + assert(s); + assert(m); + + while ((i = hashmap_steal_first(m->image_cache))) + image_unref(i); + + return 0; +} + +int image_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; + Image *image = NULL; + const char *p; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + + p = startswith(path, "/org/freedesktop/machine1/image/"); + if (!p) + return 0; + + e = bus_label_unescape(p); + if (!e) + return -ENOMEM; + + image = hashmap_get(m->image_cache, e); + if (image) { + *found = image; + return 1; + } + + r = hashmap_ensure_allocated(&m->image_cache, &string_hash_ops); + if (r < 0) + return r; + + if (!m->image_cache_defer_event) { + r = sd_event_add_defer(m->event, &m->image_cache_defer_event, image_flush_cache, m); + if (r < 0) + return r; + + r = sd_event_source_set_priority(m->image_cache_defer_event, SD_EVENT_PRIORITY_IDLE); + if (r < 0) + return r; + } + + r = sd_event_source_set_enabled(m->image_cache_defer_event, SD_EVENT_ONESHOT); + if (r < 0) + return r; + + r = image_find(e, &image); + if (r <= 0) + return r; + + image->userdata = m; + + r = hashmap_put(m->image_cache, image->name, image); + if (r < 0) { + image_unref(image); + return r; + } + + *found = image; + return 1; +} + +char *image_bus_path(const char *name) { + _cleanup_free_ char *e = NULL; + + assert(name); + + e = bus_label_escape(name); + if (!e) + return NULL; + + return strappend("/org/freedesktop/machine1/image/", e); +} + +int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_(image_hashmap_freep) Hashmap *images = NULL; + _cleanup_strv_free_ char **l = NULL; + Image *image; + Iterator i; + int r; + + assert(bus); + assert(path); + assert(nodes); + + images = hashmap_new(&string_hash_ops); + if (!images) + return -ENOMEM; + + r = image_discover(images); + if (r < 0) + return r; + + HASHMAP_FOREACH(image, images, i) { + char *p; + + p = image_bus_path(image->name); + if (!p) + return -ENOMEM; + + r = strv_consume(&l, p); + if (r < 0) + return r; + } + + *nodes = l; + l = NULL; + + return 1; +} diff --git a/src/machine/src/image-dbus.h b/src/machine/src/image-dbus.h new file mode 100644 index 0000000000..b62da996c6 --- /dev/null +++ b/src/machine/src/image-dbus.h @@ -0,0 +1,35 @@ +#pragma once + +/*** + 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 . +***/ + +#include "machined.h" + +extern const sd_bus_vtable image_vtable[]; + +char *image_bus_path(const char *name); + +int image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); +int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); + +int bus_image_method_remove(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_image_method_rename(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_image_method_clone(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_image_method_mark_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_image_method_set_limit(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/machine/src/machine-dbus.c b/src/machine/src/machine-dbus.c new file mode 100644 index 0000000000..71f20b3f07 --- /dev/null +++ b/src/machine/src/machine-dbus.c @@ -0,0 +1,1435 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include +#include + +/* When we include libgen.h because we need dirname() we immediately + * undefine basename() since libgen.h defines it as a macro to the POSIX + * version which is really broken. We prefer GNU basename(). */ +#include +#undef basename + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-internal.h" +#include "bus-label.h" +#include "bus-util.h" +#include "copy.h" +#include "env-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "in-addr-util.h" +#include "local-addresses.h" +#include "machine-dbus.h" +#include "machine.h" +#include "mkdir.h" +#include "path-util.h" +#include "process-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "user-util.h" + +static int property_get_id( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Machine *m = userdata; + + assert(bus); + assert(reply); + assert(m); + + return sd_bus_message_append_array(reply, 'y', &m->id, 16); +} + +static int property_get_state( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Machine *m = userdata; + const char *state; + int r; + + assert(bus); + assert(reply); + assert(m); + + state = machine_state_to_string(machine_get_state(m)); + + r = sd_bus_message_append_basic(reply, 's', state); + if (r < 0) + return r; + + return 1; +} + +static int property_get_netif( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Machine *m = userdata; + + assert(bus); + assert(reply); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + + return sd_bus_message_append_array(reply, 'i', m->netif, m->n_netif * sizeof(int)); +} + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_class, machine_class, MachineClass); + +int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Machine *m = userdata; + int r; + + assert(message); + assert(m); + + r = bus_verify_polkit_async( + message, + CAP_KILL, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = machine_stop(m); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Machine *m = userdata; + const char *swho; + int32_t signo; + KillWho who; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "si", &swho, &signo); + if (r < 0) + return r; + + if (isempty(swho)) + who = KILL_ALL; + else { + who = kill_who_from_string(swho); + if (who < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid kill parameter '%s'", swho); + } + + if (signo <= 0 || signo >= _NSIG) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid signal %i", signo); + + r = bus_verify_polkit_async( + message, + CAP_KILL, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = machine_kill(m, who, signo); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Machine *m = userdata; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(iay)"); + if (r < 0) + return r; + + switch (m->class) { + + case MACHINE_HOST: { + _cleanup_free_ struct local_address *addresses = NULL; + struct local_address *a; + int n, i; + + n = local_addresses(NULL, 0, AF_UNSPEC, &addresses); + if (n < 0) + return n; + + for (a = addresses, i = 0; i < n; a++, i++) { + + r = sd_bus_message_open_container(reply, 'r', "iay"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "i", addresses[i].family); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &addresses[i].address, FAMILY_ADDRESS_SIZE(addresses[i].family)); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + } + + break; + } + + case MACHINE_CONTAINER: { + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + _cleanup_free_ char *us = NULL, *them = NULL; + _cleanup_close_ int netns_fd = -1; + const char *p; + siginfo_t si; + pid_t child; + + r = readlink_malloc("/proc/self/ns/net", &us); + if (r < 0) + return r; + + p = procfs_file_alloca(m->leader, "ns/net"); + r = readlink_malloc(p, &them); + if (r < 0) + return r; + + if (streq(us, them)) + return sd_bus_error_setf(error, BUS_ERROR_NO_PRIVATE_NETWORKING, "Machine %s does not use private networking", m->name); + + r = namespace_open(m->leader, NULL, NULL, &netns_fd, NULL, NULL); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + + if (child == 0) { + _cleanup_free_ struct local_address *addresses = NULL; + struct local_address *a; + int i, n; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(-1, -1, netns_fd, -1, -1); + if (r < 0) + _exit(EXIT_FAILURE); + + n = local_addresses(NULL, 0, AF_UNSPEC, &addresses); + if (n < 0) + _exit(EXIT_FAILURE); + + for (a = addresses, i = 0; i < n; a++, i++) { + struct iovec iov[2] = { + { .iov_base = &a->family, .iov_len = sizeof(a->family) }, + { .iov_base = &a->address, .iov_len = FAMILY_ADDRESS_SIZE(a->family) }, + }; + + r = writev(pair[1], iov, 2); + if (r < 0) + _exit(EXIT_FAILURE); + } + + pair[1] = safe_close(pair[1]); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + for (;;) { + int family; + ssize_t n; + union in_addr_union in_addr; + struct iovec iov[2]; + struct msghdr mh = { + .msg_iov = iov, + .msg_iovlen = 2, + }; + + iov[0] = (struct iovec) { .iov_base = &family, .iov_len = sizeof(family) }; + iov[1] = (struct iovec) { .iov_base = &in_addr, .iov_len = sizeof(in_addr) }; + + n = recvmsg(pair[0], &mh, 0); + if (n < 0) + return -errno; + if ((size_t) n < sizeof(family)) + break; + + r = sd_bus_message_open_container(reply, 'r', "iay"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "i", family); + if (r < 0) + return r; + + switch (family) { + + case AF_INET: + if (n != sizeof(struct in_addr) + sizeof(family)) + return -EIO; + + r = sd_bus_message_append_array(reply, 'y', &in_addr.in, sizeof(in_addr.in)); + break; + + case AF_INET6: + if (n != sizeof(struct in6_addr) + sizeof(family)) + return -EIO; + + r = sd_bus_message_append_array(reply, 'y', &in_addr.in6, sizeof(in_addr.in6)); + break; + } + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + } + + r = wait_for_terminate(child, &si); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); + if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); + break; + } + + default: + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting IP address data is only supported on container machines."); + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_strv_free_ char **l = NULL; + Machine *m = userdata; + char **k, **v; + int r; + + assert(message); + assert(m); + + switch (m->class) { + + case MACHINE_HOST: + r = load_env_file_pairs(NULL, "/etc/os-release", NULL, &l); + if (r < 0) + return r; + + break; + + case MACHINE_CONTAINER: { + _cleanup_close_ int mntns_fd = -1, root_fd = -1; + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + _cleanup_fclose_ FILE *f = NULL; + siginfo_t si; + pid_t child; + + r = namespace_open(m->leader, NULL, &mntns_fd, NULL, NULL, &root_fd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + + if (child == 0) { + _cleanup_close_ int fd = -1; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(-1, mntns_fd, -1, -1, root_fd); + if (r < 0) + _exit(EXIT_FAILURE); + + fd = open("/etc/os-release", O_RDONLY|O_CLOEXEC); + if (fd < 0) { + fd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC); + if (fd < 0) + _exit(EXIT_FAILURE); + } + + r = copy_bytes(fd, pair[1], (uint64_t) -1, false); + if (r < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + f = fdopen(pair[0], "re"); + if (!f) + return -errno; + + pair[0] = -1; + + r = load_env_file_pairs(f, "/etc/os-release", NULL, &l); + if (r < 0) + return r; + + r = wait_for_terminate(child, &si); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); + if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); + + break; + } + + default: + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting OS release data is only supported on container machines."); + } + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "{ss}"); + if (r < 0) + return r; + + STRV_FOREACH_PAIR(k, v, l) { + r = sd_bus_message_append(reply, "{ss}", *k, *v); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *pty_name = NULL; + _cleanup_close_ int master = -1; + Machine *m = userdata; + int r; + + assert(message); + assert(m); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-open-pty" : "org.freedesktop.machine1.open-pty", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (master < 0) + return master; + + r = ptsname_namespace(master, &pty_name); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "hs", master, pty_name); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +static int container_bus_new(Machine *m, sd_bus_error *error, sd_bus **ret) { + int r; + + assert(m); + assert(ret); + + switch (m->class) { + + case MACHINE_HOST: + *ret = NULL; + break; + + case MACHINE_CONTAINER: { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + char *address; + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + if (asprintf(&address, "x-machine-kernel:pid=%1$" PID_PRI ";x-machine-unix:pid=%1$" PID_PRI, m->leader) < 0) + return -ENOMEM; + + bus->address = address; + bus->bus_client = true; + bus->trusted = false; + bus->is_system = true; + + r = sd_bus_start(bus); + if (r == -ENOENT) + return sd_bus_error_set_errnof(error, r, "There is no system bus in container %s.", m->name); + if (r < 0) + return r; + + *ret = bus; + bus = NULL; + break; + } + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *pty_name = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL; + _cleanup_close_ int master = -1; + sd_bus *container_bus = NULL; + Machine *m = userdata; + const char *p, *getty; + int r; + + assert(message); + assert(m); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-login" : "org.freedesktop.machine1.login", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (master < 0) + return master; + + r = ptsname_namespace(master, &pty_name); + if (r < 0) + return r; + + p = path_startswith(pty_name, "/dev/pts/"); + if (!p) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "PTS name %s is invalid", pty_name); + + r = container_bus_new(m, error, &allocated_bus); + if (r < 0) + return r; + + container_bus = allocated_bus ?: m->manager->bus; + + getty = strjoina("container-getty@", p, ".service"); + + r = sd_bus_call_method( + container_bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnit", + error, NULL, + "ss", getty, "replace"); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "hs", master, pty_name); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *tm = NULL; + _cleanup_free_ char *pty_name = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL; + sd_bus *container_bus = NULL; + _cleanup_close_ int master = -1, slave = -1; + _cleanup_strv_free_ char **env = NULL, **args = NULL; + Machine *m = userdata; + const char *p, *unit, *user, *path, *description, *utmp_id; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "ss", &user, &path); + if (r < 0) + return r; + if (isempty(user)) + user = NULL; + if (isempty(path)) + path = "/bin/sh"; + if (!path_is_absolute(path)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified path '%s' is not absolute", path); + + r = sd_bus_message_read_strv(message, &args); + if (r < 0) + return r; + if (strv_isempty(args)) { + args = strv_free(args); + + args = strv_new(path, NULL); + if (!args) + return -ENOMEM; + + args[0][0] = '-'; /* Tell /bin/sh that this shall be a login shell */ + } + + r = sd_bus_message_read_strv(message, &env); + if (r < 0) + return r; + if (!strv_env_is_valid(env)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-shell" : "org.freedesktop.machine1.shell", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (master < 0) + return master; + + r = ptsname_namespace(master, &pty_name); + if (r < 0) + return r; + + p = path_startswith(pty_name, "/dev/pts/"); + assert(p); + + slave = machine_open_terminal(m, pty_name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (slave < 0) + return slave; + + utmp_id = path_startswith(pty_name, "/dev/"); + assert(utmp_id); + + r = container_bus_new(m, error, &allocated_bus); + if (r < 0) + return r; + + container_bus = allocated_bus ?: m->manager->bus; + + r = sd_bus_message_new_method_call( + container_bus, + &tm, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return r; + + /* Name and mode */ + unit = strjoina("container-shell@", p, ".service", NULL); + r = sd_bus_message_append(tm, "ss", unit, "fail"); + if (r < 0) + return r; + + /* Properties */ + r = sd_bus_message_open_container(tm, 'a', "(sv)"); + if (r < 0) + return r; + + description = strjoina("Shell for User ", isempty(user) ? "root" : user); + r = sd_bus_message_append(tm, + "(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)", + "Description", "s", description, + "StandardInputFileDescriptor", "h", slave, + "StandardOutputFileDescriptor", "h", slave, + "StandardErrorFileDescriptor", "h", slave, + "SendSIGHUP", "b", true, + "IgnoreSIGPIPE", "b", false, + "KillMode", "s", "mixed", + "TTYReset", "b", true, + "UtmpIdentifier", "s", utmp_id, + "UtmpMode", "s", "user", + "PAMName", "s", "login", + "WorkingDirectory", "s", "-~"); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "(sv)", "User", "s", isempty(user) ? "root" : user); + if (r < 0) + return r; + + if (!strv_isempty(env)) { + r = sd_bus_message_open_container(tm, 'r', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "s", "Environment"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(tm, 'v', "as"); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(tm, env); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + } + + /* Exec container */ + r = sd_bus_message_open_container(tm, 'r', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "s", "ExecStart"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(tm, 'v', "a(sasb)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(tm, 'a', "(sasb)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(tm, 'r', "sasb"); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "s", path); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(tm, args); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "b", true); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + /* Auxiliary units */ + r = sd_bus_message_append(tm, "a(sa(sv))", 0); + if (r < 0) + return r; + + r = sd_bus_call(container_bus, tm, 0, error, NULL); + if (r < 0) + return r; + + slave = safe_close(slave); + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "hs", master, pty_name); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; + char mount_slave[] = "/tmp/propagate.XXXXXX", *mount_tmp, *mount_outside, *p; + bool mount_slave_created = false, mount_slave_mounted = false, + mount_tmp_created = false, mount_tmp_mounted = false, + mount_outside_created = false, mount_outside_mounted = false; + const char *dest, *src; + Machine *m = userdata; + int read_only, make_directory; + pid_t child; + siginfo_t si; + int r; + + assert(message); + assert(m); + + if (m->class != MACHINE_CONTAINER) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Bind mounting is only supported on container machines."); + + r = sd_bus_message_read(message, "ssbb", &src, &dest, &read_only, &make_directory); + if (r < 0) + return r; + + if (!path_is_absolute(src) || !path_is_safe(src)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and not contain ../."); + + if (isempty(dest)) + dest = src; + else if (!path_is_absolute(dest) || !path_is_safe(dest)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and not contain ../."); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + /* One day, when bind mounting /proc/self/fd/n works across + * namespace boundaries we should rework this logic to make + * use of it... */ + + p = strjoina("/run/systemd/nspawn/propagate/", m->name, "/"); + if (laccess(p, F_OK) < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Container does not allow propagation of mount points."); + + /* Our goal is to install a new bind mount into the container, + possibly read-only. This is irritatingly complex + unfortunately, currently. + + First, we start by creating a private playground in /tmp, + that we can mount MS_SLAVE. (Which is necessary, since + MS_MOVE cannot be applied to mounts with MS_SHARED parent + mounts.) */ + + if (!mkdtemp(mount_slave)) + return sd_bus_error_set_errnof(error, errno, "Failed to create playground %s: %m", mount_slave); + + mount_slave_created = true; + + if (mount(mount_slave, mount_slave, NULL, MS_BIND, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to make bind mount %s: %m", mount_slave); + goto finish; + } + + mount_slave_mounted = true; + + if (mount(NULL, mount_slave, NULL, MS_SLAVE, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to remount slave %s: %m", mount_slave); + goto finish; + } + + /* Second, we mount the source directory to a directory inside + of our MS_SLAVE playground. */ + mount_tmp = strjoina(mount_slave, "/mount"); + if (mkdir(mount_tmp, 0700) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount point %s: %m", mount_tmp); + goto finish; + } + + mount_tmp_created = true; + + if (mount(src, mount_tmp, NULL, MS_BIND, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to overmount %s: %m", mount_tmp); + goto finish; + } + + mount_tmp_mounted = true; + + /* Third, we remount the new bind mount read-only if requested. */ + if (read_only) + if (mount(NULL, mount_tmp, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to remount read-only %s: %m", mount_tmp); + goto finish; + } + + /* Fourth, we move the new bind mount into the propagation + * directory. This way it will appear there read-only + * right-away. */ + + mount_outside = strjoina("/run/systemd/nspawn/propagate/", m->name, "/XXXXXX"); + if (!mkdtemp(mount_outside)) { + r = sd_bus_error_set_errnof(error, errno, "Cannot create propagation directory %s: %m", mount_outside); + goto finish; + } + + mount_outside_created = true; + + if (mount(mount_tmp, mount_outside, NULL, MS_MOVE, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to move %s to %s: %m", mount_tmp, mount_outside); + goto finish; + } + + mount_outside_mounted = true; + mount_tmp_mounted = false; + + (void) rmdir(mount_tmp); + mount_tmp_created = false; + + (void) umount(mount_slave); + mount_slave_mounted = false; + + (void) rmdir(mount_slave); + mount_slave_created = false; + + if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); + goto finish; + } + + child = fork(); + if (child < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + goto finish; + } + + if (child == 0) { + const char *mount_inside; + int mntfd; + const char *q; + + errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); + + q = procfs_file_alloca(m->leader, "ns/mnt"); + mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (mntfd < 0) { + r = log_error_errno(errno, "Failed to open mount namespace of leader: %m"); + goto child_fail; + } + + if (setns(mntfd, CLONE_NEWNS) < 0) { + r = log_error_errno(errno, "Failed to join namespace of leader: %m"); + goto child_fail; + } + + if (make_directory) + (void) mkdir_p(dest, 0755); + + /* Fifth, move the mount to the right place inside */ + mount_inside = strjoina("/run/systemd/nspawn/incoming/", basename(mount_outside)); + if (mount(mount_inside, dest, NULL, MS_MOVE, NULL) < 0) { + r = log_error_errno(errno, "Failed to mount: %m"); + goto child_fail; + } + + _exit(EXIT_SUCCESS); + + child_fail: + (void) write(errno_pipe_fd[1], &r, sizeof(r)); + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + _exit(EXIT_FAILURE); + } + + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + r = wait_for_terminate(child, &si); + if (r < 0) { + r = sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); + goto finish; + } + if (si.si_code != CLD_EXITED) { + r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); + goto finish; + } + if (si.si_status != EXIT_SUCCESS) { + + if (read(errno_pipe_fd[0], &r, sizeof(r)) == sizeof(r)) + r = sd_bus_error_set_errnof(error, r, "Failed to mount: %m"); + else + r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child failed."); + goto finish; + } + + r = sd_bus_reply_method_return(message, NULL); + +finish: + if (mount_outside_mounted) + umount(mount_outside); + if (mount_outside_created) + rmdir(mount_outside); + + if (mount_tmp_mounted) + umount(mount_tmp); + if (mount_tmp_created) + rmdir(mount_tmp); + + if (mount_slave_mounted) + umount(mount_slave); + if (mount_slave_created) + rmdir(mount_slave); + + return r; +} + +static int machine_operation_done(sd_event_source *s, const siginfo_t *si, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + MachineOperation *o = userdata; + int r; + + assert(o); + assert(si); + + o->pid = 0; + + if (si->si_code != CLD_EXITED) { + r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child died abnormally."); + goto fail; + } + + if (si->si_status != EXIT_SUCCESS) { + if (read(o->errno_fd, &r, sizeof(r)) == sizeof(r)) + r = sd_bus_error_set_errnof(&error, r, "%m"); + else + r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child failed."); + + goto fail; + } + + r = sd_bus_reply_method_return(o->message, NULL); + if (r < 0) + log_error_errno(r, "Failed to reply to message: %m"); + + machine_operation_unref(o); + return 0; + +fail: + r = sd_bus_reply_method_error(o->message, &error); + if (r < 0) + log_error_errno(r, "Failed to reply to message: %m"); + + machine_operation_unref(o); + return 0; +} + +int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error) { + const char *src, *dest, *host_path, *container_path, *host_basename, *host_dirname, *container_basename, *container_dirname; + _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; + _cleanup_close_ int hostfd = -1; + Machine *m = userdata; + MachineOperation *o; + bool copy_from; + pid_t child; + char *t; + int r; + + assert(message); + assert(m); + + if (m->n_operations >= MACHINE_OPERATIONS_MAX) + return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing copies."); + + if (m->class != MACHINE_CONTAINER) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Copying files is only supported on container machines."); + + r = sd_bus_message_read(message, "ss", &src, &dest); + if (r < 0) + return r; + + if (!path_is_absolute(src)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute."); + + if (isempty(dest)) + dest = src; + else if (!path_is_absolute(dest)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute."); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + copy_from = strstr(sd_bus_message_get_member(message), "CopyFrom"); + + if (copy_from) { + container_path = src; + host_path = dest; + } else { + host_path = src; + container_path = dest; + } + + host_basename = basename(host_path); + t = strdupa(host_path); + host_dirname = dirname(t); + + container_basename = basename(container_path); + t = strdupa(container_path); + container_dirname = dirname(t); + + hostfd = open(host_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY); + if (hostfd < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to open host directory %s: %m", host_dirname); + + if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); + + child = fork(); + if (child < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + + if (child == 0) { + int containerfd; + const char *q; + int mntfd; + + errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); + + q = procfs_file_alloca(m->leader, "ns/mnt"); + mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (mntfd < 0) { + r = log_error_errno(errno, "Failed to open mount namespace of leader: %m"); + goto child_fail; + } + + if (setns(mntfd, CLONE_NEWNS) < 0) { + r = log_error_errno(errno, "Failed to join namespace of leader: %m"); + goto child_fail; + } + + containerfd = open(container_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY); + if (containerfd < 0) { + r = log_error_errno(errno, "Failed top open destination directory: %m"); + goto child_fail; + } + + if (copy_from) + r = copy_tree_at(containerfd, container_basename, hostfd, host_basename, true); + else + r = copy_tree_at(hostfd, host_basename, containerfd, container_basename, true); + + hostfd = safe_close(hostfd); + containerfd = safe_close(containerfd); + + if (r < 0) { + r = log_error_errno(r, "Failed to copy tree: %m"); + goto child_fail; + } + + _exit(EXIT_SUCCESS); + + child_fail: + (void) write(errno_pipe_fd[1], &r, sizeof(r)); + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + _exit(EXIT_FAILURE); + } + + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + /* Copying might take a while, hence install a watch the + * child, and return */ + + o = new0(MachineOperation, 1); + if (!o) + return log_oom(); + + o->pid = child; + o->message = sd_bus_message_ref(message); + o->errno_fd = errno_pipe_fd[0]; + errno_pipe_fd[0] = -1; + + r = sd_event_add_child(m->manager->event, &o->event_source, child, WEXITED, machine_operation_done, o); + if (r < 0) { + machine_operation_unref(o); + return log_oom(); + } + + LIST_PREPEND(operations, m->operations, o); + m->n_operations++; + o->machine = m; + + return 1; +} + +const sd_bus_vtable machine_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Machine, name), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Id", "ay", property_get_id, 0, SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("Timestamp", offsetof(Machine, timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Service", "s", NULL, offsetof(Machine, service), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Unit", "s", NULL, offsetof(Machine, unit), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Scope", "s", NULL, offsetof(Machine, unit), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("Leader", "u", NULL, offsetof(Machine, leader), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Class", "s", property_get_class, offsetof(Machine, class), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(Machine, root_directory), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NetworkInterfaces", "ai", property_get_netif, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("State", "s", property_get_state, 0, 0), + SD_BUS_METHOD("Terminate", NULL, NULL, bus_machine_method_terminate, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Kill", "si", NULL, bus_machine_method_kill, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetAddresses", NULL, "a(iay)", bus_machine_method_get_addresses, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetOSRelease", NULL, "a{ss}", bus_machine_method_get_os_release, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenPTY", NULL, "hs", bus_machine_method_open_pty, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenLogin", NULL, "hs", bus_machine_method_open_login, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenShell", "ssasas", "hs", bus_machine_method_open_shell, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("BindMount", "ssbb", NULL, bus_machine_method_bind_mount, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CopyFrom", "ss", NULL, bus_machine_method_copy, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CopyTo", "ss", NULL, bus_machine_method_copy, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END +}; + +int machine_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + if (streq(path, "/org/freedesktop/machine1/machine/self")) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + sd_bus_message *message; + pid_t pid; + + message = sd_bus_get_current_message(bus); + if (!message) + return 0; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return r; + + r = manager_get_machine_by_pid(m, pid, &machine); + if (r <= 0) + return 0; + } else { + _cleanup_free_ char *e = NULL; + const char *p; + + p = startswith(path, "/org/freedesktop/machine1/machine/"); + if (!p) + return 0; + + e = bus_label_unescape(p); + if (!e) + return -ENOMEM; + + machine = hashmap_get(m->machines, e); + if (!machine) + return 0; + } + + *found = machine; + return 1; +} + +char *machine_bus_path(Machine *m) { + _cleanup_free_ char *e = NULL; + + assert(m); + + e = bus_label_escape(m->name); + if (!e) + return NULL; + + return strappend("/org/freedesktop/machine1/machine/", e); +} + +int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_strv_free_ char **l = NULL; + Machine *machine = NULL; + Manager *m = userdata; + Iterator i; + int r; + + assert(bus); + assert(path); + assert(nodes); + + HASHMAP_FOREACH(machine, m->machines, i) { + char *p; + + p = machine_bus_path(machine); + if (!p) + return -ENOMEM; + + r = strv_consume(&l, p); + if (r < 0) + return r; + } + + *nodes = l; + l = NULL; + + return 1; +} + +int machine_send_signal(Machine *m, bool new_machine) { + _cleanup_free_ char *p = NULL; + + assert(m); + + p = machine_bus_path(m); + if (!p) + return -ENOMEM; + + return sd_bus_emit_signal( + m->manager->bus, + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + new_machine ? "MachineNew" : "MachineRemoved", + "so", m->name, p); +} + +int machine_send_create_reply(Machine *m, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL; + _cleanup_free_ char *p = NULL; + + assert(m); + + if (!m->create_message) + return 0; + + c = m->create_message; + m->create_message = NULL; + + if (error) + return sd_bus_reply_method_error(c, error); + + /* Update the machine state file before we notify the client + * about the result. */ + machine_save(m); + + p = machine_bus_path(m); + if (!p) + return -ENOMEM; + + return sd_bus_reply_method_return(c, "o", p); +} diff --git a/src/machine/src/machine-dbus.h b/src/machine/src/machine-dbus.h new file mode 100644 index 0000000000..3a8162b171 --- /dev/null +++ b/src/machine/src/machine-dbus.h @@ -0,0 +1,43 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +#include "sd-bus.h" + +#include "machine.h" + +extern const sd_bus_vtable machine_vtable[]; + +char *machine_bus_path(Machine *s); +int machine_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); +int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); + +int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error); + +int machine_send_signal(Machine *m, bool new_machine); +int machine_send_create_reply(Machine *m, sd_bus_error *error); diff --git a/src/machine/src/machine.c b/src/machine/src/machine.c new file mode 100644 index 0000000000..406d5a4b85 --- /dev/null +++ b/src/machine/src/machine.c @@ -0,0 +1,659 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include +#include + +#include "sd-messages.h" + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "escape.h" +#include "extract-word.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "hashmap.h" +#include "machine-dbus.h" +#include "machine.h" +#include "mkdir.h" +#include "parse-util.h" +#include "process-util.h" +#include "special.h" +#include "string-table.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "util.h" + +Machine* machine_new(Manager *manager, MachineClass class, const char *name) { + Machine *m; + + assert(manager); + assert(class < _MACHINE_CLASS_MAX); + assert(name); + + /* Passing class == _MACHINE_CLASS_INVALID here is fine. It + * means as much as "we don't know yet", and that we'll figure + * it out later when loading the state file. */ + + m = new0(Machine, 1); + if (!m) + return NULL; + + m->name = strdup(name); + if (!m->name) + goto fail; + + if (class != MACHINE_HOST) { + m->state_file = strappend("/run/systemd/machines/", m->name); + if (!m->state_file) + goto fail; + } + + m->class = class; + + if (hashmap_put(manager->machines, m->name, m) < 0) + goto fail; + + m->manager = manager; + + return m; + +fail: + free(m->state_file); + free(m->name); + free(m); + + return NULL; +} + +void machine_free(Machine *m) { + assert(m); + + while (m->operations) + machine_operation_unref(m->operations); + + if (m->in_gc_queue) + LIST_REMOVE(gc_queue, m->manager->machine_gc_queue, m); + + machine_release_unit(m); + + free(m->scope_job); + + (void) hashmap_remove(m->manager->machines, m->name); + + if (m->manager->host_machine == m) + m->manager->host_machine = NULL; + + if (m->leader > 0) + (void) hashmap_remove_value(m->manager->machine_leaders, PID_TO_PTR(m->leader), m); + + sd_bus_message_unref(m->create_message); + + free(m->name); + free(m->state_file); + free(m->service); + free(m->root_directory); + free(m->netif); + free(m); +} + +int machine_save(Machine *m) { + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(m); + + if (!m->state_file) + return 0; + + if (!m->started) + return 0; + + r = mkdir_safe_label("/run/systemd/machines", 0755, 0, 0); + if (r < 0) + goto fail; + + r = fopen_temporary(m->state_file, &f, &temp_path); + if (r < 0) + goto fail; + + (void) fchmod(fileno(f), 0644); + + fprintf(f, + "# This is private data. Do not parse.\n" + "NAME=%s\n", + m->name); + + if (m->unit) { + _cleanup_free_ char *escaped; + + escaped = cescape(m->unit); + if (!escaped) { + r = -ENOMEM; + goto fail; + } + + fprintf(f, "SCOPE=%s\n", escaped); /* We continue to call this "SCOPE=" because it is internal only, and we want to stay compatible with old files */ + } + + if (m->scope_job) + fprintf(f, "SCOPE_JOB=%s\n", m->scope_job); + + if (m->service) { + _cleanup_free_ char *escaped; + + escaped = cescape(m->service); + if (!escaped) { + r = -ENOMEM; + goto fail; + } + fprintf(f, "SERVICE=%s\n", escaped); + } + + if (m->root_directory) { + _cleanup_free_ char *escaped; + + escaped = cescape(m->root_directory); + if (!escaped) { + r = -ENOMEM; + goto fail; + } + fprintf(f, "ROOT=%s\n", escaped); + } + + if (!sd_id128_equal(m->id, SD_ID128_NULL)) + fprintf(f, "ID=" SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->id)); + + if (m->leader != 0) + fprintf(f, "LEADER="PID_FMT"\n", m->leader); + + if (m->class != _MACHINE_CLASS_INVALID) + fprintf(f, "CLASS=%s\n", machine_class_to_string(m->class)); + + if (dual_timestamp_is_set(&m->timestamp)) + fprintf(f, + "REALTIME="USEC_FMT"\n" + "MONOTONIC="USEC_FMT"\n", + m->timestamp.realtime, + m->timestamp.monotonic); + + if (m->n_netif > 0) { + unsigned i; + + fputs("NETIF=", f); + + for (i = 0; i < m->n_netif; i++) { + if (i != 0) + fputc(' ', f); + + fprintf(f, "%i", m->netif[i]); + } + + fputc('\n', f); + } + + r = fflush_and_check(f); + if (r < 0) + goto fail; + + if (rename(temp_path, m->state_file) < 0) { + r = -errno; + goto fail; + } + + if (m->unit) { + char *sl; + + /* Create a symlink from the unit name to the machine + * name, so that we can quickly find the machine for + * each given unit. Ignore error. */ + sl = strjoina("/run/systemd/machines/unit:", m->unit); + (void) symlink(m->name, sl); + } + + return 0; + +fail: + (void) unlink(m->state_file); + + if (temp_path) + (void) unlink(temp_path); + + return log_error_errno(r, "Failed to save machine data %s: %m", m->state_file); +} + +static void machine_unlink(Machine *m) { + assert(m); + + if (m->unit) { + + char *sl; + + sl = strjoina("/run/systemd/machines/unit:", m->unit); + (void) unlink(sl); + } + + if (m->state_file) + (void) unlink(m->state_file); +} + +int machine_load(Machine *m) { + _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *id = NULL, *leader = NULL, *class = NULL, *netif = NULL; + int r; + + assert(m); + + if (!m->state_file) + return 0; + + r = parse_env_file(m->state_file, NEWLINE, + "SCOPE", &m->unit, + "SCOPE_JOB", &m->scope_job, + "SERVICE", &m->service, + "ROOT", &m->root_directory, + "ID", &id, + "LEADER", &leader, + "CLASS", &class, + "REALTIME", &realtime, + "MONOTONIC", &monotonic, + "NETIF", &netif, + NULL); + if (r < 0) { + if (r == -ENOENT) + return 0; + + return log_error_errno(r, "Failed to read %s: %m", m->state_file); + } + + if (id) + sd_id128_from_string(id, &m->id); + + if (leader) + parse_pid(leader, &m->leader); + + if (class) { + MachineClass c; + + c = machine_class_from_string(class); + if (c >= 0) + m->class = c; + } + + if (realtime) { + unsigned long long l; + if (sscanf(realtime, "%llu", &l) > 0) + m->timestamp.realtime = l; + } + + if (monotonic) { + unsigned long long l; + if (sscanf(monotonic, "%llu", &l) > 0) + m->timestamp.monotonic = l; + } + + if (netif) { + size_t allocated = 0, nr = 0; + const char *p; + int *ni = NULL; + + p = netif; + for(;;) { + _cleanup_free_ char *word = NULL; + int ifi; + + r = extract_first_word(&p, &word, NULL, 0); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_warning_errno(r, "Failed to parse NETIF: %s", netif); + break; + } + + if (parse_ifindex(word, &ifi) < 0) + continue; + + if (!GREEDY_REALLOC(ni, allocated, nr+1)) { + free(ni); + return log_oom(); + } + + ni[nr++] = ifi; + } + + free(m->netif); + m->netif = ni; + m->n_netif = nr; + } + + return r; +} + +static int machine_start_scope(Machine *m, sd_bus_message *properties, sd_bus_error *error) { + int r = 0; + + assert(m); + assert(m->class != MACHINE_HOST); + + if (!m->unit) { + _cleanup_free_ char *escaped = NULL; + char *scope, *description, *job = NULL; + + escaped = unit_name_escape(m->name); + if (!escaped) + return log_oom(); + + scope = strjoin("machine-", escaped, ".scope", NULL); + if (!scope) + return log_oom(); + + description = strjoina(m->class == MACHINE_VM ? "Virtual Machine " : "Container ", m->name); + + r = manager_start_scope(m->manager, scope, m->leader, SPECIAL_MACHINE_SLICE, description, properties, error, &job); + if (r < 0) { + log_error("Failed to start machine scope: %s", bus_error_message(error, r)); + free(scope); + return r; + } else { + m->unit = scope; + + free(m->scope_job); + m->scope_job = job; + } + } + + if (m->unit) + hashmap_put(m->manager->machine_units, m->unit, m); + + return r; +} + +int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error) { + int r; + + assert(m); + + if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM)) + return -EOPNOTSUPP; + + if (m->started) + return 0; + + r = hashmap_put(m->manager->machine_leaders, PID_TO_PTR(m->leader), m); + if (r < 0) + return r; + + /* Create cgroup */ + r = machine_start_scope(m, properties, error); + if (r < 0) + return r; + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_START), + "NAME=%s", m->name, + "LEADER="PID_FMT, m->leader, + LOG_MESSAGE("New machine %s.", m->name), + NULL); + + if (!dual_timestamp_is_set(&m->timestamp)) + dual_timestamp_get(&m->timestamp); + + m->started = true; + + /* Save new machine data */ + machine_save(m); + + machine_send_signal(m, true); + + return 0; +} + +static int machine_stop_scope(Machine *m) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char *job = NULL; + int r; + + assert(m); + assert(m->class != MACHINE_HOST); + + if (!m->unit) + return 0; + + r = manager_stop_unit(m->manager, m->unit, &error, &job); + if (r < 0) { + log_error("Failed to stop machine scope: %s", bus_error_message(&error, r)); + return r; + } + + free(m->scope_job); + m->scope_job = job; + + return 0; +} + +int machine_stop(Machine *m) { + int r; + assert(m); + + if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM)) + return -EOPNOTSUPP; + + r = machine_stop_scope(m); + + m->stopping = true; + + machine_save(m); + + return r; +} + +int machine_finalize(Machine *m) { + assert(m); + + if (m->started) + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_STOP), + "NAME=%s", m->name, + "LEADER="PID_FMT, m->leader, + LOG_MESSAGE("Machine %s terminated.", m->name), + NULL); + + machine_unlink(m); + machine_add_to_gc_queue(m); + + if (m->started) { + machine_send_signal(m, false); + m->started = false; + } + + return 0; +} + +bool machine_check_gc(Machine *m, bool drop_not_started) { + assert(m); + + if (m->class == MACHINE_HOST) + return true; + + if (drop_not_started && !m->started) + return false; + + if (m->scope_job && manager_job_is_active(m->manager, m->scope_job)) + return true; + + if (m->unit && manager_unit_is_active(m->manager, m->unit)) + return true; + + return false; +} + +void machine_add_to_gc_queue(Machine *m) { + assert(m); + + if (m->in_gc_queue) + return; + + LIST_PREPEND(gc_queue, m->manager->machine_gc_queue, m); + m->in_gc_queue = true; +} + +MachineState machine_get_state(Machine *s) { + assert(s); + + if (s->class == MACHINE_HOST) + return MACHINE_RUNNING; + + if (s->stopping) + return MACHINE_CLOSING; + + if (s->scope_job) + return MACHINE_OPENING; + + return MACHINE_RUNNING; +} + +int machine_kill(Machine *m, KillWho who, int signo) { + assert(m); + + if (!IN_SET(m->class, MACHINE_VM, MACHINE_CONTAINER)) + return -EOPNOTSUPP; + + if (!m->unit) + return -ESRCH; + + if (who == KILL_LEADER) { + /* If we shall simply kill the leader, do so directly */ + + if (kill(m->leader, signo) < 0) + return -errno; + + return 0; + } + + /* Otherwise, make PID 1 do it for us, for the entire cgroup */ + return manager_kill_unit(m->manager, m->unit, signo, NULL); +} + +int machine_openpt(Machine *m, int flags) { + assert(m); + + switch (m->class) { + + case MACHINE_HOST: { + int fd; + + fd = posix_openpt(flags); + if (fd < 0) + return -errno; + + if (unlockpt(fd) < 0) + return -errno; + + return fd; + } + + case MACHINE_CONTAINER: + if (m->leader <= 0) + return -EINVAL; + + return openpt_in_namespace(m->leader, flags); + + default: + return -EOPNOTSUPP; + } +} + +int machine_open_terminal(Machine *m, const char *path, int mode) { + assert(m); + + switch (m->class) { + + case MACHINE_HOST: + return open_terminal(path, mode); + + case MACHINE_CONTAINER: + if (m->leader <= 0) + return -EINVAL; + + return open_terminal_in_namespace(m->leader, path, mode); + + default: + return -EOPNOTSUPP; + } +} + +MachineOperation *machine_operation_unref(MachineOperation *o) { + if (!o) + return NULL; + + sd_event_source_unref(o->event_source); + + safe_close(o->errno_fd); + + if (o->pid > 1) + (void) kill(o->pid, SIGKILL); + + sd_bus_message_unref(o->message); + + if (o->machine) { + LIST_REMOVE(operations, o->machine->operations, o); + o->machine->n_operations--; + } + + free(o); + return NULL; +} + +void machine_release_unit(Machine *m) { + assert(m); + + if (!m->unit) + return; + + (void) hashmap_remove(m->manager->machine_units, m->unit); + m->unit = mfree(m->unit); +} + +static const char* const machine_class_table[_MACHINE_CLASS_MAX] = { + [MACHINE_CONTAINER] = "container", + [MACHINE_VM] = "vm", + [MACHINE_HOST] = "host", +}; + +DEFINE_STRING_TABLE_LOOKUP(machine_class, MachineClass); + +static const char* const machine_state_table[_MACHINE_STATE_MAX] = { + [MACHINE_OPENING] = "opening", + [MACHINE_RUNNING] = "running", + [MACHINE_CLOSING] = "closing" +}; + +DEFINE_STRING_TABLE_LOOKUP(machine_state, MachineState); + +static const char* const kill_who_table[_KILL_WHO_MAX] = { + [KILL_LEADER] = "leader", + [KILL_ALL] = "all" +}; + +DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho); diff --git a/src/machine/src/machine.h b/src/machine/src/machine.h new file mode 100644 index 0000000000..1d8cc5911a --- /dev/null +++ b/src/machine/src/machine.h @@ -0,0 +1,124 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +typedef struct Machine Machine; +typedef struct MachineOperation MachineOperation; +typedef enum KillWho KillWho; + +#include "list.h" +#include "machined.h" + +typedef enum MachineState { + MACHINE_OPENING, /* Machine is being registered */ + MACHINE_RUNNING, /* Machine is running */ + MACHINE_CLOSING, /* Machine is terminating */ + _MACHINE_STATE_MAX, + _MACHINE_STATE_INVALID = -1 +} MachineState; + +typedef enum MachineClass { + MACHINE_CONTAINER, + MACHINE_VM, + MACHINE_HOST, + _MACHINE_CLASS_MAX, + _MACHINE_CLASS_INVALID = -1 +} MachineClass; + +enum KillWho { + KILL_LEADER, + KILL_ALL, + _KILL_WHO_MAX, + _KILL_WHO_INVALID = -1 +}; + +#define MACHINE_OPERATIONS_MAX 64 + +struct MachineOperation { + Machine *machine; + pid_t pid; + sd_bus_message *message; + int errno_fd; + sd_event_source *event_source; + LIST_FIELDS(MachineOperation, operations); +}; + +struct Machine { + Manager *manager; + + char *name; + sd_id128_t id; + + MachineClass class; + + char *state_file; + char *service; + char *root_directory; + + char *unit; + char *scope_job; + + pid_t leader; + + dual_timestamp timestamp; + + bool in_gc_queue:1; + bool started:1; + bool stopping:1; + + sd_bus_message *create_message; + + int *netif; + unsigned n_netif; + + LIST_FIELDS(Machine, gc_queue); + + MachineOperation *operations; + unsigned n_operations; +}; + +Machine* machine_new(Manager *manager, MachineClass class, const char *name); +void machine_free(Machine *m); +bool machine_check_gc(Machine *m, bool drop_not_started); +void machine_add_to_gc_queue(Machine *m); +int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error); +int machine_stop(Machine *m); +int machine_finalize(Machine *m); +int machine_save(Machine *m); +int machine_load(Machine *m); +int machine_kill(Machine *m, KillWho who, int signo); + +void machine_release_unit(Machine *m); + +MachineState machine_get_state(Machine *u); + +MachineOperation *machine_operation_unref(MachineOperation *o); + +const char* machine_class_to_string(MachineClass t) _const_; +MachineClass machine_class_from_string(const char *s) _pure_; + +const char* machine_state_to_string(MachineState t) _const_; +MachineState machine_state_from_string(const char *s) _pure_; + +const char *kill_who_to_string(KillWho k) _const_; +KillWho kill_who_from_string(const char *s) _pure_; + +int machine_openpt(Machine *m, int flags); +int machine_open_terminal(Machine *m, const char *path, int mode); diff --git a/src/machine/src/machinectl.c b/src/machine/src/machinectl.c new file mode 100644 index 0000000000..4853139321 --- /dev/null +++ b/src/machine/src/machinectl.c @@ -0,0 +1,2678 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sd-bus.h" + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "cgroup-show.h" +#include "cgroup-util.h" +#include "copy.h" +#include "env-util.h" +#include "fd-util.h" +#include "hostname-util.h" +#include "import-util.h" +#include "log.h" +#include "logs-show.h" +#include "macro.h" +#include "mkdir.h" +#include "pager.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "ptyfwd.h" +#include "signal-util.h" +#include "spawn-polkit-agent.h" +#include "strv.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "util.h" +#include "verbs.h" +#include "web-util.h" + +static char **arg_property = NULL; +static bool arg_all = false; +static bool arg_full = false; +static bool arg_no_pager = false; +static bool arg_legend = true; +static const char *arg_kill_who = NULL; +static int arg_signal = SIGTERM; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static char *arg_host = NULL; +static bool arg_read_only = false; +static bool arg_mkdir = false; +static bool arg_quiet = false; +static bool arg_ask_password = true; +static unsigned arg_lines = 10; +static OutputMode arg_output = OUTPUT_SHORT; +static bool arg_force = false; +static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; +static const char* arg_format = NULL; +static const char *arg_uid = NULL; +static char **arg_setenv = NULL; + +static void pager_open_if_enabled(void) { + + if (arg_no_pager) + return; + + pager_open(false); +} + +static void polkit_agent_open_if_enabled(void) { + + /* Open the polkit agent as a child process if necessary */ + + if (!arg_ask_password) + return; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return; + + polkit_agent_open(); +} + +static OutputFlags get_output_flags(void) { + return + arg_all * OUTPUT_SHOW_ALL | + arg_full * OUTPUT_FULL_WIDTH | + (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | + colors_enabled() * OUTPUT_COLOR | + !arg_quiet * OUTPUT_WARN_CUTOFF; +} + +typedef struct MachineInfo { + const char *name; + const char *class; + const char *service; +} MachineInfo; + +static int compare_machine_info(const void *a, const void *b) { + const MachineInfo *x = a, *y = b; + + return strcmp(x->name, y->name); +} + +static int list_machines(int argc, char *argv[], void *userdata) { + + size_t max_name = strlen("MACHINE"), max_class = strlen("CLASS"), max_service = strlen("SERVICE"); + _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_ MachineInfo *machines = NULL; + const char *name, *class, *service, *object; + size_t n_machines = 0, n_allocated = 0, j; + sd_bus *bus = userdata; + int r; + + assert(bus); + + pager_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "ListMachines", + &error, + &reply, + NULL); + if (r < 0) { + log_error("Could not get machines: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_enter_container(reply, 'a', "(ssso)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) { + size_t l; + + if (name[0] == '.' && !arg_all) + continue; + + if (!GREEDY_REALLOC(machines, n_allocated, n_machines + 1)) + return log_oom(); + + machines[n_machines].name = name; + machines[n_machines].class = class; + machines[n_machines].service = service; + + l = strlen(name); + if (l > max_name) + max_name = l; + + l = strlen(class); + if (l > max_class) + max_class = l; + + l = strlen(service); + if (l > max_service) + max_service = l; + + n_machines ++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + qsort_safe(machines, n_machines, sizeof(MachineInfo), compare_machine_info); + + if (arg_legend) + printf("%-*s %-*s %-*s\n", + (int) max_name, "MACHINE", + (int) max_class, "CLASS", + (int) max_service, "SERVICE"); + + for (j = 0; j < n_machines; j++) + printf("%-*s %-*s %-*s\n", + (int) max_name, machines[j].name, + (int) max_class, machines[j].class, + (int) max_service, machines[j].service); + + if (arg_legend) + printf("\n%zu machines listed.\n", n_machines); + + return 0; +} + +typedef struct ImageInfo { + const char *name; + const char *type; + bool read_only; + usec_t crtime; + usec_t mtime; + uint64_t size; +} ImageInfo; + +static int compare_image_info(const void *a, const void *b) { + const ImageInfo *x = a, *y = b; + + return strcmp(x->name, y->name); +} + +static int list_images(int argc, char *argv[], void *userdata) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + size_t max_name = strlen("NAME"), max_type = strlen("TYPE"), max_size = strlen("USAGE"), max_crtime = strlen("CREATED"), max_mtime = strlen("MODIFIED"); + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ ImageInfo *images = NULL; + size_t n_images = 0, n_allocated = 0, j; + const char *name, *type, *object; + sd_bus *bus = userdata; + uint64_t crtime, mtime, size; + int read_only, r; + + assert(bus); + + pager_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "ListImages", + &error, + &reply, + ""); + if (r < 0) { + log_error("Could not get images: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &read_only, &crtime, &mtime, &size, &object)) > 0) { + char buf[MAX(FORMAT_TIMESTAMP_MAX, FORMAT_BYTES_MAX)]; + size_t l; + + if (name[0] == '.' && !arg_all) + continue; + + if (!GREEDY_REALLOC(images, n_allocated, n_images + 1)) + return log_oom(); + + images[n_images].name = name; + images[n_images].type = type; + images[n_images].read_only = read_only; + images[n_images].crtime = crtime; + images[n_images].mtime = mtime; + images[n_images].size = size; + + l = strlen(name); + if (l > max_name) + max_name = l; + + l = strlen(type); + if (l > max_type) + max_type = l; + + if (crtime != 0) { + l = strlen(strna(format_timestamp(buf, sizeof(buf), crtime))); + if (l > max_crtime) + max_crtime = l; + } + + if (mtime != 0) { + l = strlen(strna(format_timestamp(buf, sizeof(buf), mtime))); + if (l > max_mtime) + max_mtime = l; + } + + if (size != (uint64_t) -1) { + l = strlen(strna(format_bytes(buf, sizeof(buf), size))); + if (l > max_size) + max_size = l; + } + + n_images++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + qsort_safe(images, n_images, sizeof(ImageInfo), compare_image_info); + + if (arg_legend) + printf("%-*s %-*s %-3s %-*s %-*s %-*s\n", + (int) max_name, "NAME", + (int) max_type, "TYPE", + "RO", + (int) max_size, "USAGE", + (int) max_crtime, "CREATED", + (int) max_mtime, "MODIFIED"); + + for (j = 0; j < n_images; j++) { + char crtime_buf[FORMAT_TIMESTAMP_MAX], mtime_buf[FORMAT_TIMESTAMP_MAX], size_buf[FORMAT_BYTES_MAX]; + + printf("%-*s %-*s %s%-3s%s %-*s %-*s %-*s\n", + (int) max_name, images[j].name, + (int) max_type, images[j].type, + images[j].read_only ? ansi_highlight_red() : "", yes_no(images[j].read_only), images[j].read_only ? ansi_normal() : "", + (int) max_size, strna(format_bytes(size_buf, sizeof(size_buf), images[j].size)), + (int) max_crtime, strna(format_timestamp(crtime_buf, sizeof(crtime_buf), images[j].crtime)), + (int) max_mtime, strna(format_timestamp(mtime_buf, sizeof(mtime_buf), images[j].mtime))); + } + + if (arg_legend) + printf("\n%zu images listed.\n", n_images); + + return 0; +} + +static int show_unit_cgroup(sd_bus *bus, const char *unit, pid_t leader) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *path = NULL; + const char *cgroup; + int r; + unsigned c; + + assert(bus); + assert(unit); + + if (arg_transport == BUS_TRANSPORT_REMOTE) + return 0; + + path = unit_dbus_path_from_name(unit); + if (!path) + return log_oom(); + + r = sd_bus_get_property( + bus, + "org.freedesktop.systemd1", + path, + unit_dbus_interface_from_name(unit), + "ControlGroup", + &error, + &reply, + "s"); + if (r < 0) { + log_error("Failed to query ControlGroup: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "s", &cgroup); + if (r < 0) + return bus_log_parse_error(r); + + if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup) != 0 && leader <= 0) + return 0; + + c = columns(); + if (c > 18) + c -= 18; + else + c = 0; + + show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, get_output_flags()); + return 0; +} + +static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *prefix, const char *prefix2) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + assert(bus); + assert(name); + assert(prefix); + assert(prefix2); + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineAddresses", + NULL, + &reply, + "s", name); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'a', "(iay)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) { + int family; + const void *a; + size_t sz; + char buffer[MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN)]; + + r = sd_bus_message_read(reply, "i", &family); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + return bus_log_parse_error(r); + + fputs(prefix, stdout); + fputs(inet_ntop(family, a, buffer, sizeof(buffer)), stdout); + if (family == AF_INET6 && ifi > 0) + printf("%%%i", ifi); + fputc('\n', stdout); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (prefix != prefix2) + prefix = prefix2; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + return 0; +} + +static int print_os_release(sd_bus *bus, const char *name, const char *prefix) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *k, *v, *pretty = NULL; + int r; + + assert(bus); + assert(name); + assert(prefix); + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineOSRelease", + NULL, + &reply, + "s", name); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'a', "{ss}"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "{ss}", &k, &v)) > 0) { + if (streq(k, "PRETTY_NAME")) + pretty = v; + + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (pretty) + printf("%s%s\n", prefix, pretty); + + return 0; +} + +typedef struct MachineStatusInfo { + char *name; + sd_id128_t id; + char *class; + char *service; + char *unit; + char *root_directory; + pid_t leader; + struct dual_timestamp timestamp; + int *netif; + unsigned n_netif; +} MachineStatusInfo; + +static void machine_status_info_clear(MachineStatusInfo *info) { + if (info) { + free(info->name); + free(info->class); + free(info->service); + free(info->unit); + free(info->root_directory); + free(info->netif); + zero(*info); + } +} + +static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) { + char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; + char since2[FORMAT_TIMESTAMP_MAX], *s2; + int ifi = -1; + + assert(bus); + assert(i); + + fputs(strna(i->name), stdout); + + if (!sd_id128_equal(i->id, SD_ID128_NULL)) + printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id)); + else + putchar('\n'); + + s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp.realtime); + s2 = format_timestamp(since2, sizeof(since2), i->timestamp.realtime); + + if (s1) + printf("\t Since: %s; %s\n", s2, s1); + else if (s2) + printf("\t Since: %s\n", s2); + + if (i->leader > 0) { + _cleanup_free_ char *t = NULL; + + printf("\t Leader: %u", (unsigned) i->leader); + + get_process_comm(i->leader, &t); + if (t) + printf(" (%s)", t); + + putchar('\n'); + } + + if (i->service) { + printf("\t Service: %s", i->service); + + if (i->class) + printf("; class %s", i->class); + + putchar('\n'); + } else if (i->class) + printf("\t Class: %s\n", i->class); + + if (i->root_directory) + printf("\t Root: %s\n", i->root_directory); + + if (i->n_netif > 0) { + unsigned c; + + fputs("\t Iface:", stdout); + + for (c = 0; c < i->n_netif; c++) { + char name[IF_NAMESIZE+1] = ""; + + if (if_indextoname(i->netif[c], name)) { + fputc(' ', stdout); + fputs(name, stdout); + + if (ifi < 0) + ifi = i->netif[c]; + else + ifi = 0; + } else + printf(" %i", i->netif[c]); + } + + fputc('\n', stdout); + } + + print_addresses(bus, i->name, ifi, + "\t Address: ", + "\t "); + + print_os_release(bus, i->name, "\t OS: "); + + if (i->unit) { + printf("\t Unit: %s\n", i->unit); + show_unit_cgroup(bus, i->unit, i->leader); + + if (arg_transport == BUS_TRANSPORT_LOCAL) + + show_journal_by_unit( + stdout, + i->unit, + arg_output, + 0, + i->timestamp.monotonic, + arg_lines, + 0, + get_output_flags() | OUTPUT_BEGIN_NEWLINE, + SD_JOURNAL_LOCAL_ONLY, + true, + NULL); + } +} + +static int map_netif(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { + MachineStatusInfo *i = userdata; + size_t l; + const void *v; + int r; + + assert_cc(sizeof(int32_t) == sizeof(int)); + r = sd_bus_message_read_array(m, SD_BUS_TYPE_INT32, &v, &l); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + + i->n_netif = l / sizeof(int32_t); + i->netif = memdup(v, l); + if (!i->netif) + return -ENOMEM; + + return 0; +} + +static int show_machine_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) { + + static const struct bus_properties_map map[] = { + { "Name", "s", NULL, offsetof(MachineStatusInfo, name) }, + { "Class", "s", NULL, offsetof(MachineStatusInfo, class) }, + { "Service", "s", NULL, offsetof(MachineStatusInfo, service) }, + { "Unit", "s", NULL, offsetof(MachineStatusInfo, unit) }, + { "RootDirectory", "s", NULL, offsetof(MachineStatusInfo, root_directory) }, + { "Leader", "u", NULL, offsetof(MachineStatusInfo, leader) }, + { "Timestamp", "t", NULL, offsetof(MachineStatusInfo, timestamp.realtime) }, + { "TimestampMonotonic", "t", NULL, offsetof(MachineStatusInfo, timestamp.monotonic) }, + { "Id", "ay", bus_map_id128, offsetof(MachineStatusInfo, id) }, + { "NetworkInterfaces", "ai", map_netif, 0 }, + {} + }; + + _cleanup_(machine_status_info_clear) MachineStatusInfo info = {}; + int r; + + assert(verb); + assert(bus); + assert(path); + assert(new_line); + + r = bus_map_all_properties(bus, + "org.freedesktop.machine1", + path, + map, + &info); + if (r < 0) + return log_error_errno(r, "Could not get properties: %m"); + + if (*new_line) + printf("\n"); + *new_line = true; + + print_machine_status_info(bus, &info); + + return r; +} + +static int show_machine_properties(sd_bus *bus, const char *path, bool *new_line) { + int r; + + assert(bus); + assert(path); + assert(new_line); + + if (*new_line) + printf("\n"); + + *new_line = true; + + r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all); + if (r < 0) + log_error_errno(r, "Could not get properties: %m"); + + return r; +} + +static int show_machine(int argc, char *argv[], void *userdata) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + bool properties, new_line = false; + sd_bus *bus = userdata; + int r = 0, i; + + assert(bus); + + properties = !strstr(argv[0], "status"); + + pager_open_if_enabled(); + + if (properties && argc <= 1) { + + /* If no argument is specified, inspect the manager + * itself */ + r = show_machine_properties(bus, "/org/freedesktop/machine1", &new_line); + if (r < 0) + return r; + } + + for (i = 1; i < argc; i++) { + const char *path = NULL; + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachine", + &error, + &reply, + "s", argv[i]); + if (r < 0) { + log_error("Could not get path to machine: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + if (properties) + r = show_machine_properties(bus, path, &new_line); + else + r = show_machine_info(argv[0], bus, path, &new_line); + } + + return r; +} + +typedef struct ImageStatusInfo { + char *name; + char *path; + char *type; + int read_only; + usec_t crtime; + usec_t mtime; + uint64_t usage; + uint64_t limit; + uint64_t usage_exclusive; + uint64_t limit_exclusive; +} ImageStatusInfo; + +static void image_status_info_clear(ImageStatusInfo *info) { + if (info) { + free(info->name); + free(info->path); + free(info->type); + zero(*info); + } +} + +static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) { + char ts_relative[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; + char ts_absolute[FORMAT_TIMESTAMP_MAX], *s2; + char bs[FORMAT_BYTES_MAX], *s3; + char bs_exclusive[FORMAT_BYTES_MAX], *s4; + + assert(bus); + assert(i); + + if (i->name) { + fputs(i->name, stdout); + putchar('\n'); + } + + if (i->type) + printf("\t Type: %s\n", i->type); + + if (i->path) + printf("\t Path: %s\n", i->path); + + printf("\t RO: %s%s%s\n", + i->read_only ? ansi_highlight_red() : "", + i->read_only ? "read-only" : "writable", + i->read_only ? ansi_normal() : ""); + + s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->crtime); + s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->crtime); + if (s1 && s2) + printf("\t Created: %s; %s\n", s2, s1); + else if (s2) + printf("\t Created: %s\n", s2); + + s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->mtime); + s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->mtime); + if (s1 && s2) + printf("\tModified: %s; %s\n", s2, s1); + else if (s2) + printf("\tModified: %s\n", s2); + + s3 = format_bytes(bs, sizeof(bs), i->usage); + s4 = i->usage_exclusive != i->usage ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->usage_exclusive) : NULL; + if (s3 && s4) + printf("\t Usage: %s (exclusive: %s)\n", s3, s4); + else if (s3) + printf("\t Usage: %s\n", s3); + + s3 = format_bytes(bs, sizeof(bs), i->limit); + s4 = i->limit_exclusive != i->limit ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->limit_exclusive) : NULL; + if (s3 && s4) + printf("\t Limit: %s (exclusive: %s)\n", s3, s4); + else if (s3) + printf("\t Limit: %s\n", s3); +} + +static int show_image_info(sd_bus *bus, const char *path, bool *new_line) { + + static const struct bus_properties_map map[] = { + { "Name", "s", NULL, offsetof(ImageStatusInfo, name) }, + { "Path", "s", NULL, offsetof(ImageStatusInfo, path) }, + { "Type", "s", NULL, offsetof(ImageStatusInfo, type) }, + { "ReadOnly", "b", NULL, offsetof(ImageStatusInfo, read_only) }, + { "CreationTimestamp", "t", NULL, offsetof(ImageStatusInfo, crtime) }, + { "ModificationTimestamp", "t", NULL, offsetof(ImageStatusInfo, mtime) }, + { "Usage", "t", NULL, offsetof(ImageStatusInfo, usage) }, + { "Limit", "t", NULL, offsetof(ImageStatusInfo, limit) }, + { "UsageExclusive", "t", NULL, offsetof(ImageStatusInfo, usage_exclusive) }, + { "LimitExclusive", "t", NULL, offsetof(ImageStatusInfo, limit_exclusive) }, + {} + }; + + _cleanup_(image_status_info_clear) ImageStatusInfo info = {}; + int r; + + assert(bus); + assert(path); + assert(new_line); + + r = bus_map_all_properties(bus, + "org.freedesktop.machine1", + path, + map, + &info); + if (r < 0) + return log_error_errno(r, "Could not get properties: %m"); + + if (*new_line) + printf("\n"); + *new_line = true; + + print_image_status_info(bus, &info); + + return r; +} + +typedef struct PoolStatusInfo { + char *path; + uint64_t usage; + uint64_t limit; +} PoolStatusInfo; + +static void pool_status_info_clear(PoolStatusInfo *info) { + if (info) { + free(info->path); + zero(*info); + info->usage = -1; + info->limit = -1; + } +} + +static void print_pool_status_info(sd_bus *bus, PoolStatusInfo *i) { + char bs[FORMAT_BYTES_MAX], *s; + + if (i->path) + printf("\t Path: %s\n", i->path); + + s = format_bytes(bs, sizeof(bs), i->usage); + if (s) + printf("\t Usage: %s\n", s); + + s = format_bytes(bs, sizeof(bs), i->limit); + if (s) + printf("\t Limit: %s\n", s); +} + +static int show_pool_info(sd_bus *bus) { + + static const struct bus_properties_map map[] = { + { "PoolPath", "s", NULL, offsetof(PoolStatusInfo, path) }, + { "PoolUsage", "t", NULL, offsetof(PoolStatusInfo, usage) }, + { "PoolLimit", "t", NULL, offsetof(PoolStatusInfo, limit) }, + {} + }; + + _cleanup_(pool_status_info_clear) PoolStatusInfo info = { + .usage = (uint64_t) -1, + .limit = (uint64_t) -1, + }; + int r; + + assert(bus); + + r = bus_map_all_properties(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + map, + &info); + if (r < 0) + return log_error_errno(r, "Could not get properties: %m"); + + print_pool_status_info(bus, &info); + + return 0; +} + + +static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) { + int r; + + assert(bus); + assert(path); + assert(new_line); + + if (*new_line) + printf("\n"); + + *new_line = true; + + r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all); + if (r < 0) + log_error_errno(r, "Could not get properties: %m"); + + return r; +} + +static int show_image(int argc, char *argv[], void *userdata) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + bool properties, new_line = false; + sd_bus *bus = userdata; + int r = 0, i; + + assert(bus); + + properties = !strstr(argv[0], "status"); + + pager_open_if_enabled(); + + if (argc <= 1) { + + /* If no argument is specified, inspect the manager + * itself */ + + if (properties) + r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line); + else + r = show_pool_info(bus); + if (r < 0) + return r; + } + + for (i = 1; i < argc; i++) { + const char *path = NULL; + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetImage", + &error, + &reply, + "s", argv[i]); + if (r < 0) { + log_error("Could not get path to image: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + if (properties) + r = show_image_properties(bus, path, &new_line); + else + r = show_image_info(bus, path, &new_line); + } + + return r; +} + +static int kill_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + if (!arg_kill_who) + arg_kill_who = "all"; + + for (i = 1; i < argc; i++) { + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "KillMachine", + &error, + NULL, + "ssi", argv[i], arg_kill_who, arg_signal); + if (r < 0) { + log_error("Could not kill machine: %s", bus_error_message(&error, -r)); + return r; + } + } + + return 0; +} + +static int reboot_machine(int argc, char *argv[], void *userdata) { + arg_kill_who = "leader"; + arg_signal = SIGINT; /* sysvinit + systemd */ + + return kill_machine(argc, argv, userdata); +} + +static int poweroff_machine(int argc, char *argv[], void *userdata) { + arg_kill_who = "leader"; + arg_signal = SIGRTMIN+4; /* only systemd */ + + return kill_machine(argc, argv, userdata); +} + +static int terminate_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + for (i = 1; i < argc; i++) { + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "TerminateMachine", + &error, + NULL, + "s", argv[i]); + if (r < 0) { + log_error("Could not terminate machine: %s", bus_error_message(&error, -r)); + return r; + } + } + + return 0; +} + +static int copy_files(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *abs_host_path = NULL; + char *dest, *host_path, *container_path; + sd_bus *bus = userdata; + bool copy_from; + int r; + + assert(bus); + + polkit_agent_open_if_enabled(); + + copy_from = streq(argv[0], "copy-from"); + dest = argv[3] ?: argv[2]; + host_path = copy_from ? dest : argv[2]; + container_path = copy_from ? argv[2] : dest; + + if (!path_is_absolute(host_path)) { + r = path_make_absolute_cwd(host_path, &abs_host_path); + if (r < 0) + return log_error_errno(r, "Failed to make path absolute: %m"); + + host_path = abs_host_path; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + copy_from ? "CopyFromMachine" : "CopyToMachine", + &error, + NULL, + "sss", + argv[1], + copy_from ? container_path : host_path, + copy_from ? host_path : container_path); + if (r < 0) + return log_error_errno(r, "Failed to copy: %s", bus_error_message(&error, r)); + + return 0; +} + +static int bind_mount(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "BindMountMachine", + &error, + NULL, + "sssbb", + argv[1], + argv[2], + argv[3], + arg_read_only, + arg_mkdir); + if (r < 0) { + log_error("Failed to bind mount: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int on_machine_removed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + PTYForward ** forward = (PTYForward**) userdata; + int r; + + assert(m); + assert(forward); + + if (*forward) { + /* If the forwarder is already initialized, tell it to + * exit on the next vhangup(), so that we still flush + * out what might be queued and exit then. */ + + r = pty_forward_set_ignore_vhangup(*forward, false); + if (r >= 0) + return 0; + + log_error_errno(r, "Failed to set ignore_vhangup flag: %m"); + } + + /* On error, or when the forwarder is not initialized yet, quit immediately */ + sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), EXIT_FAILURE); + return 0; +} + +static int process_forward(sd_event *event, PTYForward **forward, int master, PTYForwardFlags flags, const char *name) { + char last_char = 0; + bool machine_died; + int ret = 0, r; + + assert(event); + assert(master >= 0); + assert(name); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); + + if (streq(name, ".host")) + log_info("Connected to the local host. Press ^] three times within 1s to exit session."); + else + log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name); + + sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); + sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); + + r = pty_forward_new(event, master, flags, forward); + if (r < 0) + return log_error_errno(r, "Failed to create PTY forwarder: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + pty_forward_get_last_char(*forward, &last_char); + + machine_died = + (flags & PTY_FORWARD_IGNORE_VHANGUP) && + pty_forward_get_ignore_vhangup(*forward) == 0; + + *forward = pty_forward_free(*forward); + + if (last_char != '\n') + fputc('\n', stdout); + + if (machine_died) + log_info("Machine %s terminated.", name); + else if (streq(name, ".host")) + log_info("Connection to the local host terminated."); + else + log_info("Connection to machine %s terminated.", name); + + sd_event_get_exit_code(event, &ret); + return ret; +} + +static int login_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(pty_forward_freep) PTYForward *forward = NULL; + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int master = -1, r; + sd_bus *bus = userdata; + const char *pty, *match, *machine; + + assert(bus); + + if (!strv_isempty(arg_setenv) || arg_uid) { + log_error("--setenv= and --uid= are not supported for 'login'. Use 'shell' instead."); + return -EINVAL; + } + + if (arg_transport != BUS_TRANSPORT_LOCAL && + arg_transport != BUS_TRANSPORT_MACHINE) { + log_error("Login only supported on local machines."); + return -EOPNOTSUPP; + } + + polkit_agent_open_if_enabled(); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + machine = argc < 2 || isempty(argv[1]) ? ".host" : argv[1]; + + match = strjoina("type='signal'," + "sender='org.freedesktop.machine1'," + "path='/org/freedesktop/machine1',", + "interface='org.freedesktop.machine1.Manager'," + "member='MachineRemoved'," + "arg0='", machine, "'"); + + r = sd_bus_add_match(bus, &slot, match, on_machine_removed, &forward); + if (r < 0) + return log_error_errno(r, "Failed to add machine removal match: %m"); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "OpenMachineLogin", + &error, + &reply, + "s", machine); + if (r < 0) { + log_error("Failed to get login PTY: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "hs", &master, &pty); + if (r < 0) + return bus_log_parse_error(r); + + return process_forward(event, &forward, master, PTY_FORWARD_IGNORE_VHANGUP, machine); +} + +static int shell_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(pty_forward_freep) PTYForward *forward = NULL; + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int master = -1, r; + sd_bus *bus = userdata; + const char *pty, *match, *machine, *path, *uid = NULL; + + assert(bus); + + if (arg_transport != BUS_TRANSPORT_LOCAL && + arg_transport != BUS_TRANSPORT_MACHINE) { + log_error("Shell only supported on local machines."); + return -EOPNOTSUPP; + } + + /* Pass $TERM to shell session, if not explicitly specified. */ + if (!strv_find_prefix(arg_setenv, "TERM=")) { + const char *t; + + t = strv_find_prefix(environ, "TERM="); + if (t) { + if (strv_extend(&arg_setenv, t) < 0) + return log_oom(); + } + } + + polkit_agent_open_if_enabled(); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + machine = argc < 2 || isempty(argv[1]) ? NULL : argv[1]; + + if (arg_uid) + uid = arg_uid; + else if (machine) { + const char *at; + + at = strchr(machine, '@'); + if (at) { + uid = strndupa(machine, at - machine); + machine = at + 1; + } + } + + if (isempty(machine)) + machine = ".host"; + + match = strjoina("type='signal'," + "sender='org.freedesktop.machine1'," + "path='/org/freedesktop/machine1',", + "interface='org.freedesktop.machine1.Manager'," + "member='MachineRemoved'," + "arg0='", machine, "'"); + + r = sd_bus_add_match(bus, &slot, match, on_machine_removed, &forward); + if (r < 0) + return log_error_errno(r, "Failed to add machine removal match: %m"); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "OpenMachineShell"); + if (r < 0) + return bus_log_create_error(r); + + path = argc < 3 || isempty(argv[2]) ? NULL : argv[2]; + + r = sd_bus_message_append(m, "sss", machine, uid, path); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, strv_length(argv) <= 3 ? NULL : argv + 2); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, arg_setenv); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to get shell PTY: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "hs", &master, &pty); + if (r < 0) + return bus_log_parse_error(r); + + return process_forward(event, &forward, master, 0, machine); +} + +static int remove_image(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + for (i = 1; i < argc; i++) { + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "RemoveImage", + &error, + NULL, + "s", argv[i]); + if (r < 0) { + log_error("Could not remove image: %s", bus_error_message(&error, -r)); + return r; + } + } + + return 0; +} + +static int rename_image(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r; + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "RenameImage", + &error, + NULL, + "ss", argv[1], argv[2]); + if (r < 0) { + log_error("Could not rename image: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int clone_image(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r; + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "CloneImage", + &error, + NULL, + "ssb", argv[1], argv[2], arg_read_only); + if (r < 0) { + log_error("Could not clone image: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int read_only_image(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int b = true, r; + + if (argc > 2) { + b = parse_boolean(argv[2]); + if (b < 0) { + log_error("Failed to parse boolean argument: %s", argv[2]); + return -EINVAL; + } + } + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "MarkImageReadOnly", + &error, + NULL, + "sb", argv[1], b); + if (r < 0) { + log_error("Could not mark image read-only: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int make_service_name(const char *name, char **ret) { + _cleanup_free_ char *e = NULL; + int r; + + assert(name); + assert(ret); + + if (!machine_name_is_valid(name)) { + log_error("Invalid machine name %s.", name); + return -EINVAL; + } + + e = unit_name_escape(name); + if (!e) + return log_oom(); + + r = unit_name_build("systemd-nspawn", e, ".service", ret); + if (r < 0) + return log_error_errno(r, "Failed to build unit name: %m"); + + return 0; +} + +static int start_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_oom(); + + for (i = 1; i < argc; i++) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *unit = NULL; + const char *object; + + r = make_service_name(argv[i], &unit); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnit", + &error, + &reply, + "ss", unit, "fail"); + if (r < 0) { + log_error("Failed to start unit: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_add(w, object); + if (r < 0) + return log_oom(); + } + + r = bus_wait_for_jobs(w, arg_quiet, NULL); + if (r < 0) + return r; + + return 0; +} + +static int enable_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int carries_install_info = 0; + const char *method = NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + method = streq(argv[0], "enable") ? "EnableUnitFiles" : "DisableUnitFiles"; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + method); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return bus_log_create_error(r); + + for (i = 1; i < argc; i++) { + _cleanup_free_ char *unit = NULL; + + r = make_service_name(argv[i], &unit); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", unit); + 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); + + if (streq(argv[0], "enable")) + r = sd_bus_message_append(m, "bb", false, false); + else + r = sd_bus_message_append(m, "b", false); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to enable or disable unit: %s", bus_error_message(&error, -r)); + return r; + } + + if (streq(argv[0], "enable")) { + r = sd_bus_message_read(reply, "b", carries_install_info); + if (r < 0) + return bus_log_parse_error(r); + } + + r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, NULL, NULL); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Reload", + &error, + NULL, + NULL); + if (r < 0) { + log_error("Failed to reload daemon: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int match_log_message(sd_bus_message *m, void *userdata, sd_bus_error *error) { + const char **our_path = userdata, *line; + unsigned priority; + int r; + + assert(m); + assert(our_path); + + r = sd_bus_message_read(m, "us", &priority, &line); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (!streq_ptr(*our_path, sd_bus_message_get_path(m))) + return 0; + + if (arg_quiet && LOG_PRI(priority) >= LOG_INFO) + return 0; + + log_full(priority, "%s", line); + return 0; +} + +static int match_transfer_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + const char **our_path = userdata, *path, *result; + uint32_t id; + int r; + + assert(m); + assert(our_path); + + r = sd_bus_message_read(m, "uos", &id, &path, &result); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (!streq_ptr(*our_path, path)) + return 0; + + sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), !streq_ptr(result, "done")); + return 0; +} + +static int transfer_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + assert(s); + assert(si); + + if (!arg_quiet) + log_info("Continuing download in the background. Use \"machinectl cancel-transfer %" PRIu32 "\" to abort transfer.", PTR_TO_UINT32(userdata)); + + sd_event_exit(sd_event_source_get_event(s), EINTR); + return 0; +} + +static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot_job_removed = NULL, *slot_log_message = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_event_unrefp) sd_event* event = NULL; + const char *path = NULL; + uint32_t id; + int r; + + assert(bus); + assert(m); + + polkit_agent_open_if_enabled(); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + r = sd_bus_add_match( + bus, + &slot_job_removed, + "type='signal'," + "sender='org.freedesktop.import1'," + "interface='org.freedesktop.import1.Manager'," + "member='TransferRemoved'," + "path='/org/freedesktop/import1'", + match_transfer_removed, &path); + if (r < 0) + return log_error_errno(r, "Failed to install match: %m"); + + r = sd_bus_add_match( + bus, + &slot_log_message, + "type='signal'," + "sender='org.freedesktop.import1'," + "interface='org.freedesktop.import1.Transfer'," + "member='LogMessage'", + match_log_message, &path); + if (r < 0) + return log_error_errno(r, "Failed to install match: %m"); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed transfer image: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "uo", &id, &path); + if (r < 0) + return bus_log_parse_error(r); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + + if (!arg_quiet) + log_info("Enqueued transfer job %u. Press C-c to continue download in background.", id); + + sd_event_add_signal(event, NULL, SIGINT, transfer_signal_handler, UINT32_TO_PTR(id)); + sd_event_add_signal(event, NULL, SIGTERM, transfer_signal_handler, UINT32_TO_PTR(id)); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + return -r; +} + +static int import_tar(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *ll = NULL; + _cleanup_close_ int fd = -1; + const char *local = NULL, *path = NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + if (argc >= 2) + path = argv[1]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + if (argc >= 3) + local = argv[2]; + else if (path) + local = basename(path); + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (!local) { + log_error("Need either path or local name."); + return -EINVAL; + } + + r = tar_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + + if (path) { + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ImportTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "hsbb", + fd >= 0 ? fd : STDIN_FILENO, + local, + arg_force, + arg_read_only); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int import_raw(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *ll = NULL; + _cleanup_close_ int fd = -1; + const char *local = NULL, *path = NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + if (argc >= 2) + path = argv[1]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + if (argc >= 3) + local = argv[2]; + else if (path) + local = basename(path); + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (!local) { + log_error("Need either path or local name."); + return -EINVAL; + } + + r = raw_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + + if (path) { + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ImportRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "hsbb", + fd >= 0 ? fd : STDIN_FILENO, + local, + arg_force, + arg_read_only); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static void determine_compression_from_filename(const char *p) { + if (arg_format) + return; + + if (!p) + return; + + if (endswith(p, ".xz")) + arg_format = "xz"; + else if (endswith(p, ".gz")) + arg_format = "gzip"; + else if (endswith(p, ".bz2")) + arg_format = "bzip2"; +} + +static int export_tar(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_close_ int fd = -1; + const char *local = NULL, *path = NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + local = argv[1]; + if (!machine_name_is_valid(local)) { + log_error("Machine name %s is not valid.", local); + return -EINVAL; + } + + if (argc >= 3) + path = argv[2]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + if (path) { + determine_compression_from_filename(path); + + fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ExportTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "shs", + local, + fd >= 0 ? fd : STDOUT_FILENO, + arg_format); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int export_raw(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_close_ int fd = -1; + const char *local = NULL, *path = NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + local = argv[1]; + if (!machine_name_is_valid(local)) { + log_error("Machine name %s is not valid.", local); + return -EINVAL; + } + + if (argc >= 3) + path = argv[2]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + if (path) { + determine_compression_from_filename(path); + + fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ExportRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "shs", + local, + fd >= 0 ? fd : STDOUT_FILENO, + arg_format); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int pull_tar(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL, *ll = NULL; + const char *local, *remote; + sd_bus *bus = userdata; + int r; + + assert(bus); + + remote = argv[1]; + if (!http_url_is_valid(remote)) { + log_error("URL '%s' is not valid.", remote); + return -EINVAL; + } + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(remote, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of URL: %m"); + + local = l; + } + + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (local) { + r = tar_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "PullTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssb", + remote, + local, + import_verify_to_string(arg_verify), + arg_force); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int pull_raw(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL, *ll = NULL; + const char *local, *remote; + sd_bus *bus = userdata; + int r; + + assert(bus); + + remote = argv[1]; + if (!http_url_is_valid(remote)) { + log_error("URL '%s' is not valid.", remote); + return -EINVAL; + } + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(remote, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of URL: %m"); + + local = l; + } + + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (local) { + r = raw_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "PullRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssb", + remote, + local, + import_verify_to_string(arg_verify), + arg_force); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +typedef struct TransferInfo { + uint32_t id; + const char *type; + const char *remote; + const char *local; + double progress; +} TransferInfo; + +static int compare_transfer_info(const void *a, const void *b) { + const TransferInfo *x = a, *y = b; + + return strcmp(x->local, y->local); +} + +static int list_transfers(int argc, char *argv[], void *userdata) { + size_t max_type = strlen("TYPE"), max_local = strlen("LOCAL"), max_remote = strlen("REMOTE"); + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ TransferInfo *transfers = NULL; + size_t n_transfers = 0, n_allocated = 0, j; + const char *type, *remote, *local, *object; + sd_bus *bus = userdata; + uint32_t id, max_id = 0; + double progress; + int r; + + pager_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ListTransfers", + &error, + &reply, + NULL); + if (r < 0) { + log_error("Could not get transfers: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_enter_container(reply, 'a', "(usssdo)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(usssdo)", &id, &type, &remote, &local, &progress, &object)) > 0) { + size_t l; + + if (!GREEDY_REALLOC(transfers, n_allocated, n_transfers + 1)) + return log_oom(); + + transfers[n_transfers].id = id; + transfers[n_transfers].type = type; + transfers[n_transfers].remote = remote; + transfers[n_transfers].local = local; + transfers[n_transfers].progress = progress; + + l = strlen(type); + if (l > max_type) + max_type = l; + + l = strlen(remote); + if (l > max_remote) + max_remote = l; + + l = strlen(local); + if (l > max_local) + max_local = l; + + if (id > max_id) + max_id = id; + + n_transfers ++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + qsort_safe(transfers, n_transfers, sizeof(TransferInfo), compare_transfer_info); + + if (arg_legend) + printf("%-*s %-*s %-*s %-*s %-*s\n", + (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), "ID", + (int) 7, "PERCENT", + (int) max_type, "TYPE", + (int) max_local, "LOCAL", + (int) max_remote, "REMOTE"); + + for (j = 0; j < n_transfers; j++) + printf("%*" PRIu32 " %*u%% %-*s %-*s %-*s\n", + (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), transfers[j].id, + (int) 6, (unsigned) (transfers[j].progress * 100), + (int) max_type, transfers[j].type, + (int) max_local, transfers[j].local, + (int) max_remote, transfers[j].remote); + + if (arg_legend) + printf("\n%zu transfers listed.\n", n_transfers); + + return 0; +} + +static int cancel_transfer(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + for (i = 1; i < argc; i++) { + uint32_t id; + + r = safe_atou32(argv[i], &id); + if (r < 0) + return log_error_errno(r, "Failed to parse transfer id: %s", argv[i]); + + r = sd_bus_call_method( + bus, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "CancelTransfer", + &error, + NULL, + "u", id); + if (r < 0) { + log_error("Could not cancel transfer: %s", bus_error_message(&error, -r)); + return r; + } + } + + return 0; +} + +static int set_limit(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + uint64_t limit; + int r; + + if (STR_IN_SET(argv[argc-1], "-", "none", "infinity")) + limit = (uint64_t) -1; + else { + r = parse_size(argv[argc-1], 1024, &limit); + if (r < 0) + return log_error("Failed to parse size: %s", argv[argc-1]); + } + + if (argc > 2) + /* With two arguments changes the quota limit of the + * specified image */ + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "SetImageLimit", + &error, + NULL, + "st", argv[1], limit); + else + /* With one argument changes the pool quota limit */ + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "SetPoolLimit", + &error, + NULL, + "t", limit); + + if (r < 0) { + log_error("Could not set limit: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int help(int argc, char *argv[], void *userdata) { + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Send control commands to or query the virtual machine and container\n" + "registration manager.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --no-legend Do not show the headers and footers\n" + " --no-ask-password Do not ask for system passwords\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " -p --property=NAME Show only properties by this name\n" + " -q --quiet Suppress output\n" + " -a --all Show all properties, including empty ones\n" + " -l --full Do not ellipsize output\n" + " --kill-who=WHO Who to send signal to\n" + " -s --signal=SIGNAL Which signal to send\n" + " --uid=USER Specify user ID to invoke shell as\n" + " --setenv=VAR=VALUE Add an environment variable for shell\n" + " --read-only Create read-only bind mount\n" + " --mkdir Create directory before bind mounting, if missing\n" + " -n --lines=INTEGER Number of journal entries to show\n" + " -o --output=STRING Change journal output mode (short,\n" + " short-monotonic, verbose, export, json,\n" + " json-pretty, json-sse, cat)\n" + " --verify=MODE Verification mode for downloaded images (no,\n" + " checksum, signature)\n" + " --force Download image even if already exists\n\n" + "Machine Commands:\n" + " list List running VMs and containers\n" + " status NAME... Show VM/container details\n" + " show [NAME...] Show properties of one or more VMs/containers\n" + " start NAME... Start container as a service\n" + " login [NAME] Get a login prompt in a container or on the\n" + " local host\n" + " shell [[USER@]NAME [COMMAND...]]\n" + " Invoke a shell (or other command) in a container\n" + " or on the local host\n" + " enable NAME... Enable automatic container start at boot\n" + " disable NAME... Disable automatic container start at boot\n" + " poweroff NAME... Power off one or more containers\n" + " reboot NAME... Reboot one or more containers\n" + " terminate NAME... Terminate one or more VMs/containers\n" + " kill NAME... Send signal to processes of a VM/container\n" + " copy-to NAME PATH [PATH] Copy files from the host to a container\n" + " copy-from NAME PATH [PATH] Copy files from a container to the host\n" + " bind NAME PATH [PATH] Bind mount a path from the host into a container\n\n" + "Image Commands:\n" + " list-images Show available container and VM images\n" + " image-status [NAME...] Show image details\n" + " show-image [NAME...] Show properties of image\n" + " clone NAME NAME Clone an image\n" + " rename NAME NAME Rename an image\n" + " read-only NAME [BOOL] Mark or unmark image read-only\n" + " remove NAME... Remove an image\n" + " set-limit [NAME] BYTES Set image or pool size limit (disk quota)\n\n" + "Image Transfer Commands:\n" + " pull-tar URL [NAME] Download a TAR container image\n" + " pull-raw URL [NAME] Download a RAW container or VM image\n" + " import-tar FILE [NAME] Import a local TAR container image\n" + " import-raw FILE [NAME] Import a local RAW container or VM image\n" + " export-tar NAME [FILE] Export a TAR container image locally\n" + " export-raw NAME [FILE] Export a RAW container or VM image locally\n" + " list-transfers Show list of downloads in progress\n" + " cancel-transfer Cancel a download\n" + , program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_NO_LEGEND, + ARG_KILL_WHO, + ARG_READ_ONLY, + ARG_MKDIR, + ARG_NO_ASK_PASSWORD, + ARG_VERIFY, + ARG_FORCE, + ARG_FORMAT, + ARG_UID, + ARG_SETENV, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "property", required_argument, NULL, 'p' }, + { "all", no_argument, NULL, 'a' }, + { "full", no_argument, NULL, 'l' }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "kill-who", required_argument, NULL, ARG_KILL_WHO }, + { "signal", required_argument, NULL, 's' }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "read-only", no_argument, NULL, ARG_READ_ONLY }, + { "mkdir", no_argument, NULL, ARG_MKDIR }, + { "quiet", no_argument, NULL, 'q' }, + { "lines", required_argument, NULL, 'n' }, + { "output", required_argument, NULL, 'o' }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "verify", required_argument, NULL, ARG_VERIFY }, + { "force", no_argument, NULL, ARG_FORCE }, + { "format", required_argument, NULL, ARG_FORMAT }, + { "uid", required_argument, NULL, ARG_UID }, + { "setenv", required_argument, NULL, ARG_SETENV }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hp:als:H:M:qn:o:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(0, NULL, NULL); + + case ARG_VERSION: + return version(); + + case 'p': + r = strv_extend(&arg_property, optarg); + if (r < 0) + return log_oom(); + + /* If the user asked for a particular + * property, show it to him, even if it is + * empty. */ + arg_all = true; + break; + + case 'a': + arg_all = true; + break; + + case 'l': + arg_full = true; + break; + + case 'n': + if (safe_atou(optarg, &arg_lines) < 0) { + log_error("Failed to parse lines '%s'", optarg); + return -EINVAL; + } + break; + + case 'o': + arg_output = output_mode_from_string(optarg); + if (arg_output < 0) { + log_error("Unknown output '%s'.", optarg); + return -EINVAL; + } + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_NO_LEGEND: + arg_legend = false; + break; + + case ARG_KILL_WHO: + arg_kill_who = optarg; + break; + + case 's': + arg_signal = signal_from_string_try_harder(optarg); + if (arg_signal < 0) { + log_error("Failed to parse signal string %s.", optarg); + return -EINVAL; + } + break; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_READ_ONLY: + arg_read_only = true; + break; + + case ARG_MKDIR: + arg_mkdir = true; + break; + + case 'q': + arg_quiet = true; + break; + + case ARG_VERIFY: + arg_verify = import_verify_from_string(optarg); + if (arg_verify < 0) { + log_error("Failed to parse --verify= setting: %s", optarg); + return -EINVAL; + } + break; + + case ARG_FORCE: + arg_force = true; + break; + + case ARG_FORMAT: + if (!STR_IN_SET(optarg, "uncompressed", "xz", "gzip", "bzip2")) { + log_error("Unknown format: %s", optarg); + return -EINVAL; + } + + arg_format = optarg; + break; + + case ARG_UID: + arg_uid = optarg; + break; + + case ARG_SETENV: + if (!env_assignment_is_valid(optarg)) { + log_error("Environment assignment invalid: %s", optarg); + return -EINVAL; + } + + r = strv_extend(&arg_setenv, optarg); + if (r < 0) + return log_oom(); + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +static int machinectl_main(int argc, char *argv[], sd_bus *bus) { + + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, help }, + { "list", VERB_ANY, 1, VERB_DEFAULT, list_machines }, + { "list-images", VERB_ANY, 1, 0, list_images }, + { "status", 2, VERB_ANY, 0, show_machine }, + { "image-status", VERB_ANY, VERB_ANY, 0, show_image }, + { "show", VERB_ANY, VERB_ANY, 0, show_machine }, + { "show-image", VERB_ANY, VERB_ANY, 0, show_image }, + { "terminate", 2, VERB_ANY, 0, terminate_machine }, + { "reboot", 2, VERB_ANY, 0, reboot_machine }, + { "poweroff", 2, VERB_ANY, 0, poweroff_machine }, + { "kill", 2, VERB_ANY, 0, kill_machine }, + { "login", VERB_ANY, 2, 0, login_machine }, + { "shell", VERB_ANY, VERB_ANY, 0, shell_machine }, + { "bind", 3, 4, 0, bind_mount }, + { "copy-to", 3, 4, 0, copy_files }, + { "copy-from", 3, 4, 0, copy_files }, + { "remove", 2, VERB_ANY, 0, remove_image }, + { "rename", 3, 3, 0, rename_image }, + { "clone", 3, 3, 0, clone_image }, + { "read-only", 2, 3, 0, read_only_image }, + { "start", 2, VERB_ANY, 0, start_machine }, + { "enable", 2, VERB_ANY, 0, enable_machine }, + { "disable", 2, VERB_ANY, 0, enable_machine }, + { "import-tar", 2, 3, 0, import_tar }, + { "import-raw", 2, 3, 0, import_raw }, + { "export-tar", 2, 3, 0, export_tar }, + { "export-raw", 2, 3, 0, export_raw }, + { "pull-tar", 2, 3, 0, pull_tar }, + { "pull-raw", 2, 3, 0, pull_raw }, + { "list-transfers", VERB_ANY, 1, 0, list_transfers }, + { "cancel-transfer", 2, VERB_ANY, 0, cancel_transfer }, + { "set-limit", 2, 3, 0, set_limit }, + {} + }; + + return dispatch_verb(argc, argv, verbs, bus); +} + +int main(int argc, char*argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + setlocale(LC_ALL, ""); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = bus_connect_transport(arg_transport, arg_host, false, &bus); + if (r < 0) { + log_error_errno(r, "Failed to create bus connection: %m"); + goto finish; + } + + sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); + + r = machinectl_main(argc, argv, bus); + +finish: + pager_close(); + polkit_agent_close(); + + strv_free(arg_property); + strv_free(arg_setenv); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/machine/src/machined-dbus.c b/src/machine/src/machined-dbus.c new file mode 100644 index 0000000000..b933099330 --- /dev/null +++ b/src/machine/src/machined-dbus.c @@ -0,0 +1,1551 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include +#include + +#include "sd-id128.h" + +#include "alloc-util.h" +#include "btrfs-util.h" +#include "bus-common-errors.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "fd-util.h" +#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" +#include "machined.h" +#include "path-util.h" +#include "process-util.h" +#include "stdio-util.h" +#include "strv.h" +#include "unit-name.h" +#include "user-util.h" + +static int property_get_pool_path( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + assert(bus); + assert(reply); + + return sd_bus_message_append(reply, "s", "/var/lib/machines"); +} + +static int property_get_pool_usage( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + _cleanup_close_ int fd = -1; + uint64_t usage = (uint64_t) -1; + struct stat st; + + assert(bus); + assert(reply); + + /* We try to read the quota info from /var/lib/machines, as + * well as the usage of the loopback file + * /var/lib/machines.raw, and pick the larger value. */ + + fd = open("/var/lib/machines", O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (fd >= 0) { + BtrfsQuotaInfo q; + + if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0) + usage = q.referenced; + } + + if (stat("/var/lib/machines.raw", &st) >= 0) { + if (usage == (uint64_t) -1 || st.st_blocks * 512ULL > usage) + usage = st.st_blocks * 512ULL; + } + + return sd_bus_message_append(reply, "t", usage); +} + +static int property_get_pool_limit( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + _cleanup_close_ int fd = -1; + uint64_t size = (uint64_t) -1; + struct stat st; + + assert(bus); + assert(reply); + + /* We try to read the quota limit from /var/lib/machines, as + * well as the size of the loopback file + * /var/lib/machines.raw, and pick the smaller value. */ + + fd = open("/var/lib/machines", O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (fd >= 0) { + BtrfsQuotaInfo q; + + if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0) + size = q.referenced_max; + } + + if (stat("/var/lib/machines.raw", &st) >= 0) { + if (size == (uint64_t) -1 || (uint64_t) st.st_size < size) + size = st.st_size; + } + + return sd_bus_message_append(reply, "t", size); +} + +static int method_get_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *p = NULL; + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + p = machine_bus_path(machine); + if (!p) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", p); +} + +static int method_get_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *p = NULL; + Manager *m = userdata; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + r = image_find(name, NULL); + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); + if (r < 0) + return r; + + p = image_bus_path(name); + if (!p) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", p); +} + +static int method_get_machine_by_pid(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *p = NULL; + Manager *m = userdata; + Machine *machine = NULL; + pid_t pid; + int r; + + assert(message); + assert(m); + + assert_cc(sizeof(pid_t) == sizeof(uint32_t)); + + r = sd_bus_message_read(message, "u", &pid); + if (r < 0) + return r; + + if (pid < 0) + return -EINVAL; + + if (pid == 0) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return r; + } + + r = manager_get_machine_by_pid(m, pid, &machine); + if (r < 0) + return r; + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_MACHINE_FOR_PID, "PID "PID_FMT" does not belong to any known machine", pid); + + p = machine_bus_path(machine); + if (!p) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", p); +} + +static int method_list_machines(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Manager *m = userdata; + Machine *machine; + Iterator i; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + r = sd_bus_message_open_container(reply, 'a', "(ssso)"); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + HASHMAP_FOREACH(machine, m->machines, i) { + _cleanup_free_ char *p = NULL; + + p = machine_bus_path(machine); + if (!p) + return -ENOMEM; + + r = sd_bus_message_append(reply, "(ssso)", + machine->name, + strempty(machine_class_to_string(machine->class)), + machine->service, + p); + if (r < 0) + return sd_bus_error_set_errno(error, r); + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + return sd_bus_send(NULL, reply, NULL); +} + +static int method_create_or_register_machine(Manager *manager, sd_bus_message *message, bool read_network, Machine **_m, sd_bus_error *error) { + const char *name, *service, *class, *root_directory; + const int32_t *netif = NULL; + MachineClass c; + uint32_t leader; + sd_id128_t id; + const void *v; + Machine *m; + size_t n, n_netif = 0; + int r; + + assert(manager); + assert(message); + assert(_m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + if (!machine_name_is_valid(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine name"); + + r = sd_bus_message_read_array(message, 'y', &v, &n); + if (r < 0) + return r; + if (n == 0) + id = SD_ID128_NULL; + else if (n == 16) + memcpy(&id, v, n); + else + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine ID parameter"); + + r = sd_bus_message_read(message, "ssus", &service, &class, &leader, &root_directory); + if (r < 0) + return r; + + if (read_network) { + size_t i; + + r = sd_bus_message_read_array(message, 'i', (const void**) &netif, &n_netif); + if (r < 0) + return r; + + n_netif /= sizeof(int32_t); + + for (i = 0; i < n_netif; i++) { + if (netif[i] <= 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid network interface index %i", netif[i]); + } + } + + if (isempty(class)) + c = _MACHINE_CLASS_INVALID; + else { + c = machine_class_from_string(class); + if (c < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine class parameter"); + } + + if (leader == 1) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid leader PID"); + + if (!isempty(root_directory) && !path_is_absolute(root_directory)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Root directory must be empty or an absolute path"); + + if (leader == 0) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + assert_cc(sizeof(uint32_t) == sizeof(pid_t)); + + r = sd_bus_creds_get_pid(creds, (pid_t*) &leader); + if (r < 0) + return r; + } + + if (hashmap_get(manager->machines, name)) + return sd_bus_error_setf(error, BUS_ERROR_MACHINE_EXISTS, "Machine '%s' already exists", name); + + r = manager_add_machine(manager, name, &m); + if (r < 0) + return r; + + m->leader = leader; + m->class = c; + m->id = id; + + if (!isempty(service)) { + m->service = strdup(service); + if (!m->service) { + r = -ENOMEM; + goto fail; + } + } + + if (!isempty(root_directory)) { + m->root_directory = strdup(root_directory); + if (!m->root_directory) { + r = -ENOMEM; + goto fail; + } + } + + if (n_netif > 0) { + assert_cc(sizeof(int32_t) == sizeof(int)); + m->netif = memdup(netif, sizeof(int32_t) * n_netif); + if (!m->netif) { + r = -ENOMEM; + goto fail; + } + + m->n_netif = n_netif; + } + + *_m = m; + + return 1; + +fail: + machine_add_to_gc_queue(m); + return r; +} + +static int method_create_machine_internal(sd_bus_message *message, bool read_network, void *userdata, sd_bus_error *error) { + Manager *manager = userdata; + Machine *m = NULL; + int r; + + assert(message); + assert(manager); + + r = method_create_or_register_machine(manager, message, read_network, &m, error); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(message, 'a', "(sv)"); + if (r < 0) + goto fail; + + r = machine_start(m, message, error); + if (r < 0) + goto fail; + + m->create_message = sd_bus_message_ref(message); + return 1; + +fail: + machine_add_to_gc_queue(m); + return r; +} + +static int method_create_machine_with_network(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_create_machine_internal(message, true, userdata, error); +} + +static int method_create_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_create_machine_internal(message, false, userdata, error); +} + +static int method_register_machine_internal(sd_bus_message *message, bool read_network, void *userdata, sd_bus_error *error) { + Manager *manager = userdata; + _cleanup_free_ char *p = NULL; + Machine *m = NULL; + int r; + + assert(message); + assert(manager); + + r = method_create_or_register_machine(manager, message, read_network, &m, error); + if (r < 0) + return r; + + r = cg_pid_get_unit(m->leader, &m->unit); + if (r < 0) { + r = sd_bus_error_set_errnof(error, r, "Failed to determine unit of process "PID_FMT" : %s", m->leader, strerror(-r)); + goto fail; + } + + r = machine_start(m, NULL, error); + if (r < 0) + goto fail; + + p = machine_bus_path(m); + if (!p) { + r = -ENOMEM; + goto fail; + } + + return sd_bus_reply_method_return(message, "o", p); + +fail: + machine_add_to_gc_queue(m); + return r; +} + +static int method_register_machine_with_network(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_register_machine_internal(message, true, userdata, error); +} + +static int method_register_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_register_machine_internal(message, false, userdata, error); +} + +static int method_terminate_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_terminate(message, machine, error); +} + +static int method_kill_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_kill(message, machine, error); +} + +static int method_get_machine_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_get_addresses(message, machine, error); +} + +static int method_get_machine_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_get_os_release(message, machine, error); +} + +static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(image_hashmap_freep) Hashmap *images = NULL; + Manager *m = userdata; + Image *image; + Iterator i; + int r; + + assert(message); + assert(m); + + images = hashmap_new(&string_hash_ops); + if (!images) + return -ENOMEM; + + r = image_discover(images); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(ssbttto)"); + if (r < 0) + return r; + + HASHMAP_FOREACH(image, images, i) { + _cleanup_free_ char *p = NULL; + + p = image_bus_path(image->name); + if (!p) + return -ENOMEM; + + r = sd_bus_message_append(reply, "(ssbttto)", + image->name, + image_type_to_string(image->type), + image->read_only, + image->crtime, + image->mtime, + image->usage, + p); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +static int method_open_machine_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_open_pty(message, machine, error); +} + +static int method_open_machine_login(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_open_login(message, machine, error); +} + +static int method_open_machine_shell(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_open_shell(message, machine, error); +} + +static int method_bind_mount_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_bind_mount(message, machine, error); +} + +static int method_copy_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_copy(message, machine, error); +} + +static int method_remove_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(image_unrefp) Image* i = NULL; + const char *name; + int r; + + assert(message); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + if (!image_name_is_valid(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); + + r = image_find(name, &i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); + + i->userdata = userdata; + return bus_image_method_remove(message, i, error); +} + +static int method_rename_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(image_unrefp) Image* i = NULL; + const char *old_name; + int r; + + assert(message); + + r = sd_bus_message_read(message, "s", &old_name); + if (r < 0) + return r; + + if (!image_name_is_valid(old_name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name); + + r = image_find(old_name, &i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name); + + i->userdata = userdata; + return bus_image_method_rename(message, i, error); +} + +static int method_clone_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(image_unrefp) Image *i = NULL; + const char *old_name; + int r; + + assert(message); + + r = sd_bus_message_read(message, "s", &old_name); + if (r < 0) + return r; + + if (!image_name_is_valid(old_name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name); + + r = image_find(old_name, &i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name); + + i->userdata = userdata; + return bus_image_method_clone(message, i, error); +} + +static int method_mark_image_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(image_unrefp) Image *i = NULL; + const char *name; + int r; + + assert(message); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + if (!image_name_is_valid(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); + + r = image_find(name, &i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); + + i->userdata = userdata; + return bus_image_method_mark_read_only(message, i, error); +} + +static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + uint64_t limit; + int r; + + assert(message); + + 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, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + /* Set up the machine directory if necessary */ + r = setup_machine_directory(limit, error); + if (r < 0) + return r; + + /* 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); + + r = btrfs_subvol_set_subtree_quota_limit("/var/lib/machines", 0, limit); + if (r == -ENOTTY) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs."); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to adjust quota limit: %m"); + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_set_image_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(image_unrefp) Image *i = NULL; + const char *name; + int r; + + assert(message); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + if (!image_name_is_valid(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); + + r = image_find(name, &i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); + + i->userdata = userdata; + return bus_image_method_set_limit(message, i, error); +} + +static int method_map_from_machine_user(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_fclose_ FILE *f = NULL; + Manager *m = userdata; + const char *name, *p; + Machine *machine; + uint32_t uid; + int r; + + r = sd_bus_message_read(message, "su", &name, &uid); + if (r < 0) + return r; + + if (!uid_is_valid(uid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + if (machine->class != MACHINE_CONTAINER) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not supported for non-container machines."); + + p = procfs_file_alloca(machine->leader, "uid_map"); + f = fopen(p, "re"); + if (!f) + return -errno; + + for (;;) { + uid_t uid_base, uid_shift, uid_range, converted; + int k; + + errno = 0; + k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range); + if (k < 0 && feof(f)) + break; + if (k != 3) { + if (ferror(f) && errno > 0) + return -errno; + + return -EIO; + } + + if (uid < uid_base || uid >= uid_base + uid_range) + continue; + + converted = uid - uid_base + uid_shift; + if (!uid_is_valid(converted)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); + + return sd_bus_reply_method_return(message, "u", (uint32_t) converted); + } + + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "Machine '%s' has no matching user mappings.", name); +} + +static int method_map_to_machine_user(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + uid_t uid; + Iterator i; + int r; + + r = sd_bus_message_read(message, "u", &uid); + if (r < 0) + return r; + if (!uid_is_valid(uid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); + if (uid < 0x10000) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "User " UID_FMT " belongs to host UID range", uid); + + HASHMAP_FOREACH(machine, m->machines, i) { + _cleanup_fclose_ FILE *f = NULL; + char p[strlen("/proc//uid_map") + DECIMAL_STR_MAX(pid_t) + 1]; + + if (machine->class != MACHINE_CONTAINER) + continue; + + xsprintf(p, "/proc/" UID_FMT "/uid_map", machine->leader); + f = fopen(p, "re"); + if (!f) { + log_warning_errno(errno, "Failed top open %s, ignoring,", p); + continue; + } + + for (;;) { + _cleanup_free_ char *o = NULL; + uid_t uid_base, uid_shift, uid_range, converted; + int k; + + errno = 0; + k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range); + if (k < 0 && feof(f)) + break; + if (k != 3) { + if (ferror(f) && errno > 0) + return -errno; + + return -EIO; + } + + if (uid < uid_shift || uid >= uid_shift + uid_range) + continue; + + converted = (uid - uid_shift + uid_base); + if (!uid_is_valid(converted)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); + + o = machine_bus_path(machine); + if (!o) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted); + } + } + + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "No matching user mapping for " UID_FMT ".", uid); +} + +static int method_map_from_machine_group(sd_bus_message *message, void *groupdata, sd_bus_error *error) { + _cleanup_fclose_ FILE *f = NULL; + Manager *m = groupdata; + const char *name, *p; + Machine *machine; + uint32_t gid; + int r; + + r = sd_bus_message_read(message, "su", &name, &gid); + if (r < 0) + return r; + + if (!gid_is_valid(gid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + if (machine->class != MACHINE_CONTAINER) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not supported for non-container machines."); + + p = procfs_file_alloca(machine->leader, "gid_map"); + f = fopen(p, "re"); + if (!f) + return -errno; + + for (;;) { + gid_t gid_base, gid_shift, gid_range, converted; + int k; + + errno = 0; + k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT, &gid_base, &gid_shift, &gid_range); + if (k < 0 && feof(f)) + break; + if (k != 3) { + if (ferror(f) && errno > 0) + return -errno; + + return -EIO; + } + + if (gid < gid_base || gid >= gid_base + gid_range) + continue; + + converted = gid - gid_base + gid_shift; + if (!gid_is_valid(converted)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); + + return sd_bus_reply_method_return(message, "u", (uint32_t) converted); + } + + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "Machine '%s' has no matching group mappings.", name); +} + +static int method_map_to_machine_group(sd_bus_message *message, void *groupdata, sd_bus_error *error) { + Manager *m = groupdata; + Machine *machine; + gid_t gid; + Iterator i; + int r; + + r = sd_bus_message_read(message, "u", &gid); + if (r < 0) + return r; + if (!gid_is_valid(gid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); + if (gid < 0x10000) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "Group " GID_FMT " belongs to host GID range", gid); + + HASHMAP_FOREACH(machine, m->machines, i) { + _cleanup_fclose_ FILE *f = NULL; + char p[strlen("/proc//gid_map") + DECIMAL_STR_MAX(pid_t) + 1]; + + if (machine->class != MACHINE_CONTAINER) + continue; + + xsprintf(p, "/proc/" GID_FMT "/gid_map", machine->leader); + f = fopen(p, "re"); + if (!f) { + log_warning_errno(errno, "Failed top open %s, ignoring,", p); + continue; + } + + for (;;) { + _cleanup_free_ char *o = NULL; + gid_t gid_base, gid_shift, gid_range, converted; + int k; + + errno = 0; + k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT, &gid_base, &gid_shift, &gid_range); + if (k < 0 && feof(f)) + break; + if (k != 3) { + if (ferror(f) && errno > 0) + return -errno; + + return -EIO; + } + + if (gid < gid_shift || gid >= gid_shift + gid_range) + continue; + + converted = (gid - gid_shift + gid_base); + if (!gid_is_valid(converted)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); + + o = machine_bus_path(machine); + if (!o) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted); + } + } + + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "No matching group mapping for " GID_FMT ".", gid); +} + +const sd_bus_vtable manager_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("PoolPath", "s", property_get_pool_path, 0, 0), + SD_BUS_PROPERTY("PoolUsage", "t", property_get_pool_usage, 0, 0), + SD_BUS_PROPERTY("PoolLimit", "t", property_get_pool_limit, 0, 0), + SD_BUS_METHOD("GetMachine", "s", "o", method_get_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetImage", "s", "o", method_get_image, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetMachineByPID", "u", "o", method_get_machine_by_pid, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListMachines", NULL, "a(ssso)", method_list_machines, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListImages", NULL, "a(ssbttto)", method_list_images, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CreateMachine", "sayssusa(sv)", "o", method_create_machine, 0), + SD_BUS_METHOD("CreateMachineWithNetwork", "sayssusaia(sv)", "o", method_create_machine_with_network, 0), + SD_BUS_METHOD("RegisterMachine", "sayssus", "o", method_register_machine, 0), + SD_BUS_METHOD("RegisterMachineWithNetwork", "sayssusai", "o", method_register_machine_with_network, 0), + SD_BUS_METHOD("TerminateMachine", "s", NULL, method_terminate_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("KillMachine", "ssi", NULL, method_kill_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetMachineAddresses", "s", "a(iay)", method_get_machine_addresses, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetMachineOSRelease", "s", "a{ss}", method_get_machine_os_release, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenMachinePTY", "s", "hs", method_open_machine_pty, 0), + SD_BUS_METHOD("OpenMachineLogin", "s", "hs", method_open_machine_login, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenMachineShell", "sssasas", "hs", method_open_machine_shell, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("BindMountMachine", "sssbb", NULL, method_bind_mount_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CopyFromMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CopyToMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RemoveImage", "s", NULL, method_remove_image, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RenameImage", "ss", NULL, method_rename_image, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CloneImage", "ssb", NULL, method_clone_image, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MarkImageReadOnly", "sb", NULL, method_mark_image_read_only, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetPoolLimit", "t", NULL, method_set_pool_limit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetImageLimit", "st", NULL, method_set_image_limit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MapFromMachineUser", "su", "u", method_map_from_machine_user, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MapToMachineUser", "u", "sou", method_map_to_machine_user, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MapFromMachineGroup", "su", "u", method_map_from_machine_group, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MapToMachineGroup", "u", "sou", method_map_to_machine_group, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_SIGNAL("MachineNew", "so", 0), + SD_BUS_SIGNAL("MachineRemoved", "so", 0), + SD_BUS_VTABLE_END +}; + +int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + const char *path, *result, *unit; + Manager *m = userdata; + Machine *machine; + uint32_t id; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "uoss", &id, &path, &unit, &result); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + machine = hashmap_get(m->machine_units, unit); + if (!machine) + return 0; + + if (streq_ptr(path, machine->scope_job)) { + machine->scope_job = mfree(machine->scope_job); + + if (machine->started) { + if (streq(result, "done")) + machine_send_create_reply(machine, NULL); + else { + _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; + + sd_bus_error_setf(&e, BUS_ERROR_JOB_FAILED, "Start job for unit %s failed with '%s'", unit, result); + + machine_send_create_reply(machine, &e); + } + } + + machine_save(machine); + } + + machine_add_to_gc_queue(machine); + return 0; +} + +int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *unit = NULL; + const char *path; + Manager *m = userdata; + Machine *machine; + int r; + + assert(message); + assert(m); + + path = sd_bus_message_get_path(message); + if (!path) + return 0; + + r = unit_name_from_dbus_path(path, &unit); + if (r == -EINVAL) /* not for a unit */ + return 0; + if (r < 0){ + log_oom(); + return 0; + } + + machine = hashmap_get(m->machine_units, unit); + if (!machine) + return 0; + + machine_add_to_gc_queue(machine); + return 0; +} + +int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + const char *path, *unit; + Manager *m = userdata; + Machine *machine; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "so", &unit, &path); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + machine = hashmap_get(m->machine_units, unit); + if (!machine) + return 0; + + machine_add_to_gc_queue(machine); + return 0; +} + +int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + Iterator i; + int b, r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + if (b) + return 0; + + /* systemd finished reloading, let's recheck all our machines */ + log_debug("System manager has been reloaded, rechecking machines..."); + + HASHMAP_FOREACH(machine, m->machines, i) + machine_add_to_gc_queue(machine); + + return 0; +} + +int manager_start_scope( + Manager *manager, + const char *scope, + pid_t pid, + const char *slice, + const char *description, + sd_bus_message *more_properties, + sd_bus_error *error, + char **job) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + int r; + + assert(manager); + assert(scope); + assert(pid > 1); + + r = sd_bus_message_new_method_call( + manager->bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "ss", strempty(scope), "fail"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return r; + + if (!isempty(slice)) { + r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); + if (r < 0) + return r; + } + + if (!isempty(description)) { + r = sd_bus_message_append(m, "(sv)", "Description", "s", description); + if (r < 0) + return r; + } + + r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, pid); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)", "Delegate", "b", 1); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", UINT64_C(16384)); + if (r < 0) + return bus_log_create_error(r); + + if (more_properties) { + r = sd_bus_message_copy(m, more_properties, true); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "a(sa(sv))", 0); + if (r < 0) + return r; + + r = sd_bus_call(manager->bus, m, 0, error, &reply); + 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; +} + +int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + assert(manager); + assert(unit); + + r = sd_bus_call_method( + manager->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StopUnit", + error, + &reply, + "ss", unit, "fail"); + if (r < 0) { + 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; + + sd_bus_error_free(error); + return 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; +} + +int manager_kill_unit(Manager *manager, const char *unit, int signo, sd_bus_error *error) { + assert(manager); + assert(unit); + + return sd_bus_call_method( + manager->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "KillUnit", + error, + NULL, + "ssi", unit, "all", signo); +} + +int manager_unit_is_active(Manager *manager, const char *unit) { + _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 *path = NULL; + const char *state; + int r; + + assert(manager); + assert(unit); + + path = unit_dbus_path_from_name(unit); + if (!path) + return -ENOMEM; + + r = sd_bus_get_property( + manager->bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "ActiveState", + &error, + &reply, + "s"); + if (r < 0) { + if (sd_bus_error_has_name(&error, SD_BUS_ERROR_NO_REPLY) || + sd_bus_error_has_name(&error, SD_BUS_ERROR_DISCONNECTED)) + return true; + + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT) || + sd_bus_error_has_name(&error, BUS_ERROR_LOAD_FAILED)) + return false; + + return r; + } + + r = sd_bus_message_read(reply, "s", &state); + if (r < 0) + return -EINVAL; + + return !STR_IN_SET(state, "inactive", "failed"); +} + +int manager_job_is_active(Manager *manager, const char *path) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + assert(manager); + assert(path); + + r = sd_bus_get_property( + manager->bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Job", + "State", + &error, + &reply, + "s"); + if (r < 0) { + if (sd_bus_error_has_name(&error, SD_BUS_ERROR_NO_REPLY) || + sd_bus_error_has_name(&error, SD_BUS_ERROR_DISCONNECTED)) + return true; + + if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_OBJECT)) + return false; + + return r; + } + + /* We don't actually care about the state really. The fact + * that we could read the job state is enough for us */ + + return true; +} + +int manager_get_machine_by_pid(Manager *m, pid_t pid, Machine **machine) { + Machine *mm; + int r; + + assert(m); + assert(pid >= 1); + assert(machine); + + mm = hashmap_get(m->machine_leaders, PID_TO_PTR(pid)); + if (!mm) { + _cleanup_free_ char *unit = NULL; + + r = cg_pid_get_unit(pid, &unit); + if (r >= 0) + mm = hashmap_get(m->machine_units, unit); + } + if (!mm) + return 0; + + *machine = mm; + return 1; +} + +int manager_add_machine(Manager *m, const char *name, Machine **_machine) { + Machine *machine; + + assert(m); + assert(name); + + machine = hashmap_get(m->machines, name); + if (!machine) { + machine = machine_new(m, _MACHINE_CLASS_INVALID, name); + if (!machine) + return -ENOMEM; + } + + if (_machine) + *_machine = machine; + + return 0; +} diff --git a/src/machine/src/machined.c b/src/machine/src/machined.c new file mode 100644 index 0000000000..f2c1966a6b --- /dev/null +++ b/src/machine/src/machined.c @@ -0,0 +1,407 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +#include +#include +#include + +#include "sd-daemon.h" + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "hostname-util.h" +#include "label.h" +#include "machine-image.h" +#include "machined.h" +#include "signal-util.h" + +Manager *manager_new(void) { + Manager *m; + int r; + + m = new0(Manager, 1); + if (!m) + return NULL; + + m->machines = hashmap_new(&string_hash_ops); + m->machine_units = hashmap_new(&string_hash_ops); + m->machine_leaders = hashmap_new(NULL); + + if (!m->machines || !m->machine_units || !m->machine_leaders) { + manager_free(m); + return NULL; + } + + r = sd_event_default(&m->event); + if (r < 0) { + manager_free(m); + return NULL; + } + + sd_event_set_watchdog(m->event, true); + + return m; +} + +void manager_free(Manager *m) { + Machine *machine; + Image *i; + + assert(m); + + while ((machine = hashmap_first(m->machines))) + machine_free(machine); + + hashmap_free(m->machines); + hashmap_free(m->machine_units); + hashmap_free(m->machine_leaders); + + while ((i = hashmap_steal_first(m->image_cache))) + image_unref(i); + + hashmap_free(m->image_cache); + + sd_event_source_unref(m->image_cache_defer_event); + + bus_verify_polkit_async_registry_free(m->polkit_registry); + + sd_bus_unref(m->bus); + sd_event_unref(m->event); + + free(m); +} + +static int manager_add_host_machine(Manager *m) { + _cleanup_free_ char *rd = NULL, *unit = NULL; + sd_id128_t mid; + Machine *t; + int r; + + if (m->host_machine) + return 0; + + r = sd_id128_get_machine(&mid); + if (r < 0) + return log_error_errno(r, "Failed to get machine ID: %m"); + + rd = strdup("/"); + if (!rd) + return log_oom(); + + unit = strdup("-.slice"); + if (!unit) + return log_oom(); + + t = machine_new(m, MACHINE_HOST, ".host"); + if (!t) + return log_oom(); + + t->leader = 1; + t->id = mid; + + t->root_directory = rd; + t->unit = unit; + rd = unit = NULL; + + dual_timestamp_from_boottime_or_monotonic(&t->timestamp, 0); + + m->host_machine = t; + + return 0; +} + +int manager_enumerate_machines(Manager *m) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r = 0; + + assert(m); + + r = manager_add_host_machine(m); + if (r < 0) + return r; + + /* Read in machine data stored on disk */ + d = opendir("/run/systemd/machines"); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open /run/systemd/machines: %m"); + } + + FOREACH_DIRENT(de, d, return -errno) { + struct Machine *machine; + int k; + + if (!dirent_is_file(de)) + continue; + + /* Ignore symlinks that map the unit name to the machine */ + if (startswith(de->d_name, "unit:")) + continue; + + if (!machine_name_is_valid(de->d_name)) + continue; + + k = manager_add_machine(m, de->d_name, &machine); + if (k < 0) { + r = log_error_errno(k, "Failed to add machine by file name %s: %m", de->d_name); + continue; + } + + machine_add_to_gc_queue(machine); + + k = machine_load(machine); + if (k < 0) + r = k; + } + + return r; +} + +static int manager_connect_bus(Manager *m) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(m); + assert(!m->bus); + + r = sd_bus_default_system(&m->bus); + if (r < 0) + return log_error_errno(r, "Failed to connect to system bus: %m"); + + r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", manager_vtable, m); + if (r < 0) + return log_error_errno(r, "Failed to add manager object vtable: %m"); + + r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/machine1/machine", "org.freedesktop.machine1.Machine", machine_vtable, machine_object_find, m); + if (r < 0) + return log_error_errno(r, "Failed to add machine object vtable: %m"); + + r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/machine1/machine", machine_node_enumerator, m); + if (r < 0) + return log_error_errno(r, "Failed to add machine enumerator: %m"); + + r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/machine1/image", "org.freedesktop.machine1.Image", image_vtable, image_object_find, m); + if (r < 0) + return log_error_errno(r, "Failed to add image object vtable: %m"); + + r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/machine1/image", image_node_enumerator, m); + if (r < 0) + return log_error_errno(r, "Failed to add image enumerator: %m"); + + r = sd_bus_add_match(m->bus, + NULL, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='JobRemoved'," + "path='/org/freedesktop/systemd1'", + match_job_removed, + m); + if (r < 0) + return log_error_errno(r, "Failed to add match for JobRemoved: %m"); + + r = sd_bus_add_match(m->bus, + NULL, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='UnitRemoved'," + "path='/org/freedesktop/systemd1'", + match_unit_removed, + m); + if (r < 0) + return log_error_errno(r, "Failed to add match for UnitRemoved: %m"); + + r = sd_bus_add_match(m->bus, + NULL, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.DBus.Properties'," + "member='PropertiesChanged'," + "arg0='org.freedesktop.systemd1.Unit'", + match_properties_changed, + m); + if (r < 0) + return log_error_errno(r, "Failed to add match for PropertiesChanged: %m"); + + r = sd_bus_add_match(m->bus, + NULL, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='Reloading'," + "path='/org/freedesktop/systemd1'", + match_reloading, + m); + if (r < 0) + return log_error_errno(r, "Failed to add match for Reloading: %m"); + + r = sd_bus_call_method( + m->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Subscribe", + &error, + NULL, NULL); + if (r < 0) { + log_error("Failed to enable subscription: %s", bus_error_message(&error, r)); + return r; + } + + r = sd_bus_request_name(m->bus, "org.freedesktop.machine1", 0); + if (r < 0) + return log_error_errno(r, "Failed to register name: %m"); + + r = sd_bus_attach_event(m->bus, m->event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + return 0; +} + +void manager_gc(Manager *m, bool drop_not_started) { + Machine *machine; + + assert(m); + + while ((machine = m->machine_gc_queue)) { + LIST_REMOVE(gc_queue, m->machine_gc_queue, machine); + machine->in_gc_queue = false; + + /* First, if we are not closing yet, initiate stopping */ + if (!machine_check_gc(machine, drop_not_started) && + machine_get_state(machine) != MACHINE_CLOSING) + machine_stop(machine); + + /* Now, the stop stop probably made this referenced + * again, but if it didn't, then it's time to let it + * go entirely. */ + if (!machine_check_gc(machine, drop_not_started)) { + machine_finalize(machine); + machine_free(machine); + } + } +} + +int manager_startup(Manager *m) { + Machine *machine; + Iterator i; + int r; + + assert(m); + + /* Connect to the bus */ + r = manager_connect_bus(m); + if (r < 0) + return r; + + /* Deserialize state */ + manager_enumerate_machines(m); + + /* Remove stale objects before we start them */ + manager_gc(m, false); + + /* And start everything */ + HASHMAP_FOREACH(machine, m->machines, i) + machine_start(machine, NULL, NULL); + + return 0; +} + +static bool check_idle(void *userdata) { + Manager *m = userdata; + + manager_gc(m, true); + + return hashmap_isempty(m->machines); +} + +int manager_run(Manager *m) { + assert(m); + + return bus_event_loop_with_idle( + m->event, + m->bus, + "org.freedesktop.machine1", + DEFAULT_EXIT_USEC, + check_idle, m); +} + +int main(int argc, char *argv[]) { + Manager *m = NULL; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_set_facility(LOG_AUTH); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto finish; + } + + /* Always create the directories people can create inotify + * watches in. Note that some applications might check for the + * existence of /run/systemd/machines/ to determine whether + * machined is available, so please always make sure this + * check stays in. */ + mkdir_label("/run/systemd/machines", 0755); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); + + m = manager_new(); + if (!m) { + r = log_oom(); + goto finish; + } + + r = manager_startup(m); + if (r < 0) { + log_error_errno(r, "Failed to fully start up daemon: %m"); + goto finish; + } + + log_debug("systemd-machined running as pid "PID_FMT, getpid()); + + sd_notify(false, + "READY=1\n" + "STATUS=Processing requests..."); + + r = manager_run(m); + + log_debug("systemd-machined stopped as pid "PID_FMT, getpid()); + +finish: + manager_free(m); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/machine/src/machined.h b/src/machine/src/machined.h new file mode 100644 index 0000000000..e7d7dfdceb --- /dev/null +++ b/src/machine/src/machined.h @@ -0,0 +1,78 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +#include + +#include "sd-bus.h" +#include "sd-event.h" + +#include "hashmap.h" +#include "list.h" + +typedef struct Manager Manager; + +#include "image-dbus.h" +#include "machine-dbus.h" +#include "machine.h" + +struct Manager { + sd_event *event; + sd_bus *bus; + + Hashmap *machines; + Hashmap *machine_units; + Hashmap *machine_leaders; + + Hashmap *polkit_registry; + + Hashmap *image_cache; + sd_event_source *image_cache_defer_event; + + LIST_HEAD(Machine, machine_gc_queue); + + Machine *host_machine; +}; + +Manager *manager_new(void); +void manager_free(Manager *m); + +int manager_add_machine(Manager *m, const char *name, Machine **_machine); +int manager_enumerate_machines(Manager *m); + +int manager_startup(Manager *m); +int manager_run(Manager *m); + +void manager_gc(Manager *m, bool drop_not_started); + +int manager_get_machine_by_pid(Manager *m, pid_t pid, Machine **machine); + +extern const sd_bus_vtable manager_vtable[]; + +int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error); +int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error); +int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error); +int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error); + +int manager_start_scope(Manager *manager, const char *scope, pid_t pid, const char *slice, const char *description, sd_bus_message *more_properties, sd_bus_error *error, char **job); +int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job); +int manager_kill_unit(Manager *manager, const char *unit, int signo, sd_bus_error *error); +int manager_unit_is_active(Manager *manager, const char *unit); +int manager_job_is_active(Manager *manager, const char *path); diff --git a/src/machine/src/org.freedesktop.machine1.conf b/src/machine/src/org.freedesktop.machine1.conf new file mode 100644 index 0000000000..9d40b90151 --- /dev/null +++ b/src/machine/src/org.freedesktop.machine1.conf @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/machine/src/org.freedesktop.machine1.policy.in b/src/machine/src/org.freedesktop.machine1.policy.in new file mode 100644 index 0000000000..69f78a5c25 --- /dev/null +++ b/src/machine/src/org.freedesktop.machine1.policy.in @@ -0,0 +1,102 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + <_description>Log into a local container + <_message>Authentication is required to log into a local container. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Log into the local host + <_message>Authentication is required to log into the local host. + + auth_admin + auth_admin + yes + + + + + <_description>Acquire a shell in a local container + <_message>Authentication is required to acquire a shell in a local container. + + auth_admin + auth_admin + auth_admin_keep + + org.freedesktop.login1.login + + + + <_description>Acquire a shell on the local host + <_message>Authentication is required to acquire a shell on the local host. + + auth_admin + auth_admin + auth_admin_keep + + org.freedesktop.login1.host-login + + + + <_description>Acquire a pseudo TTY in a local container + <_message>Authentication is required to acquire a pseudo TTY in a local container. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Acquire a pseudo TTY on the local host + <_message>Authentication is required to acquire a pseudo TTY on the local host. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Manage local virtual machines and containers + <_message>Authentication is required to manage local virtual machines and containers. + + auth_admin + auth_admin + auth_admin_keep + + org.freedesktop.login1.shell org.freedesktop.login1.login + + + + <_description>Manage local virtual machine and container images + <_message>Authentication is required to manage local virtual machine and container images. + + auth_admin + auth_admin + auth_admin_keep + + + + diff --git a/src/machine/src/org.freedesktop.machine1.service b/src/machine/src/org.freedesktop.machine1.service new file mode 100644 index 0000000000..d3dc99852b --- /dev/null +++ b/src/machine/src/org.freedesktop.machine1.service @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# 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. + +[D-BUS Service] +Name=org.freedesktop.machine1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.machine1.service diff --git a/src/machine/src/test-machine-tables.c b/src/machine/src/test-machine-tables.c new file mode 100644 index 0000000000..f851a4d37d --- /dev/null +++ b/src/machine/src/test-machine-tables.c @@ -0,0 +1,29 @@ +/*** + 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 . +***/ + +#include "machine.h" +#include "test-tables.h" + +int main(int argc, char **argv) { + test_table(machine_class, MACHINE_CLASS); + test_table(machine_state, MACHINE_STATE); + test_table(kill_who, KILL_WHO); + + return EXIT_SUCCESS; +} diff --git a/src/machine/test-machine-tables.c b/src/machine/test-machine-tables.c deleted file mode 100644 index f851a4d37d..0000000000 --- a/src/machine/test-machine-tables.c +++ /dev/null @@ -1,29 +0,0 @@ -/*** - 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 . -***/ - -#include "machine.h" -#include "test-tables.h" - -int main(int argc, char **argv) { - test_table(machine_class, MACHINE_CLASS); - test_table(machine_state, MACHINE_STATE); - test_table(kill_who, KILL_WHO); - - return EXIT_SUCCESS; -} diff --git a/src/resolve/.gitignore b/src/resolve/.gitignore deleted file mode 100644 index f0835923b7..0000000000 --- a/src/resolve/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/resolved-gperf.c -/resolved.conf -/dns_type-from-name.gperf -/dns_type-from-name.h -/dns_type-list.txt -/dns_type-to-name.h diff --git a/src/resolve/Makefile b/src/resolve/Makefile deleted file mode 100644 index 3aa6a73e4c..0000000000 --- a/src/resolve/Makefile +++ /dev/null @@ -1,204 +0,0 @@ -# -*- Mode: makefile; indent-tabs-mode: t -*- -# -# This file is part of systemd. -# -# Copyright 2010-2012 Lennart Poettering -# Copyright 2010-2012 Kay Sievers -# Copyright 2013 Zbigniew Jędrzejewski-Szmek -# Copyright 2013 David Strauss -# Copyright 2016 Luke Shumaker -# -# 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 . -if ENABLE_RESOLVED - -systemd_resolved_SOURCES = \ - src/resolve/resolved.c \ - src/resolve/resolved-manager.c \ - src/resolve/resolved-manager.h \ - src/resolve/resolved-conf.c \ - src/resolve/resolved-conf.h \ - src/resolve/resolved-resolv-conf.c \ - src/resolve/resolved-resolv-conf.h \ - src/resolve/resolved-bus.c \ - 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 \ - src/resolve/resolved-mdns.c \ - src/resolve/resolved-def.h \ - src/resolve/resolved-dns-rr.h \ - src/resolve/resolved-dns-rr.c \ - src/resolve/resolved-dns-question.h \ - src/resolve/resolved-dns-question.c \ - src/resolve/resolved-dns-answer.h \ - src/resolve/resolved-dns-answer.c \ - src/resolve/resolved-dns-packet.h \ - 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 \ - src/resolve/resolved-dns-scope.c \ - src/resolve/resolved-dns-server.h \ - src/resolve/resolved-dns-server.c \ - src/resolve/resolved-dns-search-domain.h \ - src/resolve/resolved-dns-search-domain.c \ - src/resolve/resolved-dns-cache.h \ - src/resolve/resolved-dns-cache.c \ - src/resolve/resolved-dns-zone.h \ - src/resolve/resolved-dns-zone.c \ - src/resolve/resolved-dns-stream.h \ - src/resolve/resolved-dns-stream.c \ - src/resolve/resolved-dns-dnssec.h \ - 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 - -nodist_systemd_resolved_SOURCES = \ - src/resolve/dns_type-from-name.h \ - src/resolve/dns_type-to-name.h \ - src/resolve/resolved-gperf.c - -systemd_resolved_LDADD = \ - libsystemd-network.la \ - libshared.la - -rootlibexec_PROGRAMS += \ - systemd-resolved - -nodist_systemunit_DATA += \ - units/systemd-resolved.service - -dist_systemunit_DATA_busnames += \ - units/org.freedesktop.resolve1.busname - -dist_dbuspolicy_DATA += \ - src/resolve/org.freedesktop.resolve1.conf - -dist_dbussystemservice_DATA += \ - src/resolve/org.freedesktop.resolve1.service - -SYSTEM_UNIT_ALIASES += \ - systemd-resolved.service dbus-org.freedesktop.resolve1.service - -BUSNAMES_TARGET_WANTS += \ - org.freedesktop.resolve1.busname - -GENERAL_ALIASES += \ - $(systemunitdir)/systemd-resolved.service $(pkgsysconfdir)/system/multi-user.target.wants/systemd-resolved.service - -nodist_pkgsysconf_DATA += \ - src/resolve/resolved.conf - -libnss_resolve_la_SOURCES = \ - src/nss-resolve/nss-resolve.sym \ - src/nss-resolve/nss-resolve.c - -libnss_resolve_la_LDFLAGS = \ - $(AM_LDFLAGS) \ - -module \ - -export-dynamic \ - -avoid-version \ - -shared \ - -shrext .so.2 \ - -Wl,--version-script=$(top_srcdir)/src/nss-resolve/nss-resolve.sym - -libnss_resolve_la_LIBADD = \ - libsystemd-internal.la \ - -ldl - -lib_LTLIBRARIES += \ - libnss_resolve.la - -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 \ - src/resolve/resolved-dns-rr.h \ - src/resolve/resolved-dns-answer.c \ - src/resolve/resolved-dns-answer.h \ - src/resolve/resolved-dns-question.c \ - src/resolve/resolved-dns-question.h \ - src/resolve/dns-type.c \ - src/resolve/dns-type.h - -nodist_systemd_resolve_SOURCES = \ - src/resolve/dns_type-from-name.h \ - src/resolve/dns_type-to-name.h - -systemd_resolve_LDADD = \ - libshared.la - -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 \ - src/resolve/resolved-dns-packet.h \ - src/resolve/resolved-dns-rr.c \ - src/resolve/resolved-dns-rr.h \ - src/resolve/resolved-dns-answer.c \ - src/resolve/resolved-dns-answer.h \ - src/resolve/resolved-dns-question.c \ - src/resolve/resolved-dns-question.h \ - src/resolve/resolved-dns-dnssec.c \ - src/resolve/resolved-dns-dnssec.h \ - src/resolve/dns-type.c \ - src/resolve/dns-type.h - -test_dnssec_LDADD = \ - libshared.la - -test_dnssec_complex_SOURCES = \ - src/resolve/test-dnssec-complex.c \ - src/resolve/dns-type.c \ - src/resolve/dns-type.h - -test_dnssec_complex_LDADD = \ - libshared.la - -endif - -gperf_txt_sources += \ - src/resolve/dns_type-list.txt - -gperf_gperf_sources += \ - src/resolve/resolved-gperf.gperf - -EXTRA_DIST += \ - units/systemd-resolved.service.m4.in \ - src/resolve/resolved.conf.in - diff --git a/src/resolve/RFCs b/src/resolve/RFCs deleted file mode 100644 index 22004a00cd..0000000000 --- a/src/resolve/RFCs +++ /dev/null @@ -1,59 +0,0 @@ -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 deleted file mode 100644 index b2f479cae5..0000000000 --- a/src/resolve/dns-type.c +++ /dev/null @@ -1,306 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -#include "dns-type.h" -#include "parse-util.h" -#include "string-util.h" - -typedef const struct { - uint16_t type; - const char *name; -} dns_type; - -static const struct dns_type_name * -lookup_dns_type (register const char *str, register unsigned int len); - -#include "dns_type-from-name.h" -#include "dns_type-to-name.h" - -int dns_type_from_string(const char *s) { - const struct dns_type_name *sc; - - assert(s); - - sc = lookup_dns_type(s, strlen(s)); - 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 - * . */ - - 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, - - /* 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 */ -} - -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 deleted file mode 100644 index a6c1630021..0000000000 --- a/src/resolve/dns-type.h +++ /dev/null @@ -1,154 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#pragma once - -#include "macro.h" - -/* DNS record types, taken from - * http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml. - */ -enum { - /* Normal records */ - DNS_TYPE_A = 0x01, - DNS_TYPE_NS, - DNS_TYPE_MD, - DNS_TYPE_MF, - DNS_TYPE_CNAME, - DNS_TYPE_SOA, - DNS_TYPE_MB, - DNS_TYPE_MG, - DNS_TYPE_MR, - DNS_TYPE_NULL, - DNS_TYPE_WKS, - DNS_TYPE_PTR, - DNS_TYPE_HINFO, - DNS_TYPE_MINFO, - DNS_TYPE_MX, - DNS_TYPE_TXT, - DNS_TYPE_RP, - DNS_TYPE_AFSDB, - DNS_TYPE_X25, - DNS_TYPE_ISDN, - DNS_TYPE_RT, - DNS_TYPE_NSAP, - DNS_TYPE_NSAP_PTR, - DNS_TYPE_SIG, - DNS_TYPE_KEY, - DNS_TYPE_PX, - DNS_TYPE_GPOS, - DNS_TYPE_AAAA, - DNS_TYPE_LOC, - DNS_TYPE_NXT, - DNS_TYPE_EID, - DNS_TYPE_NIMLOC, - DNS_TYPE_SRV, - DNS_TYPE_ATMA, - DNS_TYPE_NAPTR, - DNS_TYPE_KX, - DNS_TYPE_CERT, - DNS_TYPE_A6, - DNS_TYPE_DNAME, - DNS_TYPE_SINK, - DNS_TYPE_OPT, /* EDNS0 option */ - DNS_TYPE_APL, - DNS_TYPE_DS, - DNS_TYPE_SSHFP, - DNS_TYPE_IPSECKEY, - DNS_TYPE_RRSIG, - DNS_TYPE_NSEC, - DNS_TYPE_DNSKEY, - DNS_TYPE_DHCID, - DNS_TYPE_NSEC3, - DNS_TYPE_NSEC3PARAM, - DNS_TYPE_TLSA, - - DNS_TYPE_HIP = 0x37, - DNS_TYPE_NINFO, - DNS_TYPE_RKEY, - DNS_TYPE_TALINK, - DNS_TYPE_CDS, - DNS_TYPE_CDNSKEY, - DNS_TYPE_OPENPGPKEY, - - DNS_TYPE_SPF = 0x63, - DNS_TYPE_NID, - DNS_TYPE_L32, - DNS_TYPE_L64, - DNS_TYPE_LP, - DNS_TYPE_EUI48, - DNS_TYPE_EUI64, - - DNS_TYPE_TKEY = 0xF9, - DNS_TYPE_TSIG, - DNS_TYPE_IXFR, - DNS_TYPE_AXFR, - DNS_TYPE_MAILB, - DNS_TYPE_MAILA, - DNS_TYPE_ANY, - DNS_TYPE_URI, - DNS_TYPE_CAA, - DNS_TYPE_TA = 0x8000, - DNS_TYPE_DLV, - - _DNS_TYPE_MAX, - _DNS_TYPE_INVALID = -1 -}; - -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/org.freedesktop.resolve1.conf b/src/resolve/org.freedesktop.resolve1.conf deleted file mode 100644 index 25b09774e5..0000000000 --- a/src/resolve/org.freedesktop.resolve1.conf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/resolve/org.freedesktop.resolve1.service b/src/resolve/org.freedesktop.resolve1.service deleted file mode 100644 index 7ac5c323f0..0000000000 --- a/src/resolve/org.freedesktop.resolve1.service +++ /dev/null @@ -1,12 +0,0 @@ -# This file is part of systemd. -# -# 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. - -[D-BUS Service] -Name=org.freedesktop.resolve1 -Exec=/bin/false -User=root -SystemdService=dbus-org.freedesktop.resolve1.service diff --git a/src/resolve/resolve-tool.c b/src/resolve/resolve-tool.c deleted file mode 100644 index 824cb267b5..0000000000 --- a/src/resolve/resolve-tool.c +++ /dev/null @@ -1,1272 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include - -#include "sd-bus.h" - -#include "af-list.h" -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "escape.h" -#include "in-addr-util.h" -#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 uint16_t arg_type = 0; -static uint16_t arg_class = 0; -static bool arg_legend = true; -static uint64_t arg_flags = 0; - -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]; - - if (!arg_legend) - return; - - if (flags == 0) - return; - - fputs("\n-- Information acquired via", stdout); - - if (flags != 0) - 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_MDNS_IPV4 ? "mDNS/IPv4" : "", - flags & SD_RESOLVED_MDNS_IPV6 ? "mDNS/IPv6" : ""); - - assert_se(format_timespan(rtt_str, sizeof(rtt_str), rtt, 100)); - - printf(" in %s", rtt_str); - - fputc('.', stdout); - fputc('\n', stdout); - - printf("-- Data is authenticated: %s\n", yes_no(flags & SD_RESOLVED_AUTHENTICATED)); -} - -static int resolve_host(sd_bus *bus, const char *name) { - - _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; - const char *canonical = NULL; - char ifname[IF_NAMESIZE] = ""; - unsigned c = 0; - int r; - uint64_t flags; - usec_t ts; - - assert(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 (family %s, interface %s).", name, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveHostname"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isit", arg_ifindex, name, arg_family, arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) - return log_error_errno(r, "%s: resolve call failed: %s", name, bus_error_message(&error, r)); - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { - _cleanup_free_ char *pretty = NULL; - int ifindex, family; - const void *a; - size_t sz; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "ii", &ifindex, &family); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (!IN_SET(family, AF_INET, AF_INET6)) { - log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); - continue; - } - - if (sz != FAMILY_ADDRESS_SIZE(family)) { - log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); - return -EINVAL; - } - - ifname[0] = 0; - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - r = in_addr_to_string(family, a, &pretty); - if (r < 0) - return log_error_errno(r, "Failed to print address for %s: %m", name); - - printf("%*s%s %s%s%s\n", - (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", - pretty, - isempty(ifname) ? "" : "%", ifname); - - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "st", &canonical, &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (!streq(name, canonical)) - printf("%*s%s (%s)\n", - (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", - canonical); - - if (c == 0) { - log_error("%s: no addresses found", name); - return -ESRCH; - } - - print_source(flags, ts); - - return 0; -} - -static int resolve_address(sd_bus *bus, int family, const union in_addr_union *address, int ifindex) { - _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 *pretty = NULL; - char ifname[IF_NAMESIZE] = ""; - uint64_t flags; - unsigned c = 0; - usec_t ts; - int r; - - assert(bus); - assert(IN_SET(family, AF_INET, AF_INET6)); - assert(address); - - if (ifindex <= 0) - ifindex = arg_ifindex; - - r = in_addr_to_string(family, address, &pretty); - if (r < 0) - return log_oom(); - - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - log_debug("Resolving %s%s%s.", pretty, isempty(ifname) ? "" : "%", ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveAddress"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "ii", ifindex, family); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_array(req, 'y', address, FAMILY_ADDRESS_SIZE(family)); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "t", arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - log_error("%s: resolve call failed: %s", pretty, bus_error_message(&error, r)); - return r; - } - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(is)"); - if (r < 0) - return bus_log_create_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "is")) > 0) { - const char *n; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "is", &ifindex, &n); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return r; - - ifname[0] = 0; - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - printf("%*s%*s%*s%s %s\n", - (int) strlen(pretty), c == 0 ? pretty : "", - isempty(ifname) ? 0 : 1, c > 0 || isempty(ifname) ? "" : "%", - (int) strlen(ifname), c == 0 ? ifname : "", - c == 0 ? ":" : " ", - n); - - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "t", &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (c == 0) { - log_error("%s: no names found", pretty); - return -ESRCH; - } - - print_source(flags, ts); - - return 0; -} - -static int parse_address(const char *s, int *family, union in_addr_union *address, int *ifindex) { - const char *percent, *a; - int ifi = 0; - int r; - - percent = strchr(s, '%'); - if (percent) { - if (parse_ifindex(percent+1, &ifi) < 0) { - ifi = if_nametoindex(percent+1); - if (ifi <= 0) - return -EINVAL; - } - - a = strndupa(s, percent - s); - } else - a = s; - - r = in_addr_from_string_auto(a, family, address); - if (r < 0) - return r; - - *ifindex = ifi; - return 0; -} - -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] = ""; - unsigned n = 0; - uint64_t flags; - int r; - usec_t ts; - - assert(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(class), dns_type_to_string(type), isempty(ifname) ? "*" : ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveRecord"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, class, type, arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r)); - return r; - } - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(iqqay)"); - if (r < 0) - return bus_log_parse_error(r); - - 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; - const char *s; - uint16_t c, t; - int ifindex; - const void *d; - size_t l; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "iqq", &ifindex, &c, &t); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &d, &l); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0); - if (r < 0) - return log_oom(); - - p->refuse_compression = true; - - r = dns_packet_append_blob(p, d, l, NULL); - if (r < 0) - return log_oom(); - - r = dns_packet_read_rr(p, &rr, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to parse RR: %m"); - - s = dns_resource_record_to_string(rr); - if (!s) - return log_oom(); - - ifname[0] = 0; - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - printf("%s%s%s\n", s, isempty(ifname) ? "" : " # interface ", ifname); - n++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "t", &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (n == 0) { - log_error("%s: no records found", name); - return -ESRCH; - } - - print_source(flags, ts); - - 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; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char ifname[IF_NAMESIZE] = ""; - size_t indent, sz; - uint64_t flags; - const char *p; - unsigned c; - usec_t ts; - int r; - - assert(bus); - assert(domain); - - if (isempty(name)) - name = NULL; - if (isempty(type)) - type = NULL; - - 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); - - if (name) - log_debug("Resolving service \"%s\" of type %s in %s (family %s, interface %s).", name, type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - else if (type) - log_debug("Resolving service type %s of %s (family %s, interface %s).", type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - else - log_debug("Resolving service type %s (family %s, interface %s).", domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveService"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isssit", arg_ifindex, name, type, domain, arg_family, arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) - return log_error_errno(r, "Resolve call failed: %s", bus_error_message(&error, r)); - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(qqqsa(iiay)s)"); - if (r < 0) - return bus_log_parse_error(r); - - indent = - (name ? strlen(name) + 1 : 0) + - (type ? strlen(type) + 1 : 0) + - strlen(domain) + 2; - - c = 0; - while ((r = sd_bus_message_enter_container(reply, 'r', "qqqsa(iiay)s")) > 0) { - uint16_t priority, weight, port; - const char *hostname, *canonical; - - r = sd_bus_message_read(reply, "qqqs", &priority, &weight, &port, &hostname); - if (r < 0) - return bus_log_parse_error(r); - - if (name) - printf("%*s%s", (int) strlen(name), c == 0 ? name : "", c == 0 ? "/" : " "); - if (type) - printf("%*s%s", (int) strlen(type), c == 0 ? type : "", c == 0 ? "/" : " "); - - printf("%*s%s %s:%u [priority=%u, weight=%u]\n", - (int) strlen(domain), c == 0 ? domain : "", - c == 0 ? ":" : " ", - hostname, port, - priority, weight); - - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { - _cleanup_free_ char *pretty = NULL; - int ifindex, family; - const void *a; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "ii", &ifindex, &family); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (!IN_SET(family, AF_INET, AF_INET6)) { - log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); - continue; - } - - if (sz != FAMILY_ADDRESS_SIZE(family)) { - log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); - return -EINVAL; - } - - ifname[0] = 0; - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - r = in_addr_to_string(family, a, &pretty); - if (r < 0) - return log_error_errno(r, "Failed to print address for %s: %m", name); - - printf("%*s%s%s%s\n", (int) indent, "", pretty, isempty(ifname) ? "" : "%s", ifname); - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "s", &canonical); - if (r < 0) - return bus_log_parse_error(r); - - if (!streq(hostname, canonical)) - printf("%*s(%s)\n", (int) indent, "", canonical); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_enter_container(reply, 'a', "ay"); - if (r < 0) - return bus_log_parse_error(r); - - c = 0; - while ((r = sd_bus_message_read_array(reply, 'y', (const void**) &p, &sz)) > 0) { - _cleanup_free_ char *escaped = NULL; - - escaped = cescape_length(p, sz); - if (!escaped) - return log_oom(); - - printf("%*s%s\n", (int) indent, "", escaped); - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "ssst", &canonical_name, &canonical_type, &canonical_domain, &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (isempty(canonical_name)) - canonical_name = NULL; - if (isempty(canonical_type)) - canonical_type = NULL; - - if (!streq_ptr(name, canonical_name) || - !streq_ptr(type, canonical_type) || - !streq_ptr(domain, canonical_domain)) { - - printf("%*s(", (int) indent, ""); - - if (canonical_name) - printf("%s/", canonical_name); - if (canonical_type) - printf("%s/", canonical_type); - - printf("%s)\n", canonical_domain); - } - - print_source(flags, ts); - - 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; - - if (arg_legend) - puts("Known DNS RR types:"); - for (i = 0; i < _DNS_TYPE_MAX; i++) { - t = dns_type_to_string(i); - if (t) - puts(t); - } -} - -static void help_dns_classes(void) { - int i; - const char *t; - - if (arg_legend) - puts("Known DNS RR classes:"); - for (i = 0; i < _DNS_CLASS_MAX; i++) { - t = dns_class_to_string(i); - if (t) - puts(t); - } -} - -static void help(void) { - printf("%s [OPTIONS...] NAME...\n" - "%s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n\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); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_LEGEND, - ARG_SERVICE, - ARG_CNAME, - 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 }, - { "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 }, - {} - }; - - int c, r; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h46i:t:c:p:", options, NULL)) >= 0) - switch(c) { - - case 'h': - help(); - return 0; /* done */; - - case ARG_VERSION: - return version(); - - case '4': - arg_family = AF_INET; - break; - - case '6': - arg_family = AF_INET6; - break; - - case 'i': { - int ifi; - - if (parse_ifindex(optarg, &ifi) >= 0) - arg_ifindex = ifi; - else { - ifi = if_nametoindex(optarg); - if (ifi <= 0) - return log_error_errno(errno, "Unknown interface %s: %m", optarg); - - arg_ifindex = ifi; - } - - break; - } - - case 't': - if (streq(optarg, "help")) { - help_dns_types(); - return 0; - } - - r = dns_type_from_string(optarg); - if (r < 0) { - log_error("Failed to parse RR record type %s", optarg); - return r; - } - arg_type = (uint16_t) r; - assert((int) arg_type == r); - - arg_mode = MODE_RESOLVE_RECORD; - break; - - case 'c': - if (streq(optarg, "help")) { - help_dns_classes(); - return 0; - } - - 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; - - case ARG_LEGEND: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --legend= argument"); - - arg_legend = r; - break; - - case 'p': - 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; - else if (streq(optarg, "llmnr-ipv4")) - arg_flags |= SD_RESOLVED_LLMNR_IPV4; - else if (streq(optarg, "llmnr-ipv6")) - arg_flags |= SD_RESOLVED_LLMNR_IPV6; - else { - log_error("Unknown protocol specifier: %s", optarg); - return -EINVAL; - } - - break; - - case ARG_SERVICE: - arg_mode = MODE_RESOLVE_SERVICE; - break; - - case ARG_CNAME: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --cname= argument."); - if (r == 0) - arg_flags |= SD_RESOLVED_NO_CNAME; - else - arg_flags &= ~SD_RESOLVED_NO_CNAME; - break; - - case ARG_SERVICE_ADDRESS: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --service-address= argument."); - if (r == 0) - arg_flags |= SD_RESOLVED_NO_ADDRESS; - else - arg_flags &= ~SD_RESOLVED_NO_ADDRESS; - break; - - case ARG_SERVICE_TXT: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --service-txt= argument."); - if (r == 0) - arg_flags |= SD_RESOLVED_NO_TXT; - else - arg_flags &= ~SD_RESOLVED_NO_TXT; - break; - - case ARG_SEARCH: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --search argument."); - if (r == 0) - arg_flags |= SD_RESOLVED_NO_SEARCH; - else - 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; - - default: - assert_not_reached("Unhandled option"); - } - - if (arg_type == 0 && arg_class != 0) { - log_error("--class= may only be used in conjunction with --type=."); - return -EINVAL; - } - - if (arg_type != 0 && arg_mode != MODE_RESOLVE_RECORD) { - log_error("--service and --type= may not be combined."); - return -EINVAL; - } - - 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 */; -} - -int main(int argc, char **argv) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = sd_bus_open_system(&bus); - if (r < 0) { - log_error_errno(r, "sd_bus_open_system: %m"); - goto finish; - } - - 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; - goto finish; - - } else if (argc == optind + 1) - r = resolve_service(bus, NULL, NULL, argv[optind]); - else if (argc == optind + 2) - r = resolve_service(bus, NULL, argv[optind], argv[optind+1]); - else if (argc == optind + 3) - r = resolve_service(bus, argv[optind], argv[optind+1], argv[optind+2]); - else { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - break; - - case MODE_STATISTICS: - if (argc > optind) { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - r = show_statistics(bus); - break; - - case MODE_RESET_STATISTICS: - if (argc > optind) { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - r = reset_statistics(bus); - break; - } - -finish: - return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c deleted file mode 100644 index fc5e6beca0..0000000000 --- a/src/resolve/resolved-bus.c +++ /dev/null @@ -1,1565 +0,0 @@ -/*** - 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 . -***/ - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-util.h" -#include "dns-domain.h" -#include "resolved-bus.h" -#include "resolved-def.h" -#include "resolved-link-bus.h" - -static int reply_query_state(DnsQuery *q) { - - switch (q->state) { - - case DNS_TRANSACTION_NO_SERVERS: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_NAME_SERVERS, "No appropriate name servers or networks for name found"); - - case DNS_TRANSACTION_TIMEOUT: - return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "Query timed out"); - - case DNS_TRANSACTION_ATTEMPTS_MAX_REACHED: - return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "All attempts to contact name servers or networks failed"); - - case DNS_TRANSACTION_INVALID_REPLY: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); - - 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_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_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", dns_query_string(q)); - else { - const char *rc, *n; - char p[DECIMAL_STR_MAX(q->answer_rcode)]; - - rc = dns_rcode_to_string(q->answer_rcode); - if (!rc) { - sprintf(p, "%i", q->answer_rcode); - rc = p; - } - - n = strjoina(_BUS_ERROR_DNS, 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); - } - - case DNS_TRANSACTION_NULL: - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - case DNS_TRANSACTION_SUCCESS: - default: - assert_not_reached("Impossible state"); - } -} - -static int append_address(sd_bus_message *reply, DnsResourceRecord *rr, int ifindex) { - int r; - - assert(reply); - assert(rr); - - r = sd_bus_message_open_container(reply, 'r', "iiay"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "i", ifindex); - if (r < 0) - return r; - - if (rr->key->type == DNS_TYPE_A) { - r = sd_bus_message_append(reply, "i", AF_INET); - if (r < 0) - return r; - - r = sd_bus_message_append_array(reply, 'y', &rr->a.in_addr, sizeof(struct in_addr)); - - } else if (rr->key->type == DNS_TYPE_AAAA) { - r = sd_bus_message_append(reply, "i", AF_INET6); - if (r < 0) - return r; - - r = sd_bus_message_append_array(reply, 'y', &rr->aaaa.in6_addr, sizeof(struct in6_addr)); - } else - return -EAFNOSUPPORT; - - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return 0; -} - -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 ifindex, r; - - assert(q); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - 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_query_string(q)); - goto finish; - } - if (r < 0) - goto finish; - 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); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(iiay)"); - if (r < 0) - goto finish; - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - DnsQuestion *question; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); - if (r < 0) - goto finish; - if (r == 0) - continue; - - r = append_address(reply, rr, ifindex); - if (r < 0) - goto finish; - - 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_query_string(q)); - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - /* Return the precise spelling and uppercasing and CNAME target reported by the server */ - assert(canonical); - r = sd_bus_message_append( - reply, "st", - DNS_RESOURCE_KEY_NAME(canonical->key), - SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); - if (r < 0) - goto finish; - - r = sd_bus_send(q->manager->bus, reply, NULL); - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send hostname reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static int check_ifindex_flags(int ifindex, uint64_t *flags, uint64_t ok, sd_bus_error *error) { - assert(flags); - - if (ifindex < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); - - if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); - - if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */ - *flags |= SD_RESOLVED_PROTOCOLS_ALL; - - return 0; -} - -static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; - Manager *m = userdata; - const char *hostname; - int family, ifindex; - uint64_t flags; - DnsQuery *q; - int r; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(message, "isit", &ifindex, &hostname, &family, &flags); - if (r < 0) - return r; - - if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); - - r = dns_name_is_valid(hostname); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", hostname); - - r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_SEARCH, error); - if (r < 0) - return r; - - 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_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) - goto fail; - - r = dns_query_go(q); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(q); - return r; -} - -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; - - assert(q); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - 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_query_string(q)); - goto finish; - } - if (r < 0) - goto finish; - 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); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(is)"); - if (r < 0) - goto finish; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - 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 = sd_bus_message_append(reply, "(is)", ifindex, rr->ptr.name); - if (r < 0) - goto finish; - - added ++; - } - - if (added <= 0) { - _cleanup_free_ char *ip = NULL; - - in_addr_to_string(q->request_family, &q->request_address, &ip); - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Address '%s' does not have any RR of requested type", strna(ip)); - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); - if (r < 0) - goto finish; - - r = sd_bus_send(q->manager->bus, reply, NULL); - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send address reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static int bus_method_resolve_address(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - Manager *m = userdata; - int family, ifindex; - uint64_t flags; - const void *d; - DnsQuery *q; - size_t sz; - int r; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(message, "ii", &ifindex, &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_read(message, "t", &flags); - if (r < 0) - return r; - - r = check_ifindex_flags(ifindex, &flags, 0, error); - if (r < 0) - return r; - - r = dns_question_new_reverse(&question, family, d); - if (r < 0) - return r; - - r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); - if (r < 0) - return r; - - q->request = sd_bus_message_ref(message); - q->request_family = family; - memcpy(&q->request_address, d, sz); - q->complete = bus_method_resolve_address_complete; - - r = dns_query_bus_track(q, message); - if (r < 0) - goto fail; - - r = dns_query_go(q); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(q); - return r; -} - -static int bus_message_append_rr(sd_bus_message *m, DnsResourceRecord *rr, int ifindex) { - int r; - - assert(m); - assert(rr); - - r = sd_bus_message_open_container(m, 'r', "iqqay"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "iqq", - ifindex, - rr->key->class, - rr->key->type); - if (r < 0) - return r; - - r = dns_resource_record_to_wire_format(rr, false); - if (r < 0) - return r; - - r = sd_bus_message_append_array(m, 'y', rr->wire_format, rr->wire_format_size); - if (r < 0) - return r; - - return sd_bus_message_close_container(m); -} - -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); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - 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_query_string(q)); - goto finish; - } - if (r < 0) - goto finish; - 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); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(iqqay)"); - if (r < 0) - goto finish; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - 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; - - 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_query_string(q)); - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); - if (r < 0) - goto finish; - - r = sd_bus_send(q->manager->bus, reply, NULL); - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send record reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - Manager *m = userdata; - uint16_t class, type; - const char *name; - int r, ifindex; - uint64_t flags; - DnsQuery *q; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(message, "isqqt", &ifindex, &name, &class, &type, &flags); - if (r < 0) - return r; - - 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 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; - - question = dns_question_new(1); - if (!question) - return -ENOMEM; - - key = dns_resource_key_new(class, type, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(question, key); - if (r < 0) - return r; - - r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); - if (r < 0) - return r; - - q->request = sd_bus_message_ref(message); - q->complete = bus_method_resolve_record_complete; - - r = dns_query_bus_track(q, message); - if (r < 0) - goto fail; - - r = dns_query_go(q); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(q); - return r; -} - -static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; - DnsQuery *aux; - int r; - - assert(q); - assert(reply); - assert(rr); - assert(rr->key); - - if (rr->key->type != DNS_TYPE_SRV) - return 0; - - if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { - /* First, let's see if we could find an appropriate A or AAAA - * 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; - - 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) - continue; - - DNS_ANSWER_FOREACH(zz, aux->answer) { - - r = dns_question_matches_rr(question, zz, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - canonical = dns_resource_record_ref(zz); - break; - } - - if (canonical) - break; - } - - /* Is there are successful A/AAAA lookup for this SRV RR? If not, don't add it */ - if (!canonical) - return 0; - } - - r = sd_bus_message_open_container(reply, 'r', "qqqsa(iiay)s"); - if (r < 0) - return r; - - r = sd_bus_message_append( - reply, - "qqqs", - rr->srv.priority, rr->srv.weight, rr->srv.port, rr->srv.name); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(iiay)"); - if (r < 0) - return r; - - 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) - continue; - if (aux->auxiliary_result != 0) - continue; - - 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) - continue; - - DNS_ANSWER_FOREACH_IFINDEX(zz, ifindex, aux->answer) { - - r = dns_question_matches_rr(question, zz, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - r = append_address(reply, zz, ifindex); - if (r < 0) - return r; - } - } - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - /* Note that above we appended the hostname as encoded in the - * SRV, and here the canonical hostname this maps to. */ - r = sd_bus_message_append(reply, "s", canonical ? DNS_RESOURCE_KEY_NAME(canonical->key) : rr->srv.name); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return 1; -} - -static int append_txt(sd_bus_message *reply, DnsResourceRecord *rr) { - DnsTxtItem *i; - int r; - - assert(reply); - assert(rr); - assert(rr->key); - - if (rr->key->type != DNS_TYPE_TXT) - return 0; - - LIST_FOREACH(items, i, rr->txt.items) { - - if (i->length <= 0) - continue; - - r = sd_bus_message_append_array(reply, 'y', i->data, i->length); - if (r < 0) - return r; - } - - return 1; -} - -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; - int r; - - assert(q); - - if (q->block_all_complete > 0) - return; - - if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { - DnsQuery *bad = NULL; - bool have_success = false; - - LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { - - switch (aux->state) { - - case DNS_TRANSACTION_PENDING: - /* If an auxiliary query is still pending, let's wait */ - return; - - case DNS_TRANSACTION_SUCCESS: - if (aux->auxiliary_result == 0) - have_success = true; - else - bad = aux; - break; - - default: - bad = aux; - break; - } - } - - if (!have_success) { - /* We can only return one error, hence pick the last error we encountered */ - - assert(bad); - - if (bad->state == DNS_TRANSACTION_SUCCESS) { - 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_query_string(bad)); - goto finish; - } - - r = bad->auxiliary_result; - goto finish; - } - - r = reply_query_state(bad); - goto finish; - } - } - - r = sd_bus_message_new_method_return(q->request, &reply); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(qqqsa(iiay)s)"); - if (r < 0) - goto finish; - - 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; - - 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_query_string(q)); - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "ay"); - if (r < 0) - goto finish; - - 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 = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - assert(canonical); - r = dns_service_split(DNS_RESOURCE_KEY_NAME(canonical->key), &name, &type, &domain); - if (r < 0) - goto finish; - - r = sd_bus_message_append( - reply, - "ssst", - name, type, domain, - SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); - if (r < 0) - goto finish; - - r = sd_bus_send(q->manager->bus, reply, NULL); - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send service reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static void resolve_service_hostname_complete(DnsQuery *q) { - int r; - - assert(q); - assert(q->auxiliary_for); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - resolve_service_all_complete(q->auxiliary_for); - return; - } - - r = dns_query_process_cname(q); - 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. */ - q->auxiliary_result = r; - resolve_service_all_complete(q->auxiliary_for); -} - -static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifindex) { - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - DnsQuery *aux; - int r; - - assert(q); - assert(rr); - assert(rr->key); - assert(rr->key->type == DNS_TYPE_SRV); - - /* 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, false); - if (r < 0) - return r; - - r = dns_query_new(q->manager, &aux, question, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH); - if (r < 0) - return r; - - aux->request_family = q->request_family; - aux->complete = resolve_service_hostname_complete; - - r = dns_query_make_auxiliary(aux, q); - if (r == -EAGAIN) { - /* Too many auxiliary lookups? If so, don't complain, - * let's just not add this one, we already have more - * than enough */ - - dns_query_free(aux); - return 0; - } - if (r < 0) - goto fail; - - /* Note that auxiliary queries do not track the original bus - * client, only the primary request does that. */ - - r = dns_query_go(aux); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(aux); - return r; -} - -static void bus_method_resolve_service_complete(DnsQuery *q) { - bool has_root_domain = false; - DnsResourceRecord *rr; - DnsQuestion *question; - unsigned found = 0; - int ifindex, r; - - assert(q); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - 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_query_string(q)); - goto finish; - } - if (r < 0) - goto finish; - if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ - return; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - 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 (dns_name_is_root(rr->srv.name)) { - has_root_domain = true; - continue; - } - - if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { - q->block_all_complete ++; - r = resolve_service_hostname(q, rr, ifindex); - q->block_all_complete --; - - if (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_query_string(q)); - goto finish; - } - - /* Maybe we are already finished? check now... */ - resolve_service_all_complete(q); - return; - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send service reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static int bus_method_resolve_service(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _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; - uint64_t flags; - DnsQuery *q; - int r; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(message, "isssit", &ifindex, &name, &type, &domain, &family, &flags); - if (r < 0) - return r; - - if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); - - 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); - - if (isempty(type)) - type = NULL; - else if (!dns_srv_type_is_valid(type)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid SRV service type '%s'", type); - - r = dns_name_is_valid(domain); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid domain '%s'", domain); - - if (name && !type) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Service name cannot be specified without service type."); - - r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS, error); - if (r < 0) - return r; - - 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_idna, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), true); - if (r < 0) - return r; - - r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags|SD_RESOLVED_NO_SEARCH); - if (r < 0) - return r; - - q->request = sd_bus_message_ref(message); - q->request_family = family; - q->complete = bus_method_resolve_service_complete; - - r = dns_query_bus_track(q, message); - if (r < 0) - goto fail; - - r = dns_query_go(q); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(q); - return r; -} - -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', with_ifindex ? "iiay" : "iay"); - if (r < 0) - return r; - - 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; - - r = sd_bus_message_append_array(reply, 'y', &s->address, FAMILY_ADDRESS_SIZE(s->family)); - if (r < 0) - return r; - - return sd_bus_message_close_container(reply); -} - -static int bus_property_get_dns_servers( - 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; - unsigned c = 0; - DnsServer *s; - Iterator i; - Link *l; - int r; - - assert(reply); - assert(m); - - r = sd_bus_message_open_container(reply, 'a', "(iiay)"); - if (r < 0) - return r; - - LIST_FOREACH(servers, s, m->dns_servers) { - r = bus_dns_server_append(reply, s, true); - if (r < 0) - return r; - - c++; - } - - HASHMAP_FOREACH(l, m->links, i) { - LIST_FOREACH(servers, s, l->dns_servers) { - r = bus_dns_server_append(reply, s, true); - if (r < 0) - return r; - c++; - } - } - - if (c == 0) { - LIST_FOREACH(servers, s, m->fallback_dns_servers) { - r = bus_dns_server_append(reply, s, true); - if (r < 0) - return r; - } - } - - return sd_bus_message_close_container(reply); -} - -static int bus_property_get_search_domains( - 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; - DnsSearchDomain *d; - Iterator i; - Link *l; - int r; - - assert(reply); - assert(m); - - 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, "(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, "(isb)", l->ifindex, d->name, d->route_only); - if (r < 0) - return r; - } - } - - 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("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, -}; - -static int on_bus_retry(sd_event_source *s, usec_t usec, void *userdata) { - Manager *m = userdata; - - assert(s); - assert(m); - - m->bus_retry_event_source = sd_event_source_unref(m->bus_retry_event_source); - - manager_connect_bus(m); - return 0; -} - -static int match_prepare_for_sleep(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) { - Manager *m = userdata; - int b, r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) { - log_debug_errno(r, "Failed to parse PrepareForSleep signal: %m"); - return 0; - } - - if (b) - return 0; - - log_debug("Coming back from suspend, verifying all RRs..."); - - manager_verify_all(m); - return 0; -} - -int manager_connect_bus(Manager *m) { - int r; - - assert(m); - - if (m->bus) - return 0; - - r = sd_bus_default_system(&m->bus); - if (r < 0) { - /* We failed to connect? Yuck, we must be in early - * boot. Let's try in 5s again. As soon as we have - * kdbus we can stop doing this... */ - - log_debug_errno(r, "Failed to connect to bus, trying again in 5s: %m"); - - r = sd_event_add_time(m->event, &m->bus_retry_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 5*USEC_PER_SEC, 0, on_bus_retry, 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; - } - - r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/resolve1", "org.freedesktop.resolve1.Manager", resolve_vtable, 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"); - - r = sd_bus_attach_event(m->bus, m->event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - r = sd_bus_add_match(m->bus, &m->prepare_for_sleep_slot, - "type='signal'," - "sender='org.freedesktop.login1'," - "interface='org.freedesktop.login1.Manager'," - "member='PrepareForSleep'," - "path='/org/freedesktop/login1'", - match_prepare_for_sleep, - m); - if (r < 0) - log_error_errno(r, "Failed to add match for PrepareForSleep: %m"); - - return 0; -} diff --git a/src/resolve/resolved-bus.h b/src/resolve/resolved-bus.h deleted file mode 100644 index f49e1337d2..0000000000 --- a/src/resolve/resolved-bus.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#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 deleted file mode 100644 index bb93fbfda2..0000000000 --- a/src/resolve/resolved-conf.c +++ /dev/null @@ -1,236 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . - ***/ - -#include "alloc-util.h" -#include "conf-parser.h" -#include "def.h" -#include "extract-word.h" -#include "parse-util.h" -#include "resolved-conf.h" -#include "string-util.h" - -int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word) { - union in_addr_union address; - int family, r; - DnsServer *s; - - assert(m); - assert(word); - - r = in_addr_from_string_auto(word, &family, &address); - if (r < 0) - return r; - - /* Filter out duplicates */ - s = dns_server_find(manager_get_first_dns_server(m, type), family, &address); - if (s) { - /* - * Drop the marker. This is used to find the servers - * that ceased to exist, see - * manager_mark_dns_servers() and - * manager_flush_marked_dns_servers(). - */ - dns_server_move_back_and_unmark(s); - return 0; - } - - return dns_server_new(m, NULL, type, NULL, family, &address); -} - -int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string) { - int r; - - assert(m); - assert(string); - - for(;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&string, &word, NULL, 0); - if (r < 0) - return r; - if (r == 0) - break; - - r = manager_add_dns_server_by_string(m, type, word); - if (r < 0) - log_warning_errno(r, "Failed to add DNS server address '%s', ignoring.", word); - } - - return 0; -} - -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) - dns_search_domain_move_back_and_unmark(d); - else { - r = dns_search_domain_new(m, &d, DNS_SEARCH_DOMAIN_SYSTEM, NULL, domain); - if (r < 0) - return r; - } - - d->route_only = route_only; - return 0; -} - -int manager_parse_search_domains_and_warn(Manager *m, const char *string) { - int r; - - assert(m); - assert(string); - - for(;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&string, &word, NULL, EXTRACT_QUOTES); - if (r < 0) - return r; - if (r == 0) - break; - - r = manager_add_search_domain_by_string(m, word); - if (r < 0) - log_warning_errno(r, "Failed to add search domain '%s', ignoring.", word); - } - - return 0; -} - -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) { - - Manager *m = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(m); - - if (isempty(rvalue)) - /* Empty assignment means clear the list */ - dns_server_unlink_all(manager_get_first_dns_server(m, ltype)); - else { - /* Otherwise, add to the list */ - r = manager_parse_dns_server_string_and_warn(m, ltype, rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse DNS server string '%s'. Ignoring.", rvalue); - return 0; - } - } - - /* If we have a manual setting, then we stop reading - * /etc/resolv.conf */ - if (ltype == DNS_SERVER_SYSTEM) - m->read_resolv_conf = false; - if (ltype == DNS_SERVER_FALLBACK) - m->need_builtin_fallbacks = false; - - return 0; -} - -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) { - - Manager *m = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(m); - - if (isempty(rvalue)) - /* Empty assignment means clear the list */ - dns_search_domain_unlink_all(m->search_domains); - else { - /* Otherwise, add to the list */ - r = manager_parse_search_domains_and_warn(m, rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse search domains string '%s'. Ignoring.", rvalue); - return 0; - } - } - - /* If we have a manual setting, then we stop reading - * /etc/resolv.conf */ - m->read_resolv_conf = false; - - return 0; -} - -int manager_parse_config_file(Manager *m) { - int r; - - assert(m); - - r = config_parse_many(PKGSYSCONFDIR "/resolved.conf", - CONF_PATHS_NULSTR("systemd/resolved.conf.d"), - "Resolve\0", - config_item_perf_lookup, resolved_gperf_lookup, - false, m); - if (r < 0) - return r; - - if (m->need_builtin_fallbacks) { - r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_FALLBACK, DNS_SERVERS); - if (r < 0) - return r; - } - - return 0; - -} diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h deleted file mode 100644 index e1fd2cceec..0000000000 --- a/src/resolve/resolved-conf.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include "resolved-manager.h" - -int manager_parse_config_file(Manager *m); - -int manager_add_search_domain_by_string(Manager *m, const char *domain); -int manager_parse_search_domains_and_warn(Manager *m, const char *string); - -int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word); -int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string); - -const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned length); - -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_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-def.h b/src/resolve/resolved-def.h deleted file mode 100644 index c4c1915b18..0000000000 --- a/src/resolve/resolved-def.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include - -#define SD_RESOLVED_DNS (UINT64_C(1) << 0) -#define SD_RESOLVED_LLMNR_IPV4 (UINT64_C(1) << 1) -#define SD_RESOLVED_LLMNR_IPV6 (UINT64_C(1) << 2) -#define SD_RESOLVED_MDNS_IPV4 (UINT64_C(1) << 3) -#define SD_RESOLVED_MDNS_IPV6 (UINT64_C(1) << 4) -#define SD_RESOLVED_NO_CNAME (UINT64_C(1) << 5) -#define SD_RESOLVED_NO_TXT (UINT64_C(1) << 6) -#define SD_RESOLVED_NO_ADDRESS (UINT64_C(1) << 7) -#define SD_RESOLVED_NO_SEARCH (UINT64_C(1) << 8) -#define SD_RESOLVED_AUTHENTICATED (UINT64_C(1) << 9) - -#define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6) -#define SD_RESOLVED_MDNS (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6) - -#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS) diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c deleted file mode 100644 index 7eb303ab95..0000000000 --- a/src/resolve/resolved-dns-answer.c +++ /dev/null @@ -1,858 +0,0 @@ -/*** - 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 . -***/ - -#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) { - DnsAnswer *a; - - a = malloc0(offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * n); - if (!a) - return NULL; - - a->n_ref = 1; - a->n_allocated = n; - - return a; -} - -DnsAnswer *dns_answer_ref(DnsAnswer *a) { - if (!a) - return NULL; - - assert(a->n_ref > 0); - a->n_ref++; - return a; -} - -static void dns_answer_flush(DnsAnswer *a) { - DnsResourceRecord *rr; - - if (!a) - return; - - DNS_ANSWER_FOREACH(rr, a) - dns_resource_record_unref(rr); - - a->n_rrs = 0; -} - -DnsAnswer *dns_answer_unref(DnsAnswer *a) { - if (!a) - return NULL; - - assert(a->n_ref > 0); - - if (a->n_ref == 1) { - dns_answer_flush(a); - free(a); - } else - a->n_ref--; - - return NULL; -} - -static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { - assert(rr); - - if (!a) - return -ENOSPC; - - if (a->n_rrs >= a->n_allocated) - return -ENOSPC; - - 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_FULL(rr, ifindex, flags, source) { - r = dns_answer_add_raw(a, rr, ifindex, flags); - if (r < 0) - return r; - } - - return 0; -} - -int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { - unsigned i; - int r; - - assert(rr); - - if (!a) - return -ENOSPC; - if (a->n_ref > 1) - return -EBUSY; - - for (i = 0; i < a->n_rrs; i++) { - if (a->items[i].ifindex != ifindex) - continue; - - r = dns_resource_record_equal(a->items[i].rr, rr); - if (r < 0) - return r; - if (r > 0) { - /* Don't mix contradicting TTLs (see below) */ - if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0)) - return -EINVAL; - - /* 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, flags); -} - -static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { - DnsResourceRecord *rr; - DnsAnswerFlags flags; - int ifindex, r; - - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, b) { - r = dns_answer_add(a, rr, ifindex, flags); - if (r < 0) - return r; - } - - return 0; -} - -int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { - int r; - - assert(a); - assert(rr); - - r = dns_answer_reserve_or_clone(a, 1); - if (r < 0) - return r; - - return dns_answer_add(*a, rr, ifindex, flags); -} - -int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *soa = NULL; - - soa = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_SOA, name); - if (!soa) - return -ENOMEM; - - soa->ttl = ttl; - - soa->soa.mname = strdup(name); - if (!soa->soa.mname) - return -ENOMEM; - - soa->soa.rname = strappend("root.", name); - if (!soa->soa.rname) - return -ENOMEM; - - soa->soa.serial = 1; - soa->soa.refresh = 1; - soa->soa.retry = 1; - soa->soa.expire = 1; - soa->soa.minimum = ttl; - - return dns_answer_add(a, soa, 0, DNS_ANSWER_AUTHENTICATED); -} - -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); - - 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) - continue; - - if (!ret_flags) - return 1; - - if (found) - flags &= i_flags; - else { - flags = i_flags; - found = true; - } - } - - if (ret_flags) - *ret_flags = flags; - - return found; -} - -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_FLAGS(i, i_flags, a) { - r = dns_resource_record_equal(i, rr); - if (r < 0) - return r; - if (r == 0) - continue; - - if (!ret_flags) - return 1; - - if (found) - flags &= i_flags; - else { - flags = i_flags; - found = true; - } - } - - if (ret_flags) - *ret_flags = flags; - - return found; -} - -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); - - 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_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; - } - } - - 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, DnsAnswerFlags *flags) { - DnsResourceRecord *rr; - DnsAnswerFlags rr_flags; - int r; - - assert(key); - - /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */ - if (!dns_type_may_redirect(key->type)) - return 0; - - 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; - } - } - - return 0; -} - -int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret) { - _cleanup_(dns_answer_unrefp) DnsAnswer *k = NULL; - int r; - - assert(ret); - - if (dns_answer_size(a) <= 0) { - *ret = dns_answer_ref(b); - return 0; - } - - if (dns_answer_size(b) <= 0) { - *ret = dns_answer_ref(a); - return 0; - } - - k = dns_answer_new(a->n_rrs + b->n_rrs); - if (!k) - return -ENOMEM; - - r = dns_answer_add_raw_all(k, a); - if (r < 0) - return r; - - r = dns_answer_add_all(k, b); - if (r < 0) - return r; - - *ret = k; - k = NULL; - - return 0; -} - -int dns_answer_extend(DnsAnswer **a, DnsAnswer *b) { - DnsAnswer *merged; - int r; - - assert(a); - - r = dns_answer_merge(*a, b, &merged); - if (r < 0) - return r; - - dns_answer_unref(*a); - *a = merged; - - return 0; -} - -int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) { - bool found = false, other = false; - DnsResourceRecord *rr; - unsigned i; - int r; - - assert(a); - assert(key); - - /* Remove all entries matching the specified key from *a */ - - DNS_ANSWER_FOREACH(rr, *a) { - r = dns_resource_key_equal(rr->key, key); - 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_key_equal(rr->key, key); - 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_key_equal((*a)->items[i].rr->key, key); - 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_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_FULL(rr_source, ifindex_source, flags_source, source) { - - r = dns_resource_key_equal(rr_source->key, key); - if (r < 0) - return r; - if (r == 0) - continue; - - /* Make space for at least one entry */ - r = dns_answer_reserve_or_clone(a, 1); - if (r < 0) - return r; - - r = dns_answer_add(*a, rr_source, ifindex_source, flags_source|or_flags); - if (r < 0) - return r; - } - - 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; - - if (!a) - return; - - if (a->n_rrs <= 1) - return; - - start = 0; - end = a->n_rrs-1; - - /* RFC 4795, Section 2.6 suggests we should order entries - * depending on whether the sender is a link-local address. */ - - items = newa(DnsAnswerItem, a->n_rrs); - for (i = 0; i < a->n_rrs; i++) { - - if (a->items[i].rr->key->class == DNS_CLASS_IN && - ((a->items[i].rr->key->type == DNS_TYPE_A && in_addr_is_link_local(AF_INET, (union in_addr_union*) &a->items[i].rr->a.in_addr) != prefer_link_local) || - (a->items[i].rr->key->type == DNS_TYPE_AAAA && in_addr_is_link_local(AF_INET6, (union in_addr_union*) &a->items[i].rr->aaaa.in6_addr) != prefer_link_local))) - /* Order address records that are are not preferred to the end of the array */ - items[end--] = a->items[i]; - else - /* Order all other records to the beginning of the array */ - items[start++] = a->items[i]; - } - - assert(start == end+1); - memcpy(a->items, items, sizeof(DnsAnswerItem) * a->n_rrs); -} - -int dns_answer_reserve(DnsAnswer **a, unsigned n_free) { - DnsAnswer *n; - - assert(a); - - if (n_free <= 0) - return 0; - - if (*a) { - unsigned ns; - - if ((*a)->n_ref > 1) - return -EBUSY; - - ns = (*a)->n_rrs + n_free; - - if ((*a)->n_allocated >= ns) - return 0; - - /* Allocate more than we need */ - ns *= 2; - - n = realloc(*a, offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * ns); - if (!n) - return -ENOMEM; - - n->n_allocated = ns; - } else { - n = dns_answer_new(n_free); - if (!n) - return -ENOMEM; - } - - *a = n; - return 0; -} - -int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free) { - _cleanup_(dns_answer_unrefp) DnsAnswer *n = NULL; - int r; - - assert(a); - - /* Tries to extend the DnsAnswer object. And if that's not - * possibly, since we are not the sole owner, then allocate a - * new, appropriately sized one. Either way, after this call - * the object will only have a single reference, and has room - * for at least the specified number of RRs. */ - - r = dns_answer_reserve(a, n_free); - if (r != -EBUSY) - return r; - - assert(*a); - - n = dns_answer_new(((*a)->n_rrs + n_free) * 2); - if (!n) - return -ENOMEM; - - r = dns_answer_add_raw_all(n, *a); - if (r < 0) - return r; - - dns_answer_unref(*a); - *a = n; - n = NULL; - - 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 deleted file mode 100644 index 8f9c15eab4..0000000000 --- a/src/resolve/resolved-dns-answer.h +++ /dev/null @@ -1,143 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -typedef struct DnsAnswer DnsAnswer; -typedef struct DnsAnswerItem DnsAnswerItem; - -#include "macro.h" -#include "resolved-dns-rr.h" - -/* A simple array of resource records. We keep track of the - * originating ifindex for each RR where that makes sense, so that we - * can qualify A and AAAA RRs referring to a local link with the - * right ifindex. - * - * 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 { - unsigned n_ref; - unsigned n_rrs, n_allocated; - DnsAnswerItem items[0]; -}; - -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, 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, 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, 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); - -void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local); - -int dns_answer_reserve(DnsAnswer **a, unsigned n_free); -int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free); - -int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key); -int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rr); - -int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags); -int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags); - -bool dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname); - -static inline unsigned dns_answer_size(DnsAnswer *a) { - return a ? a->n_rrs : 0; -} - -void dns_answer_dump(DnsAnswer *answer, FILE *f); - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); - -#define _DNS_ANSWER_FOREACH(q, kk, a) \ - for (unsigned UNIQ_T(i, q) = ({ \ - (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ - 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)) - -#define DNS_ANSWER_FOREACH(kk, a) _DNS_ANSWER_FOREACH(UNIQ, kk, a) - -#define _DNS_ANSWER_FOREACH_IFINDEX(q, kk, ifi, 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; \ - 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)) - -#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 deleted file mode 100644 index 9bcc71724e..0000000000 --- a/src/resolve/resolved-dns-cache.c +++ /dev/null @@ -1,1088 +0,0 @@ -/*** - 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 . -***/ - -#include "alloc-util.h" -#include "dns-domain.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-cache.h" -#include "resolved-dns-packet.h" -#include "string-util.h" - -/* 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 2h in our cache */ -#define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR) - -typedef enum DnsCacheItemType DnsCacheItemType; -typedef struct DnsCacheItem DnsCacheItem; - -enum DnsCacheItemType { - DNS_CACHE_POSITIVE, - DNS_CACHE_NODATA, - DNS_CACHE_NXDOMAIN, -}; - -struct DnsCacheItem { - DnsCacheItemType type; - DnsResourceKey *key; - DnsResourceRecord *rr; - - usec_t until; - 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); -}; - -static void dns_cache_item_free(DnsCacheItem *i) { - if (!i) - return; - - dns_resource_record_unref(i->rr); - dns_resource_key_unref(i->key); - free(i); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free); - -static void dns_cache_item_unlink_and_free(DnsCache *c, DnsCacheItem *i) { - DnsCacheItem *first; - - assert(c); - - if (!i) - return; - - first = hashmap_get(c->by_key, i->key); - LIST_REMOVE(by_key, first, i); - - if (first) - assert_se(hashmap_replace(c->by_key, first->key, first) >= 0); - else - hashmap_remove(c->by_key, i->key); - - prioq_remove(c->by_expiry, i, &i->prioq_idx); - - dns_cache_item_free(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); - - first = hashmap_remove(c->by_key, key); - if (!first) - return false; - - LIST_FOREACH_SAFE(by_key, i, n, first) { - prioq_remove(c->by_expiry, i, &i->prioq_idx); - dns_cache_item_free(i); - } - - return true; -} - -void dns_cache_flush(DnsCache *c) { - DnsResourceKey *key; - - assert(c); - - while ((key = hashmap_first_key(c->by_key))) - dns_cache_remove_by_key(c, key); - - 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) { - assert(c); - - if (add <= 0) - return; - - /* Makes space for n new entries. Note that we actually allow - * the cache to grow beyond CACHE_MAX, but only when we shall - * add more RRs to the cache than CACHE_MAX at once. In that - * case the cache will be emptied completely otherwise. */ - - for (;;) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - DnsCacheItem *i; - - if (prioq_size(c->by_expiry) <= 0) - break; - - if (prioq_size(c->by_expiry) + add < CACHE_MAX) - break; - - i = prioq_peek(c->by_expiry); - assert(i); - - /* 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); - } -} - -void dns_cache_prune(DnsCache *c) { - usec_t t = 0; - - assert(c); - - /* Remove all entries that are past their TTL */ - - for (;;) { - DnsCacheItem *i; - - i = prioq_peek(c->by_expiry); - if (!i) - break; - - if (t <= 0) - t = now(clock_boottime_or_monotonic()); - - if (i->until > t) - break; - - /* 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); - } - } -} - -static int dns_cache_item_prioq_compare_func(const void *a, const void *b) { - const DnsCacheItem *x = a, *y = b; - - if (x->until < y->until) - return -1; - if (x->until > y->until) - return 1; - return 0; -} - -static int dns_cache_init(DnsCache *c) { - int r; - - assert(c); - - r = prioq_ensure_allocated(&c->by_expiry, dns_cache_item_prioq_compare_func); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&c->by_key, &dns_resource_key_hash_ops); - if (r < 0) - return r; - - return r; -} - -static int dns_cache_link_item(DnsCache *c, DnsCacheItem *i) { - DnsCacheItem *first; - int r; - - assert(c); - assert(i); - - r = prioq_put(c->by_expiry, i, &i->prioq_idx); - if (r < 0) - return r; - - 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 { - r = hashmap_put(c->by_key, i->key, i); - if (r < 0) { - prioq_remove(c->by_expiry, i, &i->prioq_idx); - return r; - } - } - - return 0; -} - -static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) { - DnsCacheItem *i; - - assert(c); - assert(rr); - - LIST_FOREACH(by_key, i, hashmap_get(c->by_key, rr->key)) - if (i->rr && dns_resource_record_equal(i->rr, rr) > 0) - return i; - - return NULL; -} - -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; - - if (!i->by_key_prev) - /* We are the first item in the list, we need to - * update the key used in the hashmap */ - - assert_se(hashmap_replace(c->by_key, rr->key, i) >= 0); - - dns_resource_record_ref(rr); - dns_resource_record_unref(i->rr); - i->rr = rr; - - 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->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); -} - -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) { - - _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; - _cleanup_free_ char *key_str = NULL; - DnsCacheItem *existing; - int r, k; - - assert(c); - assert(rr); - assert(owner_address); - - /* 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_by_rr(c, rr); - - if (log_get_max_level() >= LOG_DEBUG) { - r = dns_resource_key_to_string(rr->key, &key_str); - if (r < 0) - return r; - - if (k > 0) - log_debug("Removed zero TTL entry from cache: %s", key_str); - else - log_debug("Not caching zero TTL cache entry: %s", key_str); - } - - return 0; - } - - /* 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, - shared_owner, - timestamp, - ifindex, - owner_family, - owner_address); - return 0; - } - - /* Otherwise, add the new RR */ - r = dns_cache_init(c); - if (r < 0) - return r; - - dns_cache_make_space(c, 1); - - i = new0(DnsCacheItem, 1); - if (!i) - return -ENOMEM; - - i->type = DNS_CACHE_POSITIVE; - i->key = dns_resource_key_ref(rr->key); - i->rr = dns_resource_record_ref(rr); - i->until = calculate_until(rr, (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->prioq_idx = PRIOQ_IDX_NULL; - - r = dns_cache_link_item(c, i); - if (r < 0) - return r; - - if (log_get_max_level() >= LOG_DEBUG) { - r = dns_resource_key_to_string(i->key, &key_str); - if (r < 0) - return r; - - log_debug("Added positive cache entry for %s", key_str); - } - - i = NULL; - return 0; -} - -static int dns_cache_put_negative( - DnsCache *c, - DnsResourceKey *key, - int rcode, - bool authenticated, - uint32_t nsec_ttl, - usec_t timestamp, - DnsResourceRecord *soa, - int owner_family, - const union in_addr_union *owner_address) { - - _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; - _cleanup_free_ char *key_str = NULL; - int r; - - assert(c); - assert(key); - assert(soa); - assert(owner_address); - - /* 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 (dns_type_is_pseudo(key->type)) - return 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/NSEC/NSEC3 TTL: %s", key_str); - } - - return 0; - } - - if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) - return 0; - - r = dns_cache_init(c); - if (r < 0) - return r; - - dns_cache_make_space(c, 1); - - i = new0(DnsCacheItem, 1); - if (!i) - return -ENOMEM; - - i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN; - i->until = calculate_until(soa, nsec_ttl, timestamp, true); - i->authenticated = authenticated; - i->owner_family = owner_family; - i->owner_address = *owner_address; - 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 - * a pseudo type for this purpose here. */ - 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); - - r = dns_cache_link_item(c, i); - if (r < 0) - return r; - - if (log_get_max_level() >= LOG_DEBUG) { - r = dns_resource_key_to_string(i->key, &key_str); - if (r < 0) - return r; - - log_debug("Added %s cache entry for %s", i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", key_str); - } - - i = NULL; - 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, - bool authenticated, - uint32_t nsec_ttl, - usec_t timestamp, - int owner_family, - const union in_addr_union *owner_address) { - - DnsResourceRecord *soa = NULL, *rr; - DnsAnswerFlags flags; - unsigned cache_keys; - int r, ifindex; - - assert(c); - assert(owner_address); - - dns_cache_remove_previous(c, key, answer); - - if (dns_answer_size(answer) <= 0) { - if (log_get_max_level() >= LOG_DEBUG) { - _cleanup_free_ char *key_str = NULL; - - r = dns_resource_key_to_string(key, &key_str); - if (r < 0) - return r; - - log_debug("Not caching negative entry without a SOA record: %s", key_str); - } - - return 0; - } - - /* 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 = dns_answer_size(answer); - if (key) - cache_keys ++; - - /* Make some space for our new entries */ - dns_cache_make_space(c, cache_keys); - - if (timestamp <= 0) - timestamp = now(clock_boottime_or_monotonic()); - - /* Second, add in positive entries for all contained RRs */ - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { - if ((flags & DNS_ANSWER_CACHEABLE) == 0) - continue; - - 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) /* 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, NULL); - if (r < 0) - goto fail; - if (r > 0) - return 0; - - /* 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, NULL); - if (r < 0) - goto fail; - if (r > 0) - return 0; - - /* 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, &flags); - if (r < 0) - goto fail; - if (r == 0) - return 0; - - /* 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; - - return 0; - -fail: - /* Adding all RRs failed. Let's clean up what we already - * added, just in case */ - - if (key) - dns_cache_remove_by_key(c, key); - - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - if ((flags & DNS_ANSWER_CACHEABLE) == 0) - continue; - - dns_cache_remove_by_key(c, rr->key); - } - - return r; -} - -static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, DnsResourceKey *k) { - DnsCacheItem *i; - const char *n; - int r; - - assert(c); - assert(k); - - /* If we hit some OOM error, or suchlike, we don't care too - * much, after all this is just a cache */ - - i = hashmap_get(c->by_key, k); - if (i) - return i; - - n = DNS_RESOURCE_KEY_NAME(k); - - /* Check if we have an NXDOMAIN cache item for the name, notice that we use - * the pseudo-type ANY for NXDOMAIN cache items. */ - i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_ANY, n)); - if (i && i->type == DNS_CACHE_NXDOMAIN) - return i; - - 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) - return i; - - /* OK, let's look for cached DNAME records. */ - for (;;) { - if (isempty(n)) - return NULL; - - i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n)); - if (i) - return i; - - /* Jump one label ahead */ - r = dns_name_parent(&n); - if (r <= 0) - return NULL; - } - } - - 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) - return i; - } - - return NULL; -} - -int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret, bool *authenticated) { - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - unsigned n = 0; - int r; - bool nxdomain = false; - _cleanup_free_ char *key_str = NULL; - DnsCacheItem *j, *first, *nsec = NULL; - bool have_authenticated = false, have_non_authenticated = false; - - assert(c); - assert(key); - assert(rcode); - assert(ret); - assert(authenticated); - - if (key->type == DNS_TYPE_ANY || - key->class == DNS_CLASS_ANY) { - - /* If we have ANY lookups we don't use the cache, so - * that the caller refreshes via the network. */ - - if (log_get_max_level() >= LOG_DEBUG) { - r = dns_resource_key_to_string(key, &key_str); - if (r < 0) - return r; - - log_debug("Ignoring cache for ANY lookup: %s", key_str); - } - - c->n_miss++; - - *ret = NULL; - *rcode = DNS_RCODE_SUCCESS; - return 0; - } - - first = dns_cache_get_by_key_follow_cname_dname_nsec(c, key); - if (!first) { - /* If one question cannot be answered we need to refresh */ - - if (log_get_max_level() >= LOG_DEBUG) { - r = dns_resource_key_to_string(key, &key_str); - if (r < 0) - return r; - - log_debug("Cache miss for %s", key_str); - } - - c->n_miss++; - - *ret = NULL; - *rcode = DNS_RCODE_SUCCESS; - return 0; - } - - LIST_FOREACH(by_key, j, first) { - if (j->rr) { - if (j->rr->key->type == DNS_TYPE_NSEC) - nsec = j; - - n++; - } else if (j->type == DNS_CACHE_NXDOMAIN) - nxdomain = true; - - if (j->authenticated) - have_authenticated = true; - else - have_non_authenticated = true; - } - - 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) - return r; - - log_debug("NSEC NODATA cache hit for %s", key_str); - } - - /* We only found an NSEC record that matches our name. - * If it says the type doesn't exist report - * NODATA. Otherwise report a cache miss. */ - - *ret = NULL; - *rcode = DNS_RCODE_SUCCESS; - *authenticated = nsec->authenticated; - - 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) { - r = dns_resource_key_to_string(key, &key_str); - if (r < 0) - return r; - - log_debug("%s cache hit for %s", - n > 0 ? "Positive" : - nxdomain ? "NXDOMAIN" : "NODATA", - key_str); - } - - if (n <= 0) { - c->n_hit++; - - *ret = NULL; - *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS; - *authenticated = have_authenticated && !have_non_authenticated; - return 1; - } - - answer = dns_answer_new(n); - if (!answer) - return -ENOMEM; - - LIST_FOREACH(by_key, j, first) { - if (!j->rr) - continue; - - 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; - answer = NULL; - - return n; -} - -int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) { - DnsCacheItem *i, *first; - bool same_owner = true; - - assert(cache); - assert(rr); - - dns_cache_prune(cache); - - /* See if there's a cache entry for the same key. If there - * isn't there's no conflict */ - first = hashmap_get(cache->by_key, rr->key); - if (!first) - return 0; - - /* See if the RR key is owned by the same owner, if so, there - * isn't a conflict either */ - LIST_FOREACH(by_key, i, first) { - if (i->owner_family != owner_family || - !in_addr_equal(owner_family, &i->owner_address, owner_address)) { - same_owner = false; - break; - } - } - if (same_owner) - return 0; - - /* See if there's the exact same RR in the cache. If yes, then - * there's no conflict. */ - if (dns_cache_get(cache, rr)) - return 0; - - /* There's a conflict */ - return 1; -} - -int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p) { - unsigned ancount = 0; - Iterator iterator; - DnsCacheItem *i; - int r; - - assert(cache); - assert(p); - - HASHMAP_FOREACH(i, cache->by_key, iterator) { - DnsCacheItem *j; - - LIST_FOREACH(by_key, j, i) { - if (!j->rr) - continue; - - if (!j->shared_owner) - continue; - - r = dns_packet_append_rr(p, j->rr, NULL, NULL); - if (r == -EMSGSIZE && p->protocol == DNS_PROTOCOL_MDNS) { - /* For mDNS, if we're unable to stuff all known answers into the given packet, - * allocate a new one, push the RR into that one and link it to the current one. - */ - - DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); - ancount = 0; - - r = dns_packet_new_query(&p->more, p->protocol, 0, true); - if (r < 0) - return r; - - /* continue with new packet */ - p = p->more; - r = dns_packet_append_rr(p, j->rr, NULL, NULL); - } - - if (r < 0) - return r; - - ancount ++; - } - } - - DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); - - return 0; -} - -void dns_cache_dump(DnsCache *cache, FILE *f) { - Iterator iterator; - DnsCacheItem *i; - int r; - - if (!cache) - return; - - if (!f) - f = stdout; - - HASHMAP_FOREACH(i, cache->by_key, iterator) { - DnsCacheItem *j; - - LIST_FOREACH(by_key, j, i) { - - fputc('\t', f); - - if (j->rr) { - const char *t; - t = dns_resource_record_to_string(j->rr); - if (!t) { - log_oom(); - continue; - } - - fputs(t, f); - fputc('\n', f); - } else { - _cleanup_free_ char *z = NULL; - r = dns_resource_key_to_string(j->key, &z); - if (r < 0) { - log_oom(); - continue; - } - - fputs(z, f); - fputs(" -- ", f); - fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f); - fputc('\n', f); - } - } - } -} - -bool dns_cache_is_empty(DnsCache *cache) { - if (!cache) - return true; - - 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 deleted file mode 100644 index 2293718e86..0000000000 --- a/src/resolve/resolved-dns-cache.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "hashmap.h" -#include "list.h" -#include "prioq.h" -#include "time-util.h" - -typedef struct DnsCache { - Hashmap *by_key; - Prioq *by_expiry; - unsigned n_hit; - unsigned n_miss; -} DnsCache; - -#include "resolved-dns-answer.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-question.h" -#include "resolved-dns-rr.h" - -void dns_cache_flush(DnsCache *c); -void dns_cache_prune(DnsCache *c); - -int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, bool authenticated, 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); - -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 deleted file mode 100644 index 7123d2d3a8..0000000000 --- a/src/resolve/resolved-dns-dnssec.c +++ /dev/null @@ -1,2211 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#ifdef HAVE_GCRYPT -#include -#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" - -#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: - * - * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone - * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree - * DS RRs are protected like normal RRs - * - * Example chain: - * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS - */ - -uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) { - const uint8_t *p; - uint32_t sum, f; - size_t i; - - /* The algorithm from RFC 4034, Appendix B. */ - - assert(dnskey); - assert(dnskey->key->type == DNS_TYPE_DNSKEY); - - 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; - - for (i = 0; i < dnskey->dnskey.key_size; i++) - sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i]; - - sum += (sum >> 16) & UINT32_C(0xFFFF); - - 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; - int r; - - /* Let's order the RRs according to RFC 4034, Section 6.3 */ - - assert(x); - assert(*x); - assert((*x)->wire_format); - assert(y); - assert(*y); - assert((*y)->wire_format); - - m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y)); - - r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m); - if (r != 0) - return r; - - if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) - return -1; - else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) - return 1; - - return 0; -} - -static int dnssec_rsa_verify_raw( - const char *hash_algorithm, - const void *signature, size_t signature_size, - const void *data, size_t data_size, - const void *exponent, size_t exponent_size, - const void *modulus, size_t modulus_size) { - - gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; - gcry_mpi_t n = NULL, e = NULL, s = NULL; - gcry_error_t ge; - int r; - - assert(hash_algorithm); - - ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&signature_sexp, - NULL, - "(sig-val (rsa (s %m)))", - s); - - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&data_sexp, - NULL, - "(data (flags pkcs1) (hash %s %b))", - hash_algorithm, - (int) data_size, - data); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&public_key_sexp, - NULL, - "(public-key (rsa (n %m) (e %m)))", - n, - e); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp); - if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE) - r = 0; - else if (ge != 0) { - log_debug("RSA signature check failed: %s", gpg_strerror(ge)); - r = -EIO; - } else - r = 1; - -finish: - if (e) - gcry_mpi_release(e); - if (n) - gcry_mpi_release(n); - if (s) - gcry_mpi_release(s); - - 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 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)); -} - -static void md_add_uint16(gcry_md_hd_t md, uint16_t v) { - v = htobe16(v); - gcry_md_write(md, &v, sizeof(v)); -} - -static void md_add_uint32(gcry_md_hd_t md, uint32_t v) { - v = htobe32(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; - - assert(rrsig); - assert(rrsig->key->type == DNS_TYPE_RRSIG); - - if (realtime == USEC_INFINITY) - realtime = now(CLOCK_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 true; - - /* Permit a certain amount of clock skew of 10% of the valid - * time range. This takes inspiration from unbound's - * resolver. */ - skew = (expiration - inception) / 10; - if (skew > SKEW_MAX) - skew = SKEW_MAX; - - if (inception < skew) - inception = 0; - else - inception -= skew; - - if (expiration + skew < expiration) - expiration = USEC_INFINITY; - else - expiration += skew; - - 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, - const DnsResourceKey *key, - DnsResourceRecord *rrsig, - DnsResourceRecord *dnskey, - usec_t realtime, - DnssecResult *result) { - - uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX]; - DnsResourceRecord **list, *rr; - const char *source, *name; - gcry_md_hd_t md = NULL; - int r, md_algorithm; - size_t k, n = 0; - size_t hash_size; - void *hash; - bool wildcard; - - assert(key); - assert(rrsig); - assert(dnskey); - assert(result); - assert(rrsig->key->type == DNS_TYPE_RRSIG); - assert(dnskey->key->type == DNS_TYPE_DNSKEY); - - /* Verifies the the RRSet matching the specified "key" in "a", - * using the signature "rrsig" and the key "dnskey". It's - * assumed the RRSIG and DNSKEY match. */ - - 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; - - r = dnssec_rrsig_prepare(rrsig); - if (r == -EINVAL) { - *result = DNSSEC_INVALID; - return r; - } - if (r < 0) - return r; - - r = dnssec_rrsig_expired(rrsig, realtime); - if (r < 0) - return r; - if (r > 0) { - *result = DNSSEC_SIGNATURE_EXPIRED; - 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 *, dns_answer_size(a)); - - DNS_ANSWER_FOREACH(rr, a) { - r = dns_resource_key_equal(key, rr->key); - if (r < 0) - return r; - if (r == 0) - continue; - - /* We need the wire format for ordering, and digest calculation */ - r = dns_resource_record_to_wire_format(rr, true); - if (r < 0) - return r; - - list[n++] = rr; - - if (n > VERIFY_RRS_MAX) - return -E2BIG; - } - - if (n <= 0) - return -ENODATA; - - /* Bring the RRs into canonical order */ - qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare); - - /* OK, the RRs are now in canonical order. Let's calculate the digest */ - initialize_libgcrypt(); - - hash_size = gcry_md_get_algo_dlen(md_algorithm); - assert(hash_size > 0); - - gcry_md_open(&md, md_algorithm, 0); - if (!md) - return -EIO; - - md_add_uint16(md, rrsig->rrsig.type_covered); - md_add_uint8(md, rrsig->rrsig.algorithm); - md_add_uint8(md, rrsig->rrsig.labels); - md_add_uint32(md, rrsig->rrsig.original_ttl); - md_add_uint32(md, rrsig->rrsig.expiration); - md_add_uint32(md, rrsig->rrsig.inception); - md_add_uint16(md, rrsig->rrsig.key_tag); - - r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true); - if (r < 0) - 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]; - - /* 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); - - l = DNS_RESOURCE_RECORD_RDATA_SIZE(rr); - assert(l <= 0xFFFF); - - md_add_uint16(md, (uint16_t) l); - gcry_md_write(md, DNS_RESOURCE_RECORD_RDATA(rr), l); - } - - hash = gcry_md_read(md, 0); - if (!hash) { - r = -EIO; - goto finish; - } - - switch (rrsig->rrsig.algorithm) { - - 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; - - 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; - } - - if (r < 0) - goto finish; - - /* 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: - gcry_md_close(md); - return r; -} - -int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) { - - assert(rrsig); - assert(dnskey); - - /* Checks if the specified DNSKEY RR matches the key used for - * the signature in the specified RRSIG RR */ - - if (rrsig->key->type != DNS_TYPE_RRSIG) - return -EINVAL; - - if (dnskey->key->type != DNS_TYPE_DNSKEY) - return 0; - if (dnskey->key->class != rrsig->key->class) - 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, false) != rrsig->rrsig.key_tag) - return 0; - - return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer); -} - -int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) { - assert(key); - assert(rrsig); - - /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */ - - if (rrsig->key->type != DNS_TYPE_RRSIG) - return 0; - if (rrsig->key->class != key->class) - return 0; - if (rrsig->rrsig.type_covered != key->type) - return 0; - - return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key)); -} - -int dnssec_verify_rrset_search( - DnsAnswer *a, - const DnsResourceKey *key, - DnsAnswer *validated_dnskeys, - usec_t realtime, - DnssecResult *result, - DnsResourceRecord **ret_rrsig) { - - 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 DNSKEYs in "validated_dnskeys" */ - - if (!a || a->n_rrs <= 0) - return -ENODATA; - - /* 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; - if (r == 0) - continue; - - found_rrsig = true; - - /* Look for a matching key */ - DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) { - DnssecResult one_result; - - 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; - - /* Take the time here, if it isn't set yet, so - * that we do all validations with the same - * time. */ - if (realtime == USEC_INFINITY) - realtime = now(CLOCK_REALTIME); - - /* Yay, we found a matching RRSIG with a matching - * DNSKEY, awesome. Now let's verify all entries of - * the RRSet against the RRSIG and DNSKEY - * combination. */ - - r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result); - if (r < 0) - return r; - - 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; - - 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_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_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) { - DnsResourceRecord *rr; - int r; - - /* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */ - - DNS_ANSWER_FOREACH(rr, a) { - r = dnssec_key_match_rrsig(key, rr); - if (r < 0) - return r; - if (r > 0) - return 1; - } - - return 0; -} - -static int digest_to_gcrypt_md(uint8_t algorithm) { - - /* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */ - - switch (algorithm) { - - case DNSSEC_DIGEST_SHA1: - return GCRY_MD_SHA1; - - case DNSSEC_DIGEST_SHA256: - return GCRY_MD_SHA256; - - case DNSSEC_DIGEST_SHA384: - return GCRY_MD_SHA384; - - default: - return -EOPNOTSUPP; - } -} - -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; - - assert(dnskey); - assert(ds); - - /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */ - - if (dnskey->key->type != DNS_TYPE_DNSKEY) - return -EINVAL; - if (ds->key->type != DNS_TYPE_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, mask_revoke) != ds->ds.key_tag) - return 0; - - initialize_libgcrypt(); - - md_algorithm = digest_to_gcrypt_md(ds->ds.digest_type); - if (md_algorithm < 0) - return md_algorithm; - - hash_size = gcry_md_get_algo_dlen(md_algorithm); - assert(hash_size > 0); - - if (ds->ds.digest_size != hash_size) - return 0; - - 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; - - gcry_md_write(md, owner_name, r); - 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); - - result = gcry_md_read(md, 0); - if (!result) { - r = -EIO; - goto finish; - } - - r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0; - -finish: - gcry_md_close(md); - return r; -} - -int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { - DnsResourceRecord *ds; - DnsAnswerFlags flags; - int r; - - assert(dnskey); - - if (dnskey->key->type != DNS_TYPE_DNSKEY) - return 0; - - 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_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) - return 1; - } - - return 0; -} - -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_SIGNATURE_EXPIRED] = "signature-expired", - [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm", - [DNSSEC_NO_SIGNATURE] = "no-signature", - [DNSSEC_MISSING_KEY] = "missing-key", - [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 deleted file mode 100644 index 77bd4d71bf..0000000000 --- a/src/resolve/resolved-dns-dnssec.h +++ /dev/null @@ -1,102 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -typedef enum DnssecResult DnssecResult; -typedef enum DnssecVerdict DnssecVerdict; - -#include "dns-domain.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-rr.h" - -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_SIGNATURE_EXPIRED, - DNSSEC_UNSUPPORTED_ALGORITHM, - - /* These two are added by dnssec_verify_rrset_search() */ - DNSSEC_NO_SIGNATURE, - DNSSEC_MISSING_KEY, - - /* 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) - -/* 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_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke); -int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds); - -int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key); - -uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke); - -int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max); - -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 deleted file mode 100644 index c940dd8929..0000000000 --- a/src/resolve/resolved-dns-packet.c +++ /dev/null @@ -1,2295 +0,0 @@ -/*** - 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 . - ***/ - -#include "alloc-util.h" -#include "dns-domain.h" -#include "resolved-dns-packet.h" -#include "string-table.h" -#include "strv.h" -#include "unaligned.h" -#include "utf8.h" -#include "util.h" - -#define EDNS0_OPT_DO (1<<15) - -int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { - DnsPacket *p; - size_t a; - - assert(ret); - - if (mtu <= UDP_PACKET_HEADER_SIZE) - a = DNS_PACKET_SIZE_START; - else - a = mtu - UDP_PACKET_HEADER_SIZE; - - if (a < DNS_PACKET_HEADER_SIZE) - a = DNS_PACKET_HEADER_SIZE; - - /* round up to next page size */ - a = PAGE_ALIGN(ALIGN(sizeof(DnsPacket)) + a) - ALIGN(sizeof(DnsPacket)); - - /* make sure we never allocate more than useful */ - if (a > DNS_PACKET_SIZE_MAX) - a = DNS_PACKET_SIZE_MAX; - - p = malloc0(ALIGN(sizeof(DnsPacket)) + a); - if (!p) - return -ENOMEM; - - 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; - - return 0; -} - -void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated) { - - DnsPacketHeader *h; - - assert(p); - - h = DNS_PACKET_HEADER(p); - - switch(p->protocol) { - case DNS_PROTOCOL_LLMNR: - assert(!truncated); - - h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, - 0 /* opcode */, - 0 /* c */, - 0 /* tc */, - 0 /* t */, - 0 /* ra */, - 0 /* ad */, - 0 /* cd */, - 0 /* rcode */)); - break; - - case DNS_PROTOCOL_MDNS: - h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, - 0 /* opcode */, - 0 /* aa */, - truncated /* tc */, - 0 /* rd (ask for recursion) */, - 0 /* ra */, - 0 /* ad */, - 0 /* cd */, - 0 /* rcode */)); - break; - - default: - assert(!truncated); - - h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, - 0 /* opcode */, - 0 /* aa */, - 0 /* tc */, - 1 /* rd (ask for recursion) */, - 0 /* ra */, - 0 /* ad */, - dnssec_checking_disabled /* cd */, - 0 /* rcode */)); - } -} - -int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled) { - DnsPacket *p; - int r; - - assert(ret); - - r = dns_packet_new(&p, protocol, mtu); - if (r < 0) - return r; - - /* Always set the TC bit to 0 initially. - * If there are multiple packets later, we'll update the bit shortly before sending. - */ - dns_packet_set_flags(p, dnssec_checking_disabled, false); - - *ret = p; - return 0; -} - -DnsPacket *dns_packet_ref(DnsPacket *p) { - - if (!p) - return NULL; - - assert(!p->on_stack); - - assert(p->n_ref > 0); - p->n_ref++; - return p; -} - -static void dns_packet_free(DnsPacket *p) { - char *s; - - assert(p); - - dns_question_unref(p->question); - dns_answer_unref(p->answer); - dns_resource_record_unref(p->opt); - - while ((s = hashmap_steal_first_key(p->names))) - free(s); - hashmap_free(p->names); - - free(p->_data); - - if (!p->on_stack) - free(p); -} - -DnsPacket *dns_packet_unref(DnsPacket *p) { - if (!p) - return NULL; - - assert(p->n_ref > 0); - - dns_packet_unref(p->more); - - if (p->n_ref == 1) - dns_packet_free(p); - else - p->n_ref--; - - return NULL; -} - -int dns_packet_validate(DnsPacket *p) { - assert(p); - - if (p->size < DNS_PACKET_HEADER_SIZE) - return -EBADMSG; - - if (p->size > DNS_PACKET_SIZE_MAX) - return -EBADMSG; - - return 1; -} - -int dns_packet_validate_reply(DnsPacket *p) { - int r; - - assert(p); - - r = dns_packet_validate(p); - if (r < 0) - return r; - - if (DNS_PACKET_QR(p) != 1) - return 0; - - if (DNS_PACKET_OPCODE(p) != 0) - return -EBADMSG; - - switch (p->protocol) { - - case DNS_PROTOCOL_LLMNR: - /* RFC 4795, Section 2.1.1. says to discard all replies with QDCOUNT != 1 */ - if (DNS_PACKET_QDCOUNT(p) != 1) - return -EBADMSG; - - break; - - case DNS_PROTOCOL_MDNS: - /* RFC 6762, Section 18 */ - if (DNS_PACKET_RCODE(p) != 0) - return -EBADMSG; - - break; - - default: - break; - } - - return 1; -} - -int dns_packet_validate_query(DnsPacket *p) { - int r; - - assert(p); - - r = dns_packet_validate(p); - if (r < 0) - return r; - - if (DNS_PACKET_QR(p) != 0) - return 0; - - if (DNS_PACKET_OPCODE(p) != 0) - return -EBADMSG; - - if (DNS_PACKET_TC(p)) - return -EBADMSG; - - switch (p->protocol) { - - case DNS_PROTOCOL_LLMNR: - /* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */ - if (DNS_PACKET_QDCOUNT(p) != 1) - return -EBADMSG; - - /* RFC 4795, Section 2.1.1. says to discard all queries with ANCOUNT != 0 */ - if (DNS_PACKET_ANCOUNT(p) > 0) - return -EBADMSG; - - /* RFC 4795, Section 2.1.1. says to discard all queries with NSCOUNT != 0 */ - if (DNS_PACKET_NSCOUNT(p) > 0) - return -EBADMSG; - - break; - - case DNS_PROTOCOL_MDNS: - /* RFC 6762, Section 18 */ - if (DNS_PACKET_AA(p) != 0 || - DNS_PACKET_RD(p) != 0 || - DNS_PACKET_RA(p) != 0 || - DNS_PACKET_AD(p) != 0 || - DNS_PACKET_CD(p) != 0 || - DNS_PACKET_RCODE(p) != 0) - return -EBADMSG; - - break; - - default: - break; - } - - return 1; -} - -static int dns_packet_extend(DnsPacket *p, size_t add, void **ret, size_t *start) { - assert(p); - - if (p->size + add > p->allocated) { - size_t a; - - a = PAGE_ALIGN((p->size + add) * 2); - if (a > DNS_PACKET_SIZE_MAX) - a = DNS_PACKET_SIZE_MAX; - - if (p->size + add > a) - return -EMSGSIZE; - - if (p->_data) { - void *d; - - d = realloc(p->_data, a); - if (!d) - return -ENOMEM; - - p->_data = d; - } else { - p->_data = malloc(a); - if (!p->_data) - return -ENOMEM; - - memcpy(p->_data, (uint8_t*) p + ALIGN(sizeof(DnsPacket)), p->size); - memzero((uint8_t*) p->_data + p->size, a - p->size); - } - - p->allocated = a; - } - - if (start) - *start = p->size; - - if (ret) - *ret = (uint8_t*) DNS_PACKET_DATA(p) + p->size; - - p->size += add; - return 0; -} - -void dns_packet_truncate(DnsPacket *p, size_t sz) { - Iterator i; - char *s; - void *n; - - assert(p); - - if (p->size <= sz) - return; - - HASHMAP_FOREACH_KEY(n, s, p->names, i) { - - if (PTR_TO_SIZE(n) < sz) - continue; - - hashmap_remove(p->names, s); - free(s); - } - - p->size = sz; -} - -int dns_packet_append_blob(DnsPacket *p, const void *d, size_t l, size_t *start) { - void *q; - int r; - - assert(p); - - r = dns_packet_extend(p, l, &q, start); - if (r < 0) - return r; - - memcpy(q, d, l); - return 0; -} - -int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start) { - void *d; - int r; - - assert(p); - - r = dns_packet_extend(p, sizeof(uint8_t), &d, start); - if (r < 0) - return r; - - ((uint8_t*) d)[0] = v; - - return 0; -} - -int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start) { - void *d; - int r; - - assert(p); - - r = dns_packet_extend(p, sizeof(uint16_t), &d, start); - if (r < 0) - return r; - - unaligned_write_be16(d, v); - - return 0; -} - -int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start) { - void *d; - int r; - - assert(p); - - r = dns_packet_extend(p, sizeof(uint32_t), &d, start); - if (r < 0) - return r; - - unaligned_write_be32(d, v); - - return 0; -} - -int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start) { - assert(p); - assert(s); - - return dns_packet_append_raw_string(p, s, strlen(s), start); -} - -int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start) { - void *d; - int r; - - assert(p); - assert(s || size == 0); - - if (size > 255) - return -E2BIG; - - r = dns_packet_extend(p, 1 + size, &d, start); - if (r < 0) - return r; - - ((uint8_t*) d)[0] = (uint8_t) size; - - if (size > 0) - memcpy(((uint8_t*) d) + 1, s, size); - - return 0; -} - -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); - - if (l > DNS_LABEL_MAX) - return -E2BIG; - - r = dns_packet_extend(p, 1 + l, (void**) &w, start); - if (r < 0) - return r; - - *(w++) = (uint8_t) l; - - 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++) - 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 - * matters and needs to be retained. */ - memcpy(w, d, l); - - return 0; -} - -int dns_packet_append_name( - DnsPacket *p, - const char *name, - bool allow_compression, - bool canonical_candidate, - size_t *start) { - - size_t saved_size; - int r; - - assert(p); - assert(name); - - if (p->refuse_compression) - allow_compression = false; - - saved_size = p->size; - - while (!dns_name_is_root(name)) { - const char *z = name; - char label[DNS_LABEL_MAX]; - size_t n = 0; - - if (allow_compression) - n = PTR_TO_SIZE(hashmap_get(p->names, name)); - if (n > 0) { - assert(n < p->size); - - if (n < 0x4000) { - r = dns_packet_append_uint16(p, 0xC000 | n, NULL); - if (r < 0) - goto fail; - - goto done; - } - } - - r = dns_label_unescape(&name, label, sizeof(label)); - if (r < 0) - goto fail; - - 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; - - r = hashmap_put(p->names, s, SIZE_TO_PTR(n)); - if (r < 0) - goto fail; - - s = NULL; - } - } - - r = dns_packet_append_uint8(p, 0, NULL); - if (r < 0) - return r; - -done: - if (start) - *start = saved_size; - - return 0; - -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) { - size_t saved_size; - int r; - - assert(p); - assert(k); - - saved_size = p->size; - - r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, true, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, k->type, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, k->class, NULL); - if (r < 0) - goto fail; - - if (start) - *start = saved_size; - - return 0; - -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -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; - - assert(p); - assert(types); - assert(length > 0); - - saved_size = p->size; - - r = dns_packet_append_uint8(p, window, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, length, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, types, length, NULL); - if (r < 0) - goto fail; - - if (start) - *start = saved_size; - - return 0; -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) { - Iterator i; - uint8_t window = 0; - uint8_t entry = 0; - uint8_t bitmaps[32] = {}; - unsigned n; - size_t saved_size; - int r; - - assert(p); - - saved_size = p->size; - - BITMAP_FOREACH(n, types, i) { - assert(n <= 0xffff); - - if ((n >> 8) != window && bitmaps[entry / 8] != 0) { - r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); - if (r < 0) - goto fail; - - zero(bitmaps); - } - - window = n >> 8; - entry = n & 255; - - bitmaps[entry / 8] |= 1 << (7 - (entry % 8)); - } - - 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; - - return 0; -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -/* Append the OPT pseudo-RR described in RFC6891 */ -int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start) { - size_t saved_size; - int r; - - assert(p); - /* 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 */ - r = dns_packet_append_uint8(p, 0, NULL); - if (r < 0) - return r; - - /* type */ - r = dns_packet_append_uint16(p, DNS_TYPE_OPT, NULL); - if (r < 0) - goto fail; - - /* maximum udp packet that can be received */ - r = dns_packet_append_uint16(p, max_udp_size, NULL); - if (r < 0) - goto fail; - - /* extended RCODE and VERSION */ - r = dns_packet_append_uint16(p, 0, NULL); - if (r < 0) - goto fail; - - /* flags: DNSSEC OK (DO), see RFC3225 */ - r = dns_packet_append_uint16(p, edns0_do ? EDNS0_OPT_DO : 0, NULL); - if (r < 0) - goto fail; - - /* RDLENGTH */ - - 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; - - return 0; - -fail: - dns_packet_truncate(p, saved_size); - 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; - - assert(p); - assert(rr); - - saved_size = p->size; - - r = dns_packet_append_key(p, rr->key, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->ttl, NULL); - if (r < 0) - goto fail; - - /* Initially we write 0 here */ - r = dns_packet_append_uint16(p, 0, &rdlength_offset); - if (r < 0) - goto fail; - - rds = p->size - saved_size; - - switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { - - case DNS_TYPE_SRV: - r = dns_packet_append_uint16(p, rr->srv.priority, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, rr->srv.weight, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, rr->srv.port, NULL); - if (r < 0) - goto fail; - - 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, false, NULL); - break; - - case DNS_TYPE_HINFO: - r = dns_packet_append_string(p, rr->hinfo.cpu, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_string(p, rr->hinfo.os, NULL); - break; - - case DNS_TYPE_SPF: /* exactly the same as TXT */ - case DNS_TYPE_TXT: - - if (!rr->txt.items) { - /* RFC 6763, section 6.1 suggests to generate - * single empty string for an empty array. */ - - r = dns_packet_append_raw_string(p, NULL, 0, NULL); - if (r < 0) - goto fail; - } else { - DnsTxtItem *i; - - LIST_FOREACH(items, i, rr->txt.items) { - r = dns_packet_append_raw_string(p, i->data, i->length, NULL); - if (r < 0) - goto fail; - } - } - - r = 0; - break; - - case DNS_TYPE_A: - r = dns_packet_append_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); - break; - - case DNS_TYPE_AAAA: - r = dns_packet_append_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL); - break; - - case DNS_TYPE_SOA: - 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, false, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.serial, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.refresh, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.retry, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.expire, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.minimum, NULL); - break; - - case DNS_TYPE_MX: - r = dns_packet_append_uint16(p, rr->mx.priority, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_name(p, rr->mx.exchange, true, false, NULL); - break; - - case DNS_TYPE_LOC: - r = dns_packet_append_uint8(p, rr->loc.version, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->loc.size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->loc.horiz_pre, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->loc.vert_pre, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->loc.latitude, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->loc.longitude, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->loc.altitude, NULL); - break; - - case DNS_TYPE_DS: - r = dns_packet_append_uint16(p, rr->ds.key_tag, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->ds.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->ds.digest_type, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->ds.digest, rr->ds.digest_size, NULL); - break; - - case DNS_TYPE_SSHFP: - r = dns_packet_append_uint8(p, rr->sshfp.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->sshfp.fptype, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, NULL); - break; - - case DNS_TYPE_DNSKEY: - r = dns_packet_append_uint16(p, rr->dnskey.flags, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->dnskey.protocol, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->dnskey.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->dnskey.key, rr->dnskey.key_size, NULL); - break; - - case DNS_TYPE_RRSIG: - r = dns_packet_append_uint16(p, rr->rrsig.type_covered, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->rrsig.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->rrsig.labels, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->rrsig.original_ttl, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->rrsig.expiration, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->rrsig.inception, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, rr->rrsig.key_tag, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_name(p, rr->rrsig.signer, false, true, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->rrsig.signature, rr->rrsig.signature_size, NULL); - break; - - case DNS_TYPE_NSEC: - r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, false, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_types(p, rr->nsec.types, NULL); - if (r < 0) - goto fail; - - break; - - case DNS_TYPE_NSEC3: - r = dns_packet_append_uint8(p, rr->nsec3.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->nsec3.flags, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, rr->nsec3.iterations, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->nsec3.salt_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->nsec3.salt, rr->nsec3.salt_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->nsec3.next_hashed_name_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_types(p, rr->nsec3.types, NULL); - if (r < 0) - goto fail; - - 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.data_size, NULL); - break; - } - if (r < 0) - goto fail; - - /* Let's calculate the actual data size and update the field */ - rdlength = p->size - rdlength_offset - sizeof(uint16_t); - if (rdlength > 0xFFFF) { - r = -ENOSPC; - goto fail; - } - - end = p->size; - p->size = rdlength_offset; - r = dns_packet_append_uint16(p, rdlength, NULL); - if (r < 0) - goto fail; - p->size = end; - - if (start) - *start = saved_size; - - if (rdata_start) - *rdata_start = rds; - - return 0; - -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) { - assert(p); - - if (p->rindex + sz > p->size) - return -EMSGSIZE; - - if (ret) - *ret = (uint8_t*) DNS_PACKET_DATA(p) + p->rindex; - - if (start) - *start = p->rindex; - - p->rindex += sz; - return 0; -} - -void dns_packet_rewind(DnsPacket *p, size_t idx) { - assert(p); - assert(idx <= p->size); - assert(idx >= DNS_PACKET_HEADER_SIZE); - - p->rindex = idx; -} - -int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start) { - const void *q; - int r; - - assert(p); - assert(d); - - r = dns_packet_read(p, sz, &q, start); - if (r < 0) - return r; - - memcpy(d, q, sz); - return 0; -} - -static int dns_packet_read_memdup( - DnsPacket *p, size_t size, - void **ret, size_t *ret_size, - size_t *ret_start) { - - const void *src; - size_t start; - int r; - - assert(p); - assert(ret); - - r = dns_packet_read(p, size, &src, &start); - if (r < 0) - return r; - - if (size <= 0) - *ret = NULL; - else { - void *copy; - - copy = memdup(src, size); - if (!copy) - return -ENOMEM; - - *ret = copy; - } - - if (ret_size) - *ret_size = size; - if (ret_start) - *ret_start = start; - - return 0; -} - -int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start) { - const void *d; - int r; - - assert(p); - - r = dns_packet_read(p, sizeof(uint8_t), &d, start); - if (r < 0) - return r; - - *ret = ((uint8_t*) d)[0]; - return 0; -} - -int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start) { - const void *d; - int r; - - assert(p); - - r = dns_packet_read(p, sizeof(uint16_t), &d, start); - if (r < 0) - return r; - - *ret = unaligned_read_be16(d); - - return 0; -} - -int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start) { - const void *d; - int r; - - assert(p); - - r = dns_packet_read(p, sizeof(uint32_t), &d, start); - if (r < 0) - return r; - - *ret = unaligned_read_be32(d); - - return 0; -} - -int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start) { - size_t saved_rindex; - const void *d; - char *t; - uint8_t c; - int r; - - assert(p); - - saved_rindex = p->rindex; - - r = dns_packet_read_uint8(p, &c, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read(p, c, &d, NULL); - if (r < 0) - goto fail; - - if (memchr(d, 0, c)) { - r = -EBADMSG; - goto fail; - } - - t = strndup(d, c); - if (!t) { - r = -ENOMEM; - goto fail; - } - - if (!utf8_is_valid(t)) { - free(t); - r = -EBADMSG; - goto fail; - } - - *ret = t; - - if (start) - *start = saved_rindex; - - return 0; - -fail: - dns_packet_rewind(p, saved_rindex); - return r; -} - -int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start) { - size_t saved_rindex; - uint8_t c; - int r; - - assert(p); - - saved_rindex = p->rindex; - - r = dns_packet_read_uint8(p, &c, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read(p, c, ret, NULL); - if (r < 0) - goto fail; - - if (size) - *size = c; - if (start) - *start = saved_rindex; - - return 0; - -fail: - dns_packet_rewind(p, saved_rindex); - return r; -} - -int dns_packet_read_name( - DnsPacket *p, - char **_ret, - bool allow_compression, - size_t *start) { - - size_t saved_rindex, after_rindex = 0, jump_barrier; - _cleanup_free_ char *ret = NULL; - size_t n = 0, allocated = 0; - bool first = true; - int r; - - assert(p); - assert(_ret); - - if (p->refuse_compression) - allow_compression = false; - - saved_rindex = p->rindex; - jump_barrier = p->rindex; - - for (;;) { - uint8_t c, d; - - r = dns_packet_read_uint8(p, &c, NULL); - if (r < 0) - goto fail; - - if (c == 0) - /* End of name */ - break; - else if (c <= 63) { - const char *label; - - /* Literal label */ - r = dns_packet_read(p, c, (const void**) &label, NULL); - if (r < 0) - goto fail; - - if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) { - r = -ENOMEM; - goto fail; - } - - if (first) - first = false; - else - ret[n++] = '.'; - - r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - goto fail; - - n += r; - continue; - } else if (allow_compression && (c & 0xc0) == 0xc0) { - uint16_t ptr; - - /* Pointer */ - r = dns_packet_read_uint8(p, &d, NULL); - if (r < 0) - goto fail; - - ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; - if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= jump_barrier) { - r = -EBADMSG; - goto fail; - } - - if (after_rindex == 0) - after_rindex = p->rindex; - - /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */ - jump_barrier = ptr; - p->rindex = ptr; - } else { - r = -EBADMSG; - goto fail; - } - } - - if (!GREEDY_REALLOC(ret, allocated, n + 1)) { - r = -ENOMEM; - goto fail; - } - - ret[n] = 0; - - if (after_rindex != 0) - p->rindex= after_rindex; - - *_ret = ret; - ret = NULL; - - if (start) - *start = saved_rindex; - - return 0; - -fail: - dns_packet_rewind(p, saved_rindex); - return r; -} - -static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *start) { - uint8_t window; - uint8_t length; - const uint8_t *bitmap; - uint8_t bit = 0; - unsigned i; - bool found = false; - size_t saved_rindex; - int r; - - assert(p); - assert(types); - - saved_rindex = p->rindex; - - r = bitmap_ensure_allocated(types); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &window, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &length, NULL); - if (r < 0) - goto fail; - - if (length == 0 || length > 32) - return -EBADMSG; - - r = dns_packet_read(p, length, (const void **)&bitmap, NULL); - if (r < 0) - goto fail; - - for (i = 0; i < length; i++) { - uint8_t bitmask = 1 << 7; - - if (!bitmap[i]) { - found = false; - bit += 8; - continue; - } - - found = true; - - while (bitmask) { - if (bitmap[i] & bitmask) { - uint16_t n; - - n = (uint16_t) window << 8 | (uint16_t) bit; - - /* Ignore pseudo-types. see RFC4034 section 4.1.2 */ - if (dns_type_is_pseudo(n)) - continue; - - r = bitmap_set(*types, n); - if (r < 0) - goto fail; - } - - bit ++; - bitmask >>= 1; - } - } - - if (!found) - return -EBADMSG; - - if (start) - *start = saved_rindex; - - return 0; -fail: - dns_packet_rewind(p, saved_rindex); - return r; -} - -static int dns_packet_read_type_windows(DnsPacket *p, Bitmap **types, size_t size, size_t *start) { - size_t saved_rindex; - int r; - - saved_rindex = p->rindex; - - while (p->rindex < saved_rindex + size) { - r = dns_packet_read_type_window(p, types, NULL); - if (r < 0) - goto fail; - - /* don't read past end of current RR */ - if (p->rindex > saved_rindex + size) { - r = -EBADMSG; - goto fail; - } - } - - if (p->rindex != saved_rindex + size) { - r = -EBADMSG; - goto fail; - } - - if (start) - *start = saved_rindex; - - return 0; -fail: - dns_packet_rewind(p, saved_rindex); - return r; -} - -int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start) { - _cleanup_free_ char *name = NULL; - bool cache_flush = false; - uint16_t class, type; - DnsResourceKey *key; - size_t saved_rindex; - int r; - - assert(p); - assert(ret); - - saved_rindex = p->rindex; - - r = dns_packet_read_name(p, &name, true, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint16(p, &type, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint16(p, &class, NULL); - if (r < 0) - goto fail; - - if (p->protocol == DNS_PROTOCOL_MDNS) { - /* See RFC6762, Section 10.2 */ - - if (type != DNS_TYPE_OPT && (class & MDNS_RR_CACHE_FLUSH)) { - class &= ~MDNS_RR_CACHE_FLUSH; - cache_flush = true; - } - } - - key = dns_resource_key_new_consume(class, type, name); - if (!key) { - r = -ENOMEM; - goto fail; - } - - name = NULL; - *ret = key; - - if (ret_cache_flush) - *ret_cache_flush = cache_flush; - if (start) - *start = saved_rindex; - - return 0; -fail: - dns_packet_rewind(p, saved_rindex); - return r; -} - -static bool loc_size_ok(uint8_t size) { - uint8_t m = size >> 4, e = size & 0xF; - - return m <= 9 && e <= 9 && (m > 0 || e == 0); -} - -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); - assert(ret); - - saved_rindex = p->rindex; - - r = dns_packet_read_key(p, &key, &cache_flush, NULL); - if (r < 0) - goto fail; - - if (!dns_class_is_valid_rr(key->class)|| - !dns_type_is_valid_rr(key->type)) { - r = -EBADMSG; - goto fail; - } - - rr = dns_resource_record_new(key); - if (!rr) { - r = -ENOMEM; - goto fail; - } - - r = dns_packet_read_uint32(p, &rr->ttl, NULL); - 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; - - if (p->rindex + rdlength > p->size) { - r = -EBADMSG; - goto fail; - } - - offset = p->rindex; - - switch (rr->key->type) { - - case DNS_TYPE_SRV: - r = dns_packet_read_uint16(p, &rr->srv.priority, NULL); - if (r < 0) - goto fail; - r = dns_packet_read_uint16(p, &rr->srv.weight, NULL); - if (r < 0) - goto fail; - r = dns_packet_read_uint16(p, &rr->srv.port, NULL); - if (r < 0) - goto fail; - r = dns_packet_read_name(p, &rr->srv.name, true, NULL); - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - r = dns_packet_read_name(p, &rr->ptr.name, true, NULL); - break; - - case DNS_TYPE_HINFO: - r = dns_packet_read_string(p, &rr->hinfo.cpu, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_string(p, &rr->hinfo.os, NULL); - break; - - case DNS_TYPE_SPF: /* exactly the same as TXT */ - case DNS_TYPE_TXT: - if (rdlength <= 0) { - DnsTxtItem *i; - /* RFC 6763, section 6.1 suggests to treat - * empty TXT RRs as equivalent to a TXT record - * with a single empty string. */ - - i = malloc0(offsetof(DnsTxtItem, data) + 1); /* for safety reasons we add an extra NUL byte */ - if (!i) - return -ENOMEM; - - rr->txt.items = i; - } else { - DnsTxtItem *last = NULL; - - while (p->rindex < offset + rdlength) { - DnsTxtItem *i; - const void *data; - size_t sz; - - r = dns_packet_read_raw_string(p, &data, &sz, NULL); - if (r < 0) - return r; - - i = malloc0(offsetof(DnsTxtItem, data) + sz + 1); /* extra NUL byte at the end */ - if (!i) - return -ENOMEM; - - memcpy(i->data, data, sz); - i->length = sz; - - LIST_INSERT_AFTER(items, rr->txt.items, last, i); - last = i; - } - } - - r = 0; - break; - - case DNS_TYPE_A: - r = dns_packet_read_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); - break; - - case DNS_TYPE_AAAA: - r = dns_packet_read_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL); - break; - - case DNS_TYPE_SOA: - r = dns_packet_read_name(p, &rr->soa.mname, true, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_name(p, &rr->soa.rname, true, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->soa.serial, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->soa.refresh, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->soa.retry, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->soa.expire, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->soa.minimum, NULL); - break; - - case DNS_TYPE_MX: - r = dns_packet_read_uint16(p, &rr->mx.priority, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_name(p, &rr->mx.exchange, true, NULL); - break; - - case DNS_TYPE_LOC: { - uint8_t t; - size_t pos; - - r = dns_packet_read_uint8(p, &t, &pos); - if (r < 0) - goto fail; - - if (t == 0) { - rr->loc.version = t; - - r = dns_packet_read_uint8(p, &rr->loc.size, NULL); - if (r < 0) - goto fail; - - if (!loc_size_ok(rr->loc.size)) { - r = -EBADMSG; - goto fail; - } - - r = dns_packet_read_uint8(p, &rr->loc.horiz_pre, NULL); - if (r < 0) - goto fail; - - if (!loc_size_ok(rr->loc.horiz_pre)) { - r = -EBADMSG; - goto fail; - } - - r = dns_packet_read_uint8(p, &rr->loc.vert_pre, NULL); - if (r < 0) - goto fail; - - if (!loc_size_ok(rr->loc.vert_pre)) { - r = -EBADMSG; - goto fail; - } - - r = dns_packet_read_uint32(p, &rr->loc.latitude, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->loc.longitude, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->loc.altitude, NULL); - if (r < 0) - goto fail; - - break; - } else { - dns_packet_rewind(p, pos); - rr->unparseable = true; - goto unparseable; - } - } - - case DNS_TYPE_DS: - r = dns_packet_read_uint16(p, &rr->ds.key_tag, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->ds.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->ds.digest_type, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_memdup(p, rdlength - 4, - &rr->ds.digest, &rr->ds.digest_size, - NULL); - if (r < 0) - goto fail; - - if (rr->ds.digest_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_SSHFP: - r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->sshfp.fptype, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_memdup(p, rdlength - 2, - &rr->sshfp.fingerprint, &rr->sshfp.fingerprint_size, - NULL); - - if (rr->sshfp.fingerprint_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_DNSKEY: - r = dns_packet_read_uint16(p, &rr->dnskey.flags, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->dnskey.protocol, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_memdup(p, rdlength - 4, - &rr->dnskey.key, &rr->dnskey.key_size, - NULL); - - if (rr->dnskey.key_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_RRSIG: - r = dns_packet_read_uint16(p, &rr->rrsig.type_covered, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->rrsig.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->rrsig.labels, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->rrsig.original_ttl, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->rrsig.expiration, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->rrsig.inception, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint16(p, &rr->rrsig.key_tag, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_name(p, &rr->rrsig.signer, false, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_memdup(p, offset + rdlength - p->rindex, - &rr->rrsig.signature, &rr->rrsig.signature_size, - NULL); - - if (rr->rrsig.signature_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_NSEC: { - - /* - * RFC6762, section 18.14 explictly states mDNS should use name compression. - * This contradicts RFC3845, section 2.1.1 - */ - - bool allow_compressed = p->protocol == DNS_PROTOCOL_MDNS; - - r = dns_packet_read_name(p, &rr->nsec.next_domain_name, allow_compressed, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_type_windows(p, &rr->nsec.types, offset + rdlength - p->rindex, NULL); - if (r < 0) - goto fail; - - /* We accept empty NSEC bitmaps. The bit indicating the presence of the NSEC record itself - * is redundant and in e.g., RFC4956 this fact is used to define a use for NSEC records - * without the NSEC bit set. */ - - break; - } - case DNS_TYPE_NSEC3: { - uint8_t size; - - r = dns_packet_read_uint8(p, &rr->nsec3.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->nsec3.flags, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint16(p, &rr->nsec3.iterations, NULL); - if (r < 0) - goto fail; - - /* this may be zero */ - r = dns_packet_read_uint8(p, &size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_memdup(p, size, &rr->nsec3.salt, &rr->nsec3.salt_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &size, NULL); - if (r < 0) - goto fail; - - if (size <= 0) { - r = -EBADMSG; - goto fail; - } - - r = dns_packet_read_memdup(p, size, &rr->nsec3.next_hashed_name, &rr->nsec3.next_hashed_name_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_type_windows(p, &rr->nsec3.types, offset + rdlength - p->rindex, NULL); - if (r < 0) - goto fail; - - /* empty non-terminals can have NSEC3 records, so empty bitmaps are allowed */ - - 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.data_size, NULL); - if (r < 0) - goto fail; - break; - } - if (r < 0) - goto fail; - if (p->rindex != offset + rdlength) { - r = -EBADMSG; - goto fail; - } - - *ret = rr; - rr = NULL; - - if (ret_cache_flush) - *ret_cache_flush = cache_flush; - if (start) - *start = saved_rindex; - - return 0; -fail: - dns_packet_rewind(p, saved_rindex); - 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; - size_t saved_rindex; - unsigned n, i; - int r; - - if (p->extracted) - return 0; - - saved_rindex = p->rindex; - dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE); - - n = DNS_PACKET_QDCOUNT(p); - if (n > 0) { - question = dns_question_new(n); - if (!question) { - r = -ENOMEM; - goto finish; - } - - for (i = 0; i < n; i++) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - bool cache_flush; - - 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; - } - } - - 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; - goto finish; - } - - for (i = 0; i < n; i++) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - bool cache_flush; - - 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) { - 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 is 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 { - - /* 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; - question = NULL; - - p->answer = answer; - answer = NULL; - - p->extracted = true; - - r = 0; - -finish: - p->rindex = saved_rindex; - return r; -} - -int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key) { - int r; - - assert(p); - assert(key); - - /* Checks if the specified packet is a reply for the specified - * key and the specified key is the only one in the question - * section. */ - - if (DNS_PACKET_QR(p) != 1) - return 0; - - /* Let's unpack the packet, if that hasn't happened yet. */ - r = dns_packet_extract(p); - if (r < 0) - return r; - - if (p->question->n_keys != 1) - return 0; - - return dns_resource_key_equal(p->question->keys[0], key); -} - -static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = { - [DNS_RCODE_SUCCESS] = "SUCCESS", - [DNS_RCODE_FORMERR] = "FORMERR", - [DNS_RCODE_SERVFAIL] = "SERVFAIL", - [DNS_RCODE_NXDOMAIN] = "NXDOMAIN", - [DNS_RCODE_NOTIMP] = "NOTIMP", - [DNS_RCODE_REFUSED] = "REFUSED", - [DNS_RCODE_YXDOMAIN] = "YXDOMAIN", - [DNS_RCODE_YXRRSET] = "YRRSET", - [DNS_RCODE_NXRRSET] = "NXRRSET", - [DNS_RCODE_NOTAUTH] = "NOTAUTH", - [DNS_RCODE_NOTZONE] = "NOTZONE", - [DNS_RCODE_BADVERS] = "BADVERS", - [DNS_RCODE_BADKEY] = "BADKEY", - [DNS_RCODE_BADTIME] = "BADTIME", - [DNS_RCODE_BADMODE] = "BADMODE", - [DNS_RCODE_BADNAME] = "BADNAME", - [DNS_RCODE_BADALG] = "BADALG", - [DNS_RCODE_BADTRUNC] = "BADTRUNC", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_rcode, int); - -static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = { - [DNS_PROTOCOL_DNS] = "dns", - [DNS_PROTOCOL_MDNS] = "mdns", - [DNS_PROTOCOL_LLMNR] = "llmnr", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_protocol, DnsProtocol); diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h deleted file mode 100644 index 0bf34d270c..0000000000 --- a/src/resolve/resolved-dns-packet.h +++ /dev/null @@ -1,272 +0,0 @@ -#pragma once - -/*** - 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 . - ***/ - -#include -#include - -#include "hashmap.h" -#include "in-addr-util.h" -#include "macro.h" -#include "sparse-endian.h" - -typedef struct DnsPacketHeader DnsPacketHeader; -typedef struct DnsPacket DnsPacket; - -#include "resolved-def.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-question.h" -#include "resolved-dns-rr.h" - -typedef enum DnsProtocol { - DNS_PROTOCOL_DNS, - DNS_PROTOCOL_MDNS, - DNS_PROTOCOL_LLMNR, - _DNS_PROTOCOL_MAX, - _DNS_PROTOCOL_INVALID = -1 -} DnsProtocol; - -struct DnsPacketHeader { - uint16_t id; - be16_t flags; - be16_t qdcount; - be16_t ancount; - be16_t nscount; - be16_t arcount; -}; - -#define DNS_PACKET_HEADER_SIZE sizeof(DnsPacketHeader) -#define UDP_PACKET_HEADER_SIZE (sizeof(struct iphdr) + sizeof(struct udphdr)) - -/* The various DNS protocols deviate in how large a packet can grow, - but the TCP transport has a 16bit size field, hence that appears to - be the absolute maximum. */ -#define DNS_PACKET_SIZE_MAX 0xFFFF - -/* RFC 1035 say 512 is the maximum, for classic unicast DNS */ -#define DNS_PACKET_UNICAST_SIZE_MAX 512 - -/* With EDNS0 we can use larger packets, default to 4096, which is what is commonly used */ -#define DNS_PACKET_UNICAST_SIZE_LARGE_MAX 4096 - -#define DNS_PACKET_SIZE_START 512 - -struct DnsPacket { - int n_ref; - DnsProtocol protocol; - 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; - DnsAnswer *answer; - DnsResourceRecord *opt; - - /* Packet reception metadata */ - int ifindex; - int family, ipproto; - union in_addr_union sender, destination; - uint16_t sender_port, destination_port; - uint32_t ttl; - - /* For support of truncated packets */ - DnsPacket *more; - - bool on_stack:1; - bool extracted:1; - bool refuse_compression:1; - bool canonical_form:1; -}; - -static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) { - if (_unlikely_(!p)) - return NULL; - - if (p->_data) - return p->_data; - - return ((uint8_t*) p) + ALIGN(sizeof(DnsPacket)); -} - -#define DNS_PACKET_HEADER(p) ((DnsPacketHeader*) DNS_PACKET_DATA(p)) -#define DNS_PACKET_ID(p) DNS_PACKET_HEADER(p)->id -#define DNS_PACKET_QR(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 15) & 1) -#define DNS_PACKET_OPCODE(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 11) & 15) -#define DNS_PACKET_AA(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 10) & 1) -#define DNS_PACKET_TC(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 9) & 1) -#define DNS_PACKET_RD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 8) & 1) -#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) - -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) -#define DNS_PACKET_LLMNR_T(p) DNS_PACKET_RD(p) - -#define DNS_PACKET_QDCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->qdcount) -#define DNS_PACKET_ANCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->ancount) -#define DNS_PACKET_NSCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->nscount) -#define DNS_PACKET_ARCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->arcount) - -#define DNS_PACKET_MAKE_FLAGS(qr, opcode, aa, tc, rd, ra, ad, cd, rcode) \ - (((uint16_t) !!(qr) << 15) | \ - ((uint16_t) ((opcode) & 15) << 11) | \ - ((uint16_t) !!(aa) << 10) | /* on LLMNR: c */ \ - ((uint16_t) !!(tc) << 9) | \ - ((uint16_t) !!(rd) << 8) | /* on LLMNR: t */ \ - ((uint16_t) !!(ra) << 7) | \ - ((uint16_t) !!(ad) << 5) | \ - ((uint16_t) !!(cd) << 4) | \ - ((uint16_t) ((rcode) & 15))) - -static inline unsigned DNS_PACKET_RRCOUNT(DnsPacket *p) { - return - (unsigned) DNS_PACKET_ANCOUNT(p) + - (unsigned) DNS_PACKET_NSCOUNT(p) + - (unsigned) DNS_PACKET_ARCOUNT(p); -} - -int dns_packet_new(DnsPacket **p, DnsProtocol protocol, size_t mtu); -int dns_packet_new_query(DnsPacket **p, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled); - -void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated); - -DnsPacket *dns_packet_ref(DnsPacket *p); -DnsPacket *dns_packet_unref(DnsPacket *p); - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsPacket*, dns_packet_unref); - -int dns_packet_validate(DnsPacket *p); -int dns_packet_validate_reply(DnsPacket *p); -int dns_packet_validate_query(DnsPacket *p); - -int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key); - -int dns_packet_append_blob(DnsPacket *p, const void *d, size_t sz, size_t *start); -int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start); -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, 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(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); -int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start); -int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start); -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, 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); - -int dns_packet_skip_question(DnsPacket *p); -int dns_packet_extract(DnsPacket *p); - -static inline bool DNS_PACKET_SHALL_CACHE(DnsPacket *p) { - /* Never cache data originating from localhost, under the - * assumption, that it's coming from a locally DNS forwarder - * or server, that is caching on its own. */ - - 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, - DNS_RCODE_SERVFAIL = 2, - DNS_RCODE_NXDOMAIN = 3, - DNS_RCODE_NOTIMP = 4, - DNS_RCODE_REFUSED = 5, - DNS_RCODE_YXDOMAIN = 6, - DNS_RCODE_YXRRSET = 7, - DNS_RCODE_NXRRSET = 8, - DNS_RCODE_NOTAUTH = 9, - DNS_RCODE_NOTZONE = 10, - DNS_RCODE_BADVERS = 16, - DNS_RCODE_BADSIG = 16, /* duplicate value! */ - DNS_RCODE_BADKEY = 17, - DNS_RCODE_BADTIME = 18, - DNS_RCODE_BADMODE = 19, - DNS_RCODE_BADNAME = 20, - DNS_RCODE_BADALG = 21, - DNS_RCODE_BADTRUNC = 22, - _DNS_RCODE_MAX_DEFINED -}; - -const char* dns_rcode_to_string(int i) _const_; -int dns_rcode_from_string(const char *s) _pure_; - -const char* dns_protocol_to_string(DnsProtocol p) _const_; -DnsProtocol dns_protocol_from_string(const char *s) _pure_; - -#define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) }) -#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, 0x00, 0x00, 0xfb } }) - -static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family, bool authenticated) { - uint64_t f; - - /* Converts a protocol + family into a flags field as used in queries and responses */ - - f = authenticated ? SD_RESOLVED_AUTHENTICATED : 0; - - switch (protocol) { - case DNS_PROTOCOL_DNS: - return f|SD_RESOLVED_DNS; - - case DNS_PROTOCOL_LLMNR: - return f|(family == AF_INET6 ? SD_RESOLVED_LLMNR_IPV6 : SD_RESOLVED_LLMNR_IPV4); - - case DNS_PROTOCOL_MDNS: - return family == AF_INET6 ? SD_RESOLVED_MDNS_IPV6 : SD_RESOLVED_MDNS_IPV4; - - default: - break; - } - - return 0; -} diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c deleted file mode 100644 index a378b2b7f7..0000000000 --- a/src/resolve/resolved-dns-query.c +++ /dev/null @@ -1,1114 +0,0 @@ -/*** - 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 . -***/ - -#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) - -#define CNAME_MAX 8 -#define QUERIES_MAX 2048 -#define AUXILIARY_QUERIES_MAX 64 - -static int dns_query_candidate_new(DnsQueryCandidate **ret, DnsQuery *q, DnsScope *s) { - DnsQueryCandidate *c; - - assert(ret); - assert(q); - assert(s); - - c = new0(DnsQueryCandidate, 1); - if (!c) - return -ENOMEM; - - c->query = q; - c->scope = s; - - LIST_PREPEND(candidates_by_query, q->candidates, c); - LIST_PREPEND(candidates_by_scope, s->query_candidates, c); - - *ret = c; - return 0; -} - -static void dns_query_candidate_stop(DnsQueryCandidate *c) { - DnsTransaction *t; - - assert(c); - - while ((t = set_steal_first(c->transactions))) { - set_remove(t->notify_query_candidates, c); - dns_transaction_gc(t); - } -} - -DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c) { - - if (!c) - return NULL; - - dns_query_candidate_stop(c); - - set_free(c->transactions); - dns_search_domain_unref(c->search_domain); - - if (c->query) - LIST_REMOVE(candidates_by_query, c->query->candidates, c); - - if (c->scope) - LIST_REMOVE(candidates_by_scope, c->scope->query_candidates, c); - - free(c); - - return NULL; -} - -static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) { - DnsSearchDomain *next = NULL; - - assert(c); - - 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; - - if (!next->route_only) - break; - - /* Skip over route-only domains */ - next = next->domains_next; - } - - dns_search_domain_unref(c->search_domain); - c->search_domain = dns_search_domain_ref(next); - - return 1; -} - -static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResourceKey *key) { - DnsTransaction *t; - int r; - - assert(c); - assert(key); - - t = dns_scope_find_transaction(c->scope, key, true); - if (!t) { - r = dns_transaction_new(&t, c->scope, key); - if (r < 0) - return r; - } else { - if (set_contains(c->transactions, t)) - return 0; - } - - r = set_ensure_allocated(&c->transactions, NULL); - if (r < 0) - goto gc; - - r = set_ensure_allocated(&t->notify_query_candidates, NULL); - if (r < 0) - goto gc; - - r = set_put(t->notify_query_candidates, c); - if (r < 0) - goto gc; - - r = set_put(c->transactions, t); - if (r < 0) { - (void) set_remove(t->notify_query_candidates, c); - goto gc; - } - - return 1; - -gc: - dns_transaction_gc(t); - return r; -} - -static int dns_query_candidate_go(DnsQueryCandidate *c) { - DnsTransaction *t; - Iterator i; - int r; - unsigned n = 0; - - assert(c); - - /* Start the transactions that are not started yet */ - SET_FOREACH(t, c->transactions, i) { - if (t->state != DNS_TRANSACTION_NULL) - continue; - - 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; -} - -static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { - DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; - DnsTransaction *t; - Iterator i; - - assert(c); - - if (c->error_code != 0) - 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 - * VALIDATING state, then this means there's - * also one in PENDING state, hence we can - * return PENDING immediately. */ - return DNS_TRANSACTION_PENDING; - - case DNS_TRANSACTION_SUCCESS: - state = t->state; - break; - - default: - if (state != DNS_TRANSACTION_SUCCESS) - state = t->state; - - break; - } - } - - 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; - - assert(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, 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; - - 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; - - n++; - } - - return n; - -fail: - dns_query_candidate_stop(c); - return r; -} - -void dns_query_candidate_notify(DnsQueryCandidate *c) { - DnsTransactionState state; - int r; - - assert(c); - - state = dns_query_candidate_state(c); - - if (DNS_TRANSACTION_IS_LIVE(state)) - return; - - if (state != DNS_TRANSACTION_SUCCESS && c->search_domain) { - - r = dns_query_candidate_next_search_domain(c); - if (r < 0) - goto fail; - - if (r > 0) { - /* OK, there's another search domain to try, let's do so. */ - - r = dns_query_candidate_setup_transactions(c); - if (r < 0) - goto fail; - - if (r > 0) { - /* New transactions where queued. Start them and wait */ - - r = dns_query_candidate_go(c); - if (r < 0) - goto fail; - - return; - } - } - - } - - dns_query_ready(c->query); - return; - -fail: - log_warning_errno(r, "Failed to follow search domains: %m"); - c->error_code = r; - dns_query_ready(c->query); -} - -static void dns_query_stop(DnsQuery *q) { - DnsQueryCandidate *c; - - assert(q); - - q->timeout_event_source = sd_event_source_unref(q->timeout_event_source); - - LIST_FOREACH(candidates_by_query, c, q->candidates) - 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; - - while (q->auxiliary_queries) - dns_query_free(q->auxiliary_queries); - - if (q->auxiliary_for) { - assert(q->auxiliary_for->n_auxiliary_queries > 0); - q->auxiliary_for->n_auxiliary_queries--; - LIST_REMOVE(auxiliary_queries, q->auxiliary_for->auxiliary_queries, q); - } - - dns_query_free_candidates(q); - - 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--; - } - - free(q); - - return NULL; -} - -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; - DnsResourceKey *key; - bool good = false; - int r; - - assert(m); - - 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; - - q = new0(DnsQuery, 1); - if (!q) - return -ENOMEM; - - q->question_utf8 = dns_question_ref(question_utf8); - q->question_idna = dns_question_ref(question_idna); - q->ifindex = ifindex; - q->flags = flags; - 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; - - r = dns_question_contains(question_utf8, key); - if (r < 0) - return r; - if (r > 0) - continue; - - r = dns_resource_key_to_string(key, &p); - if (r < 0) - return r; - - log_debug("Looking up IDNA RR for %s.", strstrip(p)); - } - - LIST_PREPEND(queries, m->dns_queries, q); - m->n_dns_queries++; - q->manager = m; - - if (ret) - *ret = q; - q = NULL; - - return 0; -} - -int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for) { - assert(q); - assert(auxiliary_for); - - /* Ensure that that the query is not auxiliary yet, and - * nothing else is auxiliary to it either */ - assert(!q->auxiliary_for); - assert(!q->auxiliary_queries); - - /* Ensure that the unit we shall be made auxiliary for isn't - * auxiliary itself */ - assert(!auxiliary_for->auxiliary_for); - - if (auxiliary_for->n_auxiliary_queries >= AUXILIARY_QUERIES_MAX) - return -EAGAIN; - - LIST_PREPEND(auxiliary_queries, auxiliary_for->auxiliary_queries, q); - q->auxiliary_for = auxiliary_for; - - auxiliary_for->n_auxiliary_queries++; - return 0; -} - -static void dns_query_complete(DnsQuery *q, DnsTransactionState state) { - assert(q); - assert(!DNS_TRANSACTION_IS_LIVE(state)); - assert(DNS_TRANSACTION_IS_LIVE(q->state)); - - /* Note that this call might invalidate the query. Callers - * should hence not attempt to access the query or transaction - * after calling this function. */ - - q->state = state; - - dns_query_stop(q); - if (q->complete) - q->complete(q); -} - -static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) { - DnsQuery *q = userdata; - - assert(s); - assert(q); - - dns_query_complete(q, DNS_TRANSACTION_TIMEOUT); - return 0; -} - -static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) { - DnsQueryCandidate *c; - int r; - - assert(q); - assert(s); - - r = dns_query_candidate_new(&c, q, s); - if (r < 0) - return r; - - /* 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_idna)); - if (r < 0) - goto fail; - if (r > 0) { - /* OK, we need a search domain now. Let's find one for this scope */ - - r = dns_query_candidate_next_search_domain(c); - if (r <= 0) /* if there's no search domain, then we won't add any transaction. */ - goto fail; - } - } - - r = dns_query_candidate_setup_transactions(c); - if (r < 0) - goto fail; - - return 0; - -fail: - dns_query_candidate_free(c); - return r; -} - -static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - int r; - - assert(q); - assert(state); - - /* 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_RCODE_FAILURE, - DNS_TRANSACTION_NO_SERVERS, - DNS_TRANSACTION_TIMEOUT, - DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, - DNS_TRANSACTION_NETWORK_DOWN, - DNS_TRANSACTION_NOT_FOUND)) - return 0; - - r = dns_synthesize_answer( - q->manager, - q->question_utf8, - q->ifindex, - &answer); - - if (r <= 0) - return r; - - dns_query_reset_answer(q); - - 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; - - *state = DNS_TRANSACTION_SUCCESS; - - return 1; -} - -static int dns_query_try_etc_hosts(DnsQuery *q) { - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - int r; - - assert(q); - - /* 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 = manager_etc_hosts_lookup( - q->manager, - q->question_utf8, - &answer); - if (r <= 0) - return r; - - dns_query_reset_answer(q); - - 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; - - return 1; -} - -int dns_query_go(DnsQuery *q) { - DnsScopeMatch found = DNS_SCOPE_NO; - DnsScope *s, *first = NULL; - DnsQueryCandidate *c; - int r; - - assert(q); - - if (q->state != DNS_TRANSACTION_NULL) - return 0; - - 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) - return match; - - if (match == DNS_SCOPE_NO) - continue; - - found = match; - - if (match == DNS_SCOPE_YES) { - first = s; - break; - } else { - assert(match == DNS_SCOPE_MAYBE); - - if (!first) - first = s; - } - } - - if (found == DNS_SCOPE_NO) { - DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; - - r = dns_query_synthesize_reply(q, &state); - if (r < 0) - return r; - - dns_query_complete(q, state); - return 1; - } - - r = dns_query_add_candidate(q, first); - if (r < 0) - goto fail; - - 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) - goto fail; - - if (match != found) - continue; - - r = dns_query_add_candidate(q, s); - if (r < 0) - goto fail; - } - - dns_query_reset_answer(q); - - r = sd_event_add_time( - q->manager->event, - &q->timeout_event_source, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + QUERY_TIMEOUT_USEC, 0, - on_query_timeout, 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++; - - /* Start the transactions */ - LIST_FOREACH(candidates_by_query, c, q->candidates) { - r = dns_query_candidate_go(c); - if (r < 0) { - q->block_ready--; - goto fail; - } - } - - q->block_ready--; - dns_query_ready(q); - - return 1; - -fail: - dns_query_stop(q); - return r; -} - -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; - - assert(q); - - if (!c) { - 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) { - - 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) - goto fail; - - q->answer_rcode = t->answer_rcode; - q->answer_errno = 0; - - if (t->answer_authenticated) { - has_authenticated = true; - 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; - } - - case DNS_TRANSACTION_NULL: - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - case DNS_TRANSACTION_ABORTED: - /* Ignore transactions that didn't complete */ - continue; - - default: - /* Any kind of failure? Store the data away, - * if there's nothing stored yet. */ - - if (state == DNS_TRANSACTION_SUCCESS) - continue; - - 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; - - dns_search_domain_unref(q->answer_search_domain); - q->answer_search_domain = dns_search_domain_ref(c->search_domain); - - 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) { - - DnsQueryCandidate *bad = NULL, *c; - bool pending = false; - - assert(q); - assert(DNS_TRANSACTION_IS_LIVE(q->state)); - - /* Note that this call might invalidate the query. Callers - * should hence not attempt to access the query or transaction - * after calling this function, unless the block_ready - * counter was explicitly bumped before doing so. */ - - if (q->block_ready > 0) - return; - - LIST_FOREACH(candidates_by_query, c, q->candidates) { - DnsTransactionState state; - - state = dns_query_candidate_state(c); - switch (state) { - - case DNS_TRANSACTION_SUCCESS: - /* One of the candidates is successful, - * let's use it, and copy its data out */ - dns_query_accept(q, c); - return; - - case DNS_TRANSACTION_NULL: - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - /* One of the candidates is still going on, - * let's maybe wait for it */ - pending = true; - break; - - default: - /* Any kind of failure */ - bad = c; - break; - } - } - - if (pending) - return; - - dns_query_accept(q, bad); -} - -static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) { - _cleanup_(dns_question_unrefp) DnsQuestion *nq_idna = NULL, *nq_utf8 = NULL; - int r, k; - - assert(q); - - q->n_cname_redirects ++; - if (q->n_cname_redirects > CNAME_MAX) - return -ELOOP; - - 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)); - - 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); - - q->state = DNS_TRANSACTION_NULL; - - return 0; -} - -int dns_query_process_cname(DnsQuery *q) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL; - DnsQuestion *question; - DnsResourceRecord *rr; - int r; - - assert(q); - - if (!IN_SET(q->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_NULL)) - return DNS_QUERY_NOMATCH; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); - if (r < 0) - return r; - if (r > 0) - return DNS_QUERY_MATCH; /* The answer matches directly, no need to follow cnames */ - - r = dns_question_matches_cname_or_dname(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); - if (r < 0) - return r; - if (r > 0 && !cname) - cname = dns_resource_record_ref(rr); - } - - if (!cname) - return DNS_QUERY_NOMATCH; /* No match and no cname to follow */ - - if (q->flags & SD_RESOLVED_NO_CNAME) - return -ELOOP; - - /* OK, let's actually follow the CNAME */ - r = dns_query_cname_redirect(q, cname); - if (r < 0) - return r; - - /* Let's see if the answer can already answer the new - * redirected question */ - 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 DNS_QUERY_RESTARTED; /* We restarted the query for a new cname */ -} - -static int on_bus_track(sd_bus_track *t, void *userdata) { - DnsQuery *q = userdata; - - assert(t); - assert(q); - - log_debug("Client of active query vanished, aborting query."); - dns_query_complete(q, DNS_TRANSACTION_ABORTED); - return 0; -} - -int dns_query_bus_track(DnsQuery *q, sd_bus_message *m) { - int r; - - assert(q); - assert(m); - - if (!q->bus_track) { - r = sd_bus_track_new(sd_bus_message_get_bus(m), &q->bus_track, on_bus_track, q); - if (r < 0) - return r; - } - - r = sd_bus_track_add_sender(q->bus_track, m); - if (r < 0) - return r; - - 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 deleted file mode 100644 index c2ac02f68b..0000000000 --- a/src/resolve/resolved-dns-query.h +++ /dev/null @@ -1,133 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - - -#include "sd-bus.h" - -#include "set.h" - -typedef struct DnsQueryCandidate DnsQueryCandidate; -typedef struct DnsQuery DnsQuery; - -#include "resolved-dns-answer.h" -#include "resolved-dns-question.h" -#include "resolved-dns-stream.h" -#include "resolved-dns-search-domain.h" - -struct DnsQueryCandidate { - DnsQuery *query; - DnsScope *scope; - - DnsSearchDomain *search_domain; - - int error_code; - Set *transactions; - - LIST_FIELDS(DnsQueryCandidate, candidates_by_query); - LIST_FIELDS(DnsQueryCandidate, candidates_by_scope); -}; - -struct DnsQuery { - Manager *manager; - - /* When resolving a service, we first create a TXT+SRV query, - * and then for the hostnames we discover auxiliary A+AAAA - * queries. This pointer always points from the auxiliary - * queries back to the TXT+SRV query. */ - DnsQuery *auxiliary_for; - LIST_HEAD(DnsQuery, auxiliary_queries); - unsigned n_auxiliary_queries; - int auxiliary_result; - - /* 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; - - LIST_HEAD(DnsQueryCandidate, candidates); - sd_event_source *timeout_event_source; - - /* Discovered data */ - DnsAnswer *answer; - int answer_rcode; - DnssecResult answer_dnssec_result; - bool answer_authenticated; - DnsProtocol answer_protocol; - int answer_family; - DnsSearchDomain *answer_search_domain; - int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ - - /* Bus client information */ - sd_bus_message *request; - int request_family; - bool request_address_valid; - union in_addr_union request_address; - unsigned block_all_complete; - char *request_address_string; - - /* Completion callback */ - void (*complete)(DnsQuery* q); - unsigned block_ready; - - sd_bus_track *bus_track; - - LIST_FIELDS(DnsQuery, queries); - 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_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); - -int dns_query_go(DnsQuery *q); -void dns_query_ready(DnsQuery *q); - -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 deleted file mode 100644 index 8e452e79a4..0000000000 --- a/src/resolve/resolved-dns-question.c +++ /dev/null @@ -1,468 +0,0 @@ -/*** - 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 . -***/ - -#include "alloc-util.h" -#include "dns-domain.h" -#include "dns-type.h" -#include "resolved-dns-question.h" - -DnsQuestion *dns_question_new(unsigned n) { - DnsQuestion *q; - - assert(n > 0); - - q = malloc0(offsetof(DnsQuestion, keys) + sizeof(DnsResourceKey*) * n); - if (!q) - return NULL; - - q->n_ref = 1; - q->n_allocated = n; - - return q; -} - -DnsQuestion *dns_question_ref(DnsQuestion *q) { - if (!q) - return NULL; - - assert(q->n_ref > 0); - q->n_ref++; - return q; -} - -DnsQuestion *dns_question_unref(DnsQuestion *q) { - if (!q) - return NULL; - - assert(q->n_ref > 0); - - if (q->n_ref == 1) { - unsigned i; - - for (i = 0; i < q->n_keys; i++) - dns_resource_key_unref(q->keys[i]); - free(q); - } else - q->n_ref--; - - return NULL; -} - -int dns_question_add(DnsQuestion *q, DnsResourceKey *key) { - unsigned i; - int r; - - assert(key); - - if (!q) - return -ENOSPC; - - for (i = 0; i < q->n_keys; i++) { - r = dns_resource_key_equal(q->keys[i], key); - if (r < 0) - return r; - if (r > 0) - return 0; - } - - if (q->n_keys >= q->n_allocated) - return -ENOSPC; - - q->keys[q->n_keys++] = dns_resource_key_ref(key); - return 0; -} - -int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { - unsigned i; - int r; - - assert(rr); - - if (!q) - return 0; - - for (i = 0; i < q->n_keys; i++) { - r = dns_resource_key_match_rr(q->keys[i], rr, search_domain); - if (r != 0) - return r; - } - - return 0; -} - -int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { - unsigned i; - int r; - - assert(rr); - - 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; - } - - return 0; -} - -int dns_question_is_valid_for_query(DnsQuestion *q) { - const char *name; - unsigned i; - int r; - - if (!q) - return 0; - - if (q->n_keys <= 0) - return 0; - - if (q->n_keys > 65535) - return 0; - - name = DNS_RESOURCE_KEY_NAME(q->keys[0]); - if (!name) - return 0; - - /* Check that all keys in this question bear the same name */ - for (i = 0; i < q->n_keys; i++) { - assert(q->keys[i]); - - 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, const DnsResourceKey *k) { - unsigned j; - int r; - - assert(k); - - if (!a) - return 0; - - for (j = 0; j < a->n_keys; j++) { - r = dns_resource_key_equal(a->keys[j], k); - if (r != 0) - return r; - } - - return 0; -} - -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) - return a->n_keys == 0; - - /* Checks if all keys in a are also contained b, and vice versa */ - - for (j = 0; j < a->n_keys; j++) { - r = dns_question_contains(b, a->keys[j]); - if (r <= 0) - return r; - } - - for (j = 0; j < b->n_keys; j++) { - r = dns_question_contains(a, b->keys[j]); - if (r <= 0) - return r; - } - - return 1; -} - -int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret) { - _cleanup_(dns_question_unrefp) DnsQuestion *n = NULL; - DnsResourceKey *key; - bool same = true; - int r; - - assert(cname); - assert(ret); - assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); - - if (dns_question_size(q) <= 0) { - *ret = NULL; - return 0; - } - - 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(key), DNS_RESOURCE_KEY_NAME(cname->key), cname->dname.name, &destination); - if (r < 0) - return r; - if (r == 0) - continue; - - d = destination; - } - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), d); - if (r < 0) - return r; - - if (r == 0) { - same = false; - break; - } - } - - /* Fully the same, indicate we didn't do a thing */ - if (same) { - *ret = NULL; - return 0; - } - - n = dns_question_new(q->n_keys); - if (!n) - return -ENOMEM; - - /* Create a new question, and patch in the new name */ - DNS_QUESTION_FOREACH(key, q) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; - - k = dns_resource_key_new_redirect(key, cname); - if (!k) - return -ENOMEM; - - r = dns_question_add(n, k); - if (r < 0) - return r; - } - - *ret = n; - n = NULL; - - return 1; -} - -const char *dns_question_first_name(DnsQuestion *q) { - - if (!q) - return NULL; - - if (q->n_keys < 1) - return NULL; - - return DNS_RESOURCE_KEY_NAME(q->keys[0]); -} - -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); - assert(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; - - if (family != AF_INET6) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(q, key); - if (r < 0) - return r; - } - - if (family != AF_INET) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(q, key); - if (r < 0) - return r; - } - - *ret = q; - q = NULL; - - return 0; -} - -int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; - _cleanup_free_ char *reverse = NULL; - int r; - - assert(ret); - assert(a); - - if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) - return -EAFNOSUPPORT; - - r = dns_name_reverse(family, a, &reverse); - if (r < 0) - return r; - - q = dns_question_new(1); - if (!q) - return -ENOMEM; - - key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, reverse); - if (!key) - return -ENOMEM; - - reverse = NULL; - - r = dns_question_add(q, key); - if (r < 0) - return r; - - *ret = q; - q = NULL; - - return 0; -} - -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); - - /* 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) - return -ENOMEM; - - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_SRV, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(q, key); - if (r < 0) - return r; - - if (with_txt) { - dns_resource_key_unref(key); - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_TXT, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(q, key); - if (r < 0) - return r; - } - - *ret = q; - q = NULL; - - return 0; -} diff --git a/src/resolve/resolved-dns-question.h b/src/resolve/resolved-dns-question.h deleted file mode 100644 index ea41478975..0000000000 --- a/src/resolve/resolved-dns-question.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -typedef struct DnsQuestion DnsQuestion; - -#include "macro.h" -#include "resolved-dns-rr.h" - -/* A simple array of resource keys */ - -struct DnsQuestion { - unsigned n_ref; - unsigned n_keys, n_allocated; - DnsResourceKey* keys[0]; -}; - -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, 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 *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_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, 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) \ - for (unsigned UNIQ_T(i, u) = ({ \ - (key) = ((q) && (q)->n_keys > 0) ? (q)->keys[0] : NULL; \ - 0; \ - }); \ - (q) && (UNIQ_T(i, u) < (q)->n_keys); \ - UNIQ_T(i, u)++, (key) = (UNIQ_T(i, u) < (q)->n_keys ? (q)->keys[UNIQ_T(i, u)] : NULL)) - -#define DNS_QUESTION_FOREACH(key, q) _DNS_QUESTION_FOREACH(UNIQ, key, q) diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c deleted file mode 100644 index 40f8e28dfd..0000000000 --- a/src/resolve/resolved-dns-rr.c +++ /dev/null @@ -1,1521 +0,0 @@ -/*** - 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 . -***/ - -#include - -#include "alloc-util.h" -#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; - size_t l; - - assert(name); - - l = strlen(name); - k = malloc0(sizeof(DnsResourceKey) + l + 1); - if (!k) - return NULL; - - k->n_ref = 1; - k->class = class; - k->type = type; - - strcpy((char*) k + sizeof(DnsResourceKey), name); - - return k; -} - -DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname) { - int r; - - assert(key); - assert(cname); - - assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); - - if (cname->key->type == DNS_TYPE_CNAME) - return dns_resource_key_new(key->class, key->type, cname->cname.name); - else { - DnsResourceKey *k; - char *destination = NULL; - - r = dns_name_change_suffix(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname->key), cname->dname.name, &destination); - if (r < 0) - return NULL; - if (r == 0) - return dns_resource_key_ref((DnsResourceKey*) key); - - k = dns_resource_key_new_consume(key->class, key->type, destination); - if (!k) { - free(destination); - return NULL; - } - - return k; - } -} - -int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name) { - DnsResourceKey *new_key; - char *joined; - int r; - - assert(ret); - assert(key); - assert(name); - - if (dns_name_is_root(name)) { - *ret = dns_resource_key_ref(key); - return 0; - } - - r = dns_name_concat(DNS_RESOURCE_KEY_NAME(key), name, &joined); - if (r < 0) - return r; - - new_key = dns_resource_key_new_consume(key->class, key->type, joined); - if (!new_key) { - free(joined); - return -ENOMEM; - } - - *ret = new_key; - return 0; -} - -DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name) { - DnsResourceKey *k; - - assert(name); - - k = new0(DnsResourceKey, 1); - if (!k) - return NULL; - - k->n_ref = 1; - k->class = class; - k->type = type; - k->_name = name; - - return k; -} - -DnsResourceKey* dns_resource_key_ref(DnsResourceKey *k) { - - if (!k) - return NULL; - - /* Static/const keys created with DNS_RESOURCE_KEY_CONST will - * set this to -1, they should not be reffed/unreffed */ - assert(k->n_ref != (unsigned) -1); - - assert(k->n_ref > 0); - k->n_ref++; - - return k; -} - -DnsResourceKey* dns_resource_key_unref(DnsResourceKey *k) { - if (!k) - return NULL; - - assert(k->n_ref != (unsigned) -1); - assert(k->n_ref > 0); - - if (k->n_ref == 1) { - free(k->_name); - free(k); - } else - k->n_ref--; - - return NULL; -} - -bool dns_resource_key_is_address(const DnsResourceKey *key) { - assert(key); - - /* Check if this is an A or AAAA resource key */ - - return key->class == DNS_CLASS_IN && IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_AAAA); -} - -int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) { - int r; - - if (a == b) - return 1; - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(a), DNS_RESOURCE_KEY_NAME(b)); - if (r <= 0) - return r; - - if (a->class != b->class) - return 0; - - if (a->type != b->type) - return 0; - - return 1; -} - -int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain) { - int r; - - assert(key); - assert(rr); - - if (key == rr->key) - return 1; - - /* Checks if an rr matches the specified key. If a search - * domain is specified, it will also be checked if the key - * with the search domain suffixed might match the RR. */ - - if (rr->key->class != key->class && key->class != DNS_CLASS_ANY) - return 0; - - if (rr->key->type != key->type && key->type != DNS_TYPE_ANY) - return 0; - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key)); - if (r != 0) - return r; - - if (search_domain) { - _cleanup_free_ char *joined = NULL; - - r = dns_name_concat(DNS_RESOURCE_KEY_NAME(key), search_domain, &joined); - if (r < 0) - return r; - - return dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), joined); - } - - return 0; -} - -int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain) { - int r; - - assert(key); - assert(cname); - - if (cname->class != key->class && key->class != DNS_CLASS_ANY) - return 0; - - if (cname->type == DNS_TYPE_CNAME) - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname)); - else if (cname->type == DNS_TYPE_DNAME) - r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname)); - else - return 0; - - if (r != 0) - return r; - - if (search_domain) { - _cleanup_free_ char *joined = NULL; - - r = dns_name_concat(DNS_RESOURCE_KEY_NAME(key), search_domain, &joined); - if (r < 0) - return r; - - if (cname->type == DNS_TYPE_CNAME) - return dns_name_equal(joined, DNS_RESOURCE_KEY_NAME(cname)); - else if (cname->type == DNS_TYPE_DNAME) - return dns_name_endswith(joined, DNS_RESOURCE_KEY_NAME(cname)); - } - - return 0; -} - -int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa) { - assert(soa); - assert(key); - - /* Checks whether 'soa' is a SOA record for the specified key. */ - - if (soa->class != key->class) - return 0; - - if (soa->type != DNS_TYPE_SOA) - return 0; - - 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) { - const DnsResourceKey *k = i; - - assert(k); - - dns_name_hash_func(DNS_RESOURCE_KEY_NAME(k), state); - siphash24_compress(&k->class, sizeof(k->class), state); - siphash24_compress(&k->type, sizeof(k->type), state); -} - -static int dns_resource_key_compare_func(const void *a, const void *b) { - const DnsResourceKey *x = a, *y = b; - int ret; - - ret = dns_name_compare_func(DNS_RESOURCE_KEY_NAME(x), DNS_RESOURCE_KEY_NAME(y)); - if (ret != 0) - return ret; - - if (x->type < y->type) - return -1; - if (x->type > y->type) - return 1; - - if (x->class < y->class) - return -1; - if (x->class > y->class) - return 1; - - return 0; -} - -const struct hash_ops dns_resource_key_hash_ops = { - .hash = dns_resource_key_hash_func, - .compare = dns_resource_key_compare_func -}; - -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, *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); - c = cbuf; - } - - t = dns_type_to_string(key->type); - if (!t){ - sprintf(tbuf, "TYPE%u", key->type); - t = tbuf; - } - - 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; - - rr = new0(DnsResourceRecord, 1); - if (!rr) - return NULL; - - 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; -} - -DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(class, type, name); - if (!key) - return NULL; - - return dns_resource_record_new(key); -} - -DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr) { - if (!rr) - return NULL; - - assert(rr->n_ref > 0); - rr->n_ref++; - - return rr; -} - -DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) { - if (!rr) - return NULL; - - assert(rr->n_ref > 0); - - if (rr->n_ref > 1) { - rr->n_ref--; - return NULL; - } - - if (rr->key) { - switch(rr->key->type) { - - case DNS_TYPE_SRV: - free(rr->srv.name); - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - free(rr->ptr.name); - break; - - case DNS_TYPE_HINFO: - free(rr->hinfo.cpu); - free(rr->hinfo.os); - break; - - case DNS_TYPE_TXT: - case DNS_TYPE_SPF: - dns_txt_item_free_all(rr->txt.items); - break; - - case DNS_TYPE_SOA: - free(rr->soa.mname); - free(rr->soa.rname); - break; - - case DNS_TYPE_MX: - free(rr->mx.exchange); - break; - - case DNS_TYPE_DS: - free(rr->ds.digest); - break; - - case DNS_TYPE_SSHFP: - free(rr->sshfp.fingerprint); - break; - - case DNS_TYPE_DNSKEY: - free(rr->dnskey.key); - break; - - case DNS_TYPE_RRSIG: - free(rr->rrsig.signer); - free(rr->rrsig.signature); - break; - - case DNS_TYPE_NSEC: - free(rr->nsec.next_domain_name); - bitmap_free(rr->nsec.types); - break; - - case DNS_TYPE_NSEC3: - free(rr->nsec3.next_hashed_name); - free(rr->nsec3.salt); - bitmap_free(rr->nsec3.types); - break; - - case DNS_TYPE_LOC: - case DNS_TYPE_A: - case DNS_TYPE_AAAA: - break; - - case DNS_TYPE_TLSA: - free(rr->tlsa.data); - break; - - case DNS_TYPE_OPENPGPKEY: - default: - free(rr->generic.data); - } - - free(rr->wire_format); - dns_resource_key_unref(rr->key); - } - - free(rr->to_string); - free(rr); - - return NULL; -} - -int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *hostname) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_free_ char *ptr = NULL; - int r; - - assert(ret); - assert(address); - assert(hostname); - - r = dns_name_reverse(family, address, &ptr); - if (r < 0) - return r; - - key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, ptr); - if (!key) - return -ENOMEM; - - ptr = NULL; - - rr = dns_resource_record_new(key); - if (!rr) - return -ENOMEM; - - rr->ptr.name = strdup(hostname); - if (!rr->ptr.name) - return -ENOMEM; - - *ret = rr; - rr = NULL; - - return 0; -} - -int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name) { - DnsResourceRecord *rr; - - assert(ret); - assert(address); - assert(family); - - if (family == AF_INET) { - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, name); - if (!rr) - return -ENOMEM; - - rr->a.in_addr = address->in; - - } else if (family == AF_INET6) { - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, name); - if (!rr) - return -ENOMEM; - - rr->aaaa.in6_addr = address->in6; - } else - return -EAFNOSUPPORT; - - *ret = rr; - - 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; - - assert(a); - assert(b); - - if (a == b) - return 1; - - r = dns_resource_key_equal(a->key, b->key); - if (r <= 0) - return r; - - if (a->unparseable != b->unparseable) - return 0; - - switch (a->unparseable ? _DNS_TYPE_INVALID : a->key->type) { - - case DNS_TYPE_SRV: - r = dns_name_equal(a->srv.name, b->srv.name); - if (r <= 0) - return r; - - return a->srv.priority == b->srv.priority && - a->srv.weight == b->srv.weight && - a->srv.port == b->srv.port; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - return dns_name_equal(a->ptr.name, b->ptr.name); - - case DNS_TYPE_HINFO: - return strcaseeq(a->hinfo.cpu, b->hinfo.cpu) && - strcaseeq(a->hinfo.os, b->hinfo.os); - - case DNS_TYPE_SPF: /* exactly the same as TXT */ - case DNS_TYPE_TXT: - return dns_txt_item_equal(a->txt.items, b->txt.items); - - case DNS_TYPE_A: - return memcmp(&a->a.in_addr, &b->a.in_addr, sizeof(struct in_addr)) == 0; - - case DNS_TYPE_AAAA: - return memcmp(&a->aaaa.in6_addr, &b->aaaa.in6_addr, sizeof(struct in6_addr)) == 0; - - case DNS_TYPE_SOA: - r = dns_name_equal(a->soa.mname, b->soa.mname); - if (r <= 0) - return r; - r = dns_name_equal(a->soa.rname, b->soa.rname); - if (r <= 0) - return r; - - return a->soa.serial == b->soa.serial && - a->soa.refresh == b->soa.refresh && - a->soa.retry == b->soa.retry && - a->soa.expire == b->soa.expire && - a->soa.minimum == b->soa.minimum; - - case DNS_TYPE_MX: - if (a->mx.priority != b->mx.priority) - return 0; - - return dns_name_equal(a->mx.exchange, b->mx.exchange); - - case DNS_TYPE_LOC: - assert(a->loc.version == b->loc.version); - - return a->loc.size == b->loc.size && - a->loc.horiz_pre == b->loc.horiz_pre && - a->loc.vert_pre == b->loc.vert_pre && - a->loc.latitude == b->loc.latitude && - a->loc.longitude == b->loc.longitude && - a->loc.altitude == b->loc.altitude; - - case DNS_TYPE_DS: - return a->ds.key_tag == b->ds.key_tag && - a->ds.algorithm == b->ds.algorithm && - a->ds.digest_type == b->ds.digest_type && - FIELD_EQUAL(a->ds, b->ds, digest); - - case DNS_TYPE_SSHFP: - return a->sshfp.algorithm == b->sshfp.algorithm && - a->sshfp.fptype == b->sshfp.fptype && - 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 && - FIELD_EQUAL(a->dnskey, b->dnskey, key); - - case DNS_TYPE_RRSIG: - /* do the fast comparisons first */ - 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) && - bitmap_equal(a->nsec.types, b->nsec.types); - - case DNS_TYPE_NSEC3: - return a->nsec3.algorithm == b->nsec3.algorithm && - 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 FIELD_EQUAL(a->generic, b->generic, data); - } -} - -static char* format_location(uint32_t latitude, uint32_t longitude, uint32_t altitude, - uint8_t size, uint8_t horiz_pre, uint8_t vert_pre) { - char *s; - char NS = latitude >= 1U<<31 ? 'N' : 'S'; - char EW = longitude >= 1U<<31 ? 'E' : 'W'; - - int lat = latitude >= 1U<<31 ? (int) (latitude - (1U<<31)) : (int) ((1U<<31) - latitude); - int lon = longitude >= 1U<<31 ? (int) (longitude - (1U<<31)) : (int) ((1U<<31) - longitude); - double alt = altitude >= 10000000u ? altitude - 10000000u : -(double)(10000000u - altitude); - double siz = (size >> 4) * exp10((double) (size & 0xF)); - double hor = (horiz_pre >> 4) * exp10((double) (horiz_pre & 0xF)); - double ver = (vert_pre >> 4) * exp10((double) (vert_pre & 0xF)); - - if (asprintf(&s, "%d %d %.3f %c %d %d %.3f %c %.2fm %.2fm %.2fm %.2fm", - (lat / 60000 / 60), - (lat / 60000) % 60, - (lat % 60000) / 1000., - NS, - (lon / 60000 / 60), - (lon / 60000) % 60, - (lon % 60000) / 1000., - EW, - alt / 100., - siz / 100., - hor / 100., - ver / 100.) < 0) - return NULL; - - return s; -} - -static int format_timestamp_dns(char *buf, size_t l, time_t sec) { - struct tm tm; - - assert(buf); - assert(l > strlen("YYYYMMDDHHmmSS")); - - if (!gmtime_r(&sec, &tm)) - return -EINVAL; - - if (strftime(buf, l, "%Y%m%d%H%M%S", &tm) <= 0) - return -EINVAL; - - return 0; -} - -static char *format_types(Bitmap *types) { - _cleanup_strv_free_ char **strv = NULL; - _cleanup_free_ char *str = NULL; - Iterator i; - unsigned type; - int r; - - BITMAP_FOREACH(type, types, i) { - if (dns_type_to_string(type)) { - r = strv_extend(&strv, dns_type_to_string(type)); - if (r < 0) - return NULL; - } else { - char *t; - - r = asprintf(&t, "TYPE%u", type); - if (r < 0) - return NULL; - - r = strv_consume(&strv, t); - if (r < 0) - return NULL; - } - } - - str = strv_join(strv, " "); - if (!str) - return NULL; - - return strjoin("( ", str, " )", NULL); -} - -static char *format_txt(DnsTxtItem *first) { - DnsTxtItem *i; - size_t c = 1; - char *p, *s; - - LIST_FOREACH(items, i, first) - c += i->length * 4 + 3; - - p = s = new(char, c); - if (!s) - return NULL; - - LIST_FOREACH(items, i, first) { - size_t j; - - if (i != first) - *(p++) = ' '; - - *(p++) = '"'; - - for (j = 0; j < i->length; j++) { - if (i->data[j] < ' ' || i->data[j] == '"' || i->data[j] >= 127) { - *(p++) = '\\'; - *(p++) = '0' + (i->data[j] / 100); - *(p++) = '0' + ((i->data[j] / 10) % 10); - *(p++) = '0' + (i->data[j] % 10); - } else - *(p++) = i->data[j]; - } - - *(p++) = '"'; - } - - *p = 0; - return s; -} - -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 NULL; - - switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { - - case DNS_TYPE_SRV: - r = asprintf(&s, "%s %u %u %u %s", - k, - rr->srv.priority, - rr->srv.weight, - rr->srv.port, - strna(rr->srv.name)); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - s = strjoin(k, " ", rr->ptr.name, NULL); - if (!s) - return NULL; - - break; - - case DNS_TYPE_HINFO: - s = strjoin(k, " ", rr->hinfo.cpu, " ", rr->hinfo.os, NULL); - if (!s) - 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 NULL; - - s = strjoin(k, " ", t, NULL); - if (!s) - return NULL; - break; - - case DNS_TYPE_A: { - _cleanup_free_ char *x = NULL; - - r = in_addr_to_string(AF_INET, (const union in_addr_union*) &rr->a.in_addr, &x); - if (r < 0) - return NULL; - - s = strjoin(k, " ", x, NULL); - if (!s) - 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 NULL; - - s = strjoin(k, " ", t, NULL); - if (!s) - return NULL; - break; - - case DNS_TYPE_SOA: - r = asprintf(&s, "%s %s %s %u %u %u %u %u", - k, - strna(rr->soa.mname), - strna(rr->soa.rname), - rr->soa.serial, - rr->soa.refresh, - rr->soa.retry, - rr->soa.expire, - rr->soa.minimum); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_MX: - r = asprintf(&s, "%s %u %s", - k, - rr->mx.priority, - rr->mx.exchange); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_LOC: - assert(rr->loc.version == 0); - - t = format_location(rr->loc.latitude, - rr->loc.longitude, - rr->loc.altitude, - rr->loc.size, - rr->loc.horiz_pre, - rr->loc.vert_pre); - if (!t) - return NULL; - - s = strjoin(k, " ", t, NULL); - if (!s) - return NULL; - break; - - case DNS_TYPE_DS: - t = hexmem(rr->ds.digest, rr->ds.digest_size); - if (!t) - return NULL; - - r = asprintf(&s, "%s %u %u %u %s", - k, - rr->ds.key_tag, - rr->ds.algorithm, - rr->ds.digest_type, - t); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_SSHFP: - t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); - if (!t) - return NULL; - - r = asprintf(&s, "%s %u %u %s", - k, - rr->sshfp.algorithm, - rr->sshfp.fptype, - t); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_DNSKEY: { - _cleanup_free_ char *alg = NULL; - char *ss; - int n, n1; - uint16_t key_tag; - - key_tag = dnssec_keytag(rr, true); - - r = dnssec_algorithm_to_string_alloc(rr->dnskey.algorithm, &alg); - if (r < 0) - return NULL; - - r = asprintf(&s, "%s %n%u %u %s %n", - k, - &n1, - rr->dnskey.flags, - rr->dnskey.protocol, - alg, - &n); - if (r < 0) - 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: { - _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); - - 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 NULL; - - r = format_timestamp_dns(inception, sizeof(inception), rr->rrsig.inception); - if (r < 0) - return NULL; - - /* TYPE?? follows - * http://tools.ietf.org/html/rfc3597#section-5 */ - - 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, - rr->rrsig.labels, - rr->rrsig.original_ttl, - expiration, - inception, - rr->rrsig.key_tag, - rr->rrsig.signer, - &n); - if (r < 0) - 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 NULL; - - r = asprintf(&s, "%s %s %s", - k, - rr->nsec.next_domain_name, - t); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_NSEC3: { - _cleanup_free_ char *salt = NULL, *hash = NULL; - - if (rr->nsec3.salt_size > 0) { - salt = hexmem(rr->nsec3.salt, rr->nsec3.salt_size); - if (!salt) - return NULL; - } - - hash = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false); - if (!hash) - return NULL; - - t = format_types(rr->nsec3.types); - if (!t) - return NULL; - - r = asprintf(&s, "%s %"PRIu8" %"PRIu8" %"PRIu16" %s %s %s", - k, - rr->nsec3.algorithm, - rr->nsec3.flags, - rr->nsec3.iterations, - rr->nsec3.salt_size > 0 ? salt : "-", - hash, - t); - if (r < 0) - 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.data_size); - if (!t) - return NULL; - - /* Format as documented in RFC 3597, Section 5 */ - r = asprintf(&s, "%s \\# %zu %s", k, rr->generic.data_size, t); - if (r < 0) - return NULL; - break; - } - - rr->to_string = s; - return s; -} - -int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) { - - DnsPacket packet = { - .n_ref = 1, - .protocol = DNS_PROTOCOL_DNS, - .on_stack = true, - .refuse_compression = true, - .canonical_form = canonical, - }; - - size_t start, rds; - int r; - - assert(rr); - - /* Generates the RR in wire-format, optionally in the - * canonical form as discussed in the DNSSEC RFC 4034, Section - * 6.2. We allocate a throw-away DnsPacket object on the stack - * here, because we need some book-keeping for memory - * management, and can reuse the DnsPacket serializer, that - * can generate the canonical form, too, but also knows label - * compression and suchlike. */ - - if (rr->wire_format && rr->wire_format_canonical == canonical) - return 0; - - r = dns_packet_append_rr(&packet, rr, &start, &rds); - if (r < 0) - return r; - - assert(start == 0); - assert(packet._data); - - free(rr->wire_format); - rr->wire_format = packet._data; - rr->wire_format_size = packet.size; - rr->wire_format_rdata_offset = rds; - rr->wire_format_canonical = canonical; - - packet._data = NULL; - dns_packet_unref(&packet); - - return 0; -} - -int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret) { - const char *n; - int r; - - assert(rr); - assert(ret); - - /* Returns the RRset's signer, if it is known. */ - - if (rr->n_skip_labels_signer == (unsigned) -1) - return -ENODATA; - - n = DNS_RESOURCE_KEY_NAME(rr->key); - r = dns_name_skip(n, rr->n_skip_labels_signer, &n); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - *ret = n; - return 0; -} - -int dns_resource_record_source(DnsResourceRecord *rr, const char **ret) { - const char *n; - int r; - - assert(rr); - assert(ret); - - /* Returns the RRset's synthesizing source, if it is known. */ - - if (rr->n_skip_labels_source == (unsigned) -1) - return -ENODATA; - - n = DNS_RESOURCE_KEY_NAME(rr->key); - r = dns_name_skip(n, rr->n_skip_labels_source, &n); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - *ret = n; - return 0; -} - -int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone) { - const char *signer; - int r; - - assert(rr); - - r = dns_resource_record_signer(rr, &signer); - if (r < 0) - return r; - - return dns_name_equal(zone, signer); -} - -int dns_resource_record_is_synthetic(DnsResourceRecord *rr) { - int r; - - assert(rr); - - /* Returns > 0 if the RR is generated from a wildcard, and is not the asterisk name itself */ - - if (rr->n_skip_labels_source == (unsigned) -1) - return -ENODATA; - - if (rr->n_skip_labels_source == 0) - return 0; - - if (rr->n_skip_labels_source > 1) - return 1; - - r = dns_name_startswith(DNS_RESOURCE_KEY_NAME(rr->key), "*"); - if (r < 0) - return r; - - return !r; -} - -static void dns_resource_record_hash_func(const void *i, struct siphash *state) { - const DnsResourceRecord *rr = i; - - assert(rr); - - dns_resource_key_hash_func(rr->key, state); - - switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { - - case DNS_TYPE_SRV: - siphash24_compress(&rr->srv.priority, sizeof(rr->srv.priority), state); - siphash24_compress(&rr->srv.weight, sizeof(rr->srv.weight), state); - siphash24_compress(&rr->srv.port, sizeof(rr->srv.port), state); - dns_name_hash_func(rr->srv.name, state); - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - dns_name_hash_func(rr->ptr.name, state); - break; - - case DNS_TYPE_HINFO: - string_hash_func(rr->hinfo.cpu, state); - string_hash_func(rr->hinfo.os, state); - break; - - case DNS_TYPE_TXT: - case DNS_TYPE_SPF: { - DnsTxtItem *j; - - LIST_FOREACH(items, j, rr->txt.items) { - siphash24_compress(j->data, j->length, state); - - /* Add an extra NUL byte, so that "a" followed by "b" doesn't result in the same hash as "ab" - * followed by "". */ - siphash24_compress_byte(0, state); - } - break; - } - - case DNS_TYPE_A: - siphash24_compress(&rr->a.in_addr, sizeof(rr->a.in_addr), state); - break; - - case DNS_TYPE_AAAA: - siphash24_compress(&rr->aaaa.in6_addr, sizeof(rr->aaaa.in6_addr), state); - break; - - case DNS_TYPE_SOA: - dns_name_hash_func(rr->soa.mname, state); - dns_name_hash_func(rr->soa.rname, state); - siphash24_compress(&rr->soa.serial, sizeof(rr->soa.serial), state); - siphash24_compress(&rr->soa.refresh, sizeof(rr->soa.refresh), state); - siphash24_compress(&rr->soa.retry, sizeof(rr->soa.retry), state); - siphash24_compress(&rr->soa.expire, sizeof(rr->soa.expire), state); - siphash24_compress(&rr->soa.minimum, sizeof(rr->soa.minimum), state); - break; - - case DNS_TYPE_MX: - siphash24_compress(&rr->mx.priority, sizeof(rr->mx.priority), state); - dns_name_hash_func(rr->mx.exchange, state); - break; - - case DNS_TYPE_LOC: - siphash24_compress(&rr->loc.version, sizeof(rr->loc.version), state); - siphash24_compress(&rr->loc.size, sizeof(rr->loc.size), state); - siphash24_compress(&rr->loc.horiz_pre, sizeof(rr->loc.horiz_pre), state); - siphash24_compress(&rr->loc.vert_pre, sizeof(rr->loc.vert_pre), state); - siphash24_compress(&rr->loc.latitude, sizeof(rr->loc.latitude), state); - siphash24_compress(&rr->loc.longitude, sizeof(rr->loc.longitude), state); - siphash24_compress(&rr->loc.altitude, sizeof(rr->loc.altitude), state); - break; - - case DNS_TYPE_SSHFP: - siphash24_compress(&rr->sshfp.algorithm, sizeof(rr->sshfp.algorithm), state); - siphash24_compress(&rr->sshfp.fptype, sizeof(rr->sshfp.fptype), state); - siphash24_compress(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, state); - break; - - case DNS_TYPE_DNSKEY: - siphash24_compress(&rr->dnskey.flags, sizeof(rr->dnskey.flags), state); - siphash24_compress(&rr->dnskey.protocol, sizeof(rr->dnskey.protocol), state); - siphash24_compress(&rr->dnskey.algorithm, sizeof(rr->dnskey.algorithm), state); - siphash24_compress(rr->dnskey.key, rr->dnskey.key_size, state); - break; - - case DNS_TYPE_RRSIG: - siphash24_compress(&rr->rrsig.type_covered, sizeof(rr->rrsig.type_covered), state); - siphash24_compress(&rr->rrsig.algorithm, sizeof(rr->rrsig.algorithm), state); - siphash24_compress(&rr->rrsig.labels, sizeof(rr->rrsig.labels), state); - siphash24_compress(&rr->rrsig.original_ttl, sizeof(rr->rrsig.original_ttl), state); - siphash24_compress(&rr->rrsig.expiration, sizeof(rr->rrsig.expiration), state); - siphash24_compress(&rr->rrsig.inception, sizeof(rr->rrsig.inception), state); - siphash24_compress(&rr->rrsig.key_tag, sizeof(rr->rrsig.key_tag), state); - dns_name_hash_func(rr->rrsig.signer, state); - siphash24_compress(rr->rrsig.signature, rr->rrsig.signature_size, state); - break; - - case DNS_TYPE_NSEC: - dns_name_hash_func(rr->nsec.next_domain_name, state); - /* FIXME: we leave out the type bitmap here. Hash - * would be better if we'd take it into account - * too. */ - break; - - case DNS_TYPE_DS: - siphash24_compress(&rr->ds.key_tag, sizeof(rr->ds.key_tag), state); - siphash24_compress(&rr->ds.algorithm, sizeof(rr->ds.algorithm), state); - siphash24_compress(&rr->ds.digest_type, sizeof(rr->ds.digest_type), state); - siphash24_compress(rr->ds.digest, rr->ds.digest_size, state); - break; - - case DNS_TYPE_NSEC3: - siphash24_compress(&rr->nsec3.algorithm, sizeof(rr->nsec3.algorithm), state); - siphash24_compress(&rr->nsec3.flags, sizeof(rr->nsec3.flags), state); - siphash24_compress(&rr->nsec3.iterations, sizeof(rr->nsec3.iterations), state); - siphash24_compress(rr->nsec3.salt, rr->nsec3.salt_size, state); - siphash24_compress(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, state); - /* FIXME: We leave the bitmaps out */ - break; - - 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 ordering, 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; - - if (!i) - return NULL; - - n = i->items_next; - - free(i); - return dns_txt_item_free_all(n); -} - -bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) { - - if (a == b) - return true; - - if (!a != !b) - return false; - - if (!a) - return true; - - if (a->length != b->length) - return false; - - if (memcmp(a->data, b->data, a->length) != 0) - return false; - - return dns_txt_item_equal(a->items_next, b->items_next); -} - -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", - [DNSSEC_ALGORITHM_ECC] = "ECC", - [DNSSEC_ALGORITHM_RSASHA1] = "RSASHA1", - [DNSSEC_ALGORITHM_DSA_NSEC3_SHA1] = "DSA-NSEC3-SHA1", - [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_WITH_FALLBACK(dnssec_algorithm, int, 255); - -static const char* const dnssec_digest_table[_DNSSEC_DIGEST_MAX_DEFINED] = { - /* 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_WITH_FALLBACK(dnssec_digest, int, 255); diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h deleted file mode 100644 index 2e0dfbaba3..0000000000 --- a/src/resolve/resolved-dns-rr.h +++ /dev/null @@ -1,333 +0,0 @@ -#pragma once - -/*** - 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 . - ***/ - -#include - -#include "bitmap.h" -#include "dns-type.h" -#include "hashmap.h" -#include "in-addr-util.h" -#include "list.h" - -typedef struct DnsResourceKey DnsResourceKey; -typedef struct DnsResourceRecord DnsResourceRecord; -typedef struct DnsTxtItem DnsTxtItem; - -/* DNSKEY RR flags */ -#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) - -/* DNSSEC algorithm identifiers, see - * http://tools.ietf.org/html/rfc4034#appendix-A.1 and - * https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ -enum { - DNSSEC_ALGORITHM_RSAMD5 = 1, - DNSSEC_ALGORITHM_DH, - DNSSEC_ALGORITHM_DSA, - DNSSEC_ALGORITHM_ECC, - 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_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, - _DNSSEC_ALGORITHM_MAX_DEFINED -}; - -/* DNSSEC digest identifiers, see - * https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ -enum { - DNSSEC_DIGEST_SHA1 = 1, - 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 -1) for const keys, see below */ - uint16_t class, type; - char *_name; /* don't access directy, use DNS_RESOURCE_KEY_NAME()! */ -}; - -/* Creates a temporary resource key. This is only useful to quickly - * look up something, without allocating a full DnsResourceKey object - * for it. Note that it is not OK to take references to this kind of - * resource key object. */ -#define DNS_RESOURCE_KEY_CONST(c, t, n) \ - ((DnsResourceKey) { \ - .n_ref = (unsigned) -1, \ - .class = c, \ - .type = t, \ - ._name = (char*) n, \ - }) - - -struct DnsTxtItem { - size_t length; - LIST_FIELDS(DnsTxtItem, items); - uint8_t data[]; -}; - -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 data_size; - } generic, opt; - - struct { - uint16_t priority; - uint16_t weight; - uint16_t port; - char *name; - } srv; - - struct { - char *name; - } ptr, ns, cname, dname; - - struct { - char *cpu; - char *os; - } hinfo; - - struct { - DnsTxtItem *items; - } txt, spf; - - struct { - struct in_addr in_addr; - } a; - - struct { - struct in6_addr in6_addr; - } aaaa; - - struct { - char *mname; - char *rname; - uint32_t serial; - uint32_t refresh; - uint32_t retry; - uint32_t expire; - uint32_t minimum; - } soa; - - struct { - uint16_t priority; - char *exchange; - } mx; - - /* https://tools.ietf.org/html/rfc1876 */ - struct { - uint8_t version; - uint8_t size; - uint8_t horiz_pre; - uint8_t vert_pre; - uint32_t latitude; - uint32_t longitude; - uint32_t altitude; - } loc; - - /* https://tools.ietf.org/html/rfc4255#section-3.1 */ - struct { - uint8_t algorithm; - uint8_t fptype; - void *fingerprint; - size_t fingerprint_size; - } sshfp; - - /* http://tools.ietf.org/html/rfc4034#section-2.1 */ - struct { - uint16_t flags; - uint8_t protocol; - uint8_t algorithm; - void* key; - size_t key_size; - } dnskey; - - /* http://tools.ietf.org/html/rfc4034#section-3.1 */ - struct { - uint16_t type_covered; - uint8_t algorithm; - uint8_t labels; - uint32_t original_ttl; - uint32_t expiration; - uint32_t inception; - uint16_t key_tag; - char *signer; - void *signature; - size_t signature_size; - } rrsig; - - /* https://tools.ietf.org/html/rfc4034#section-4.1 */ - struct { - char *next_domain_name; - 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; - uint16_t iterations; - void *salt; - size_t salt_size; - void *next_hashed_name; - 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 (!key) - return NULL; - - if (key->_name) - return key->_name; - - 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); -DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name); -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, 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); -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceKey*, dns_resource_key_unref); - -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); -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); -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); - -extern const struct hash_ops dns_resource_key_hash_ops; -extern const struct hash_ops dns_resource_record_hash_ops; - -int dnssec_algorithm_to_string_alloc(int i, char **ret); -int dnssec_algorithm_from_string(const char *s) _pure_; - -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 deleted file mode 100644 index a406872a38..0000000000 --- a/src/resolve/resolved-dns-scope.c +++ /dev/null @@ -1,1028 +0,0 @@ -/*** - 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 . -***/ - -#include - -#include "af-list.h" -#include "alloc-util.h" -#include "dns-domain.h" -#include "fd-util.h" -#include "hostname-util.h" -#include "missing.h" -#include "random-util.h" -#include "resolved-dns-scope.h" -#include "resolved-llmnr.h" -#include "resolved-mdns.h" -#include "socket-util.h" -#include "strv.h" - -#define MULTICAST_RATELIMIT_INTERVAL_USEC (1*USEC_PER_SEC) -#define MULTICAST_RATELIMIT_BURST 1000 - -/* After how much time to repeat LLMNR requests, see RFC 4795 Section 7 */ -#define MULTICAST_RESEND_TIMEOUT_MIN_USEC (100 * USEC_PER_MSEC) -#define MULTICAST_RESEND_TIMEOUT_MAX_USEC (1 * USEC_PER_SEC) - -int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int family) { - DnsScope *s; - - assert(m); - assert(ret); - - s = new0(DnsScope, 1); - if (!s) - return -ENOMEM; - - s->manager = m; - s->link = l; - s->protocol = protocol; - 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); - dns_scope_mdns_membership(s, true); - - log_debug("New scope on link %s, protocol %s, family %s", l ? l->name : "*", dns_protocol_to_string(protocol), family == AF_UNSPEC ? "*" : af_to_name(family)); - - /* Enforce ratelimiting for the multicast protocols */ - RATELIMIT_INIT(s->ratelimit, MULTICAST_RATELIMIT_INTERVAL_USEC, MULTICAST_RATELIMIT_BURST); - - *ret = s; - return 0; -} - -static void dns_scope_abort_transactions(DnsScope *s) { - assert(s); - - while (s->transactions) { - DnsTransaction *t = s->transactions; - - /* Abort the transaction, but make sure it is not - * freed while we still look at it */ - - t->block_gc++; - if (DNS_TRANSACTION_IS_LIVE(t->state)) - dns_transaction_complete(t, DNS_TRANSACTION_ABORTED); - t->block_gc--; - - dns_transaction_free(t); - } -} - -DnsScope* dns_scope_free(DnsScope *s) { - DnsResourceRecord *rr; - - if (!s) - return NULL; - - log_debug("Removing scope on link %s, protocol %s, family %s", s->link ? s->link->name : "*", dns_protocol_to_string(s->protocol), s->family == AF_UNSPEC ? "*" : af_to_name(s->family)); - - dns_scope_llmnr_membership(s, false); - dns_scope_mdns_membership(s, false); - dns_scope_abort_transactions(s); - - while (s->query_candidates) - dns_query_candidate_free(s->query_candidates); - - hashmap_free(s->transactions_by_key); - - while ((rr = ordered_hashmap_steal_first(s->conflict_queue))) - dns_resource_record_unref(rr); - - ordered_hashmap_free(s->conflict_queue); - sd_event_source_unref(s->conflict_event_source); - - dns_cache_flush(&s->cache); - dns_zone_flush(&s->zone); - - LIST_REMOVE(scopes, s->manager->dns_scopes, s); - free(s); - - return NULL; -} - -DnsServer *dns_scope_get_dns_server(DnsScope *s) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_DNS) - return NULL; - - if (s->link) - return link_get_dns_server(s->link); - else - return manager_get_dns_server(s->manager); -} - -void dns_scope_next_dns_server(DnsScope *s) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_DNS) - return; - - if (s->link) - link_next_dns_server(s->link); - else - manager_next_dns_server(s->manager); -} - -void dns_scope_packet_received(DnsScope *s, usec_t rtt) { - assert(s); - - if (rtt <= s->max_rtt) - return; - - s->max_rtt = rtt; - s->resend_timeout = MIN(MAX(MULTICAST_RESEND_TIMEOUT_MIN_USEC, s->max_rtt * 2), MULTICAST_RESEND_TIMEOUT_MAX_USEC); -} - -void dns_scope_packet_lost(DnsScope *s, usec_t usec) { - assert(s); - - if (s->resend_timeout <= usec) - s->resend_timeout = MIN(s->resend_timeout * 2, MULTICAST_RESEND_TIMEOUT_MAX_USEC); -} - -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; - - assert(s); - assert(p); - assert(p->protocol == s->protocol); - - if (s->link) { - mtu = s->link->mtu; - ifindex = s->link->ifindex; - } else - mtu = manager_find_mtu(s->manager); - - switch (s->protocol) { - - case DNS_PROTOCOL_DNS: - assert(fd >= 0); - - if (DNS_PACKET_QDCOUNT(p) > 1) - return -EOPNOTSUPP; - - if (p->size > DNS_PACKET_UNICAST_SIZE_MAX) - return -EMSGSIZE; - - if (p->size + UDP_PACKET_HEADER_SIZE > mtu) - return -EMSGSIZE; - - r = manager_write(s->manager, fd, p); - if (r < 0) - return r; - - break; - - case DNS_PROTOCOL_LLMNR: - assert(fd < 0); - - if (DNS_PACKET_QDCOUNT(p) > 1) - return -EOPNOTSUPP; - - if (!ratelimit_test(&s->ratelimit)) - return -EBUSY; - - family = s->family; - - if (family == AF_INET) { - addr.in = LLMNR_MULTICAST_IPV4_ADDRESS; - fd = manager_llmnr_ipv4_udp_fd(s->manager); - } else if (family == AF_INET6) { - addr.in6 = LLMNR_MULTICAST_IPV6_ADDRESS; - fd = manager_llmnr_ipv6_udp_fd(s->manager); - } else - return -EAFNOSUPPORT; - if (fd < 0) - return fd; - - r = manager_send(s->manager, fd, ifindex, family, &addr, LLMNR_PORT, p); - if (r < 0) - return r; - - break; - - case DNS_PROTOCOL_MDNS: - assert(fd < 0); - - if (!ratelimit_test(&s->ratelimit)) - return -EBUSY; - - family = s->family; - - if (family == AF_INET) { - addr.in = MDNS_MULTICAST_IPV4_ADDRESS; - fd = manager_mdns_ipv4_fd(s->manager); - } else if (family == AF_INET6) { - addr.in6 = MDNS_MULTICAST_IPV6_ADDRESS; - fd = manager_mdns_ipv6_fd(s->manager); - } else - return -EAFNOSUPPORT; - if (fd < 0) - return fd; - - r = manager_send(s->manager, fd, ifindex, family, &addr, MDNS_PORT, p); - if (r < 0) - return r; - - break; - - default: - return -EAFNOSUPPORT; - } - - return 1; -} - -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)); - - do { - /* If there are multiple linked packets, set the TC bit in all but the last of them */ - if (p->more) { - assert(p->protocol == DNS_PROTOCOL_MDNS); - dns_packet_set_flags(p, true, true); - } - - r = dns_scope_emit_one(s, fd, p); - if (r < 0) - return r; - - p = p->more; - } while (p); - - return 0; -} - -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; - static const int one = 1; - int ret, r; - - assert(s); - - if (server) { - assert(family == AF_UNSPEC); - assert(!address); - - sa.sa.sa_family = server->family; - if (server->family == AF_INET) { - sa.in.sin_port = htobe16(port); - sa.in.sin_addr = server->address.in; - salen = sizeof(sa.in); - } else if (server->family == AF_INET6) { - sa.in6.sin6_port = htobe16(port); - 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) { - sa.in.sin_port = htobe16(port); - sa.in.sin_addr = address->in; - salen = sizeof(sa.in); - } else if (family == AF_INET6) { - sa.in6.sin6_port = htobe16(port); - sa.in6.sin6_addr = address->in6; - sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0; - salen = sizeof(sa.in6); - } else - return -EAFNOSUPPORT; - } - - fd = socket(sa.sa.sa_family, type|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) - return -errno; - - if (type == SOCK_STREAM) { - r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); - if (r < 0) - return -errno; - } - - if (s->link) { - uint32_t ifindex = htobe32(s->link->ifindex); - - if (sa.sa.sa_family == AF_INET) { - r = setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex)); - if (r < 0) - return -errno; - } else if (sa.sa.sa_family == AF_INET6) { - r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex)); - if (r < 0) - return -errno; - } - } - - if (s->protocol == DNS_PROTOCOL_LLMNR) { - /* RFC 4795, section 2.5 requires the TTL to be set to 1 */ - - if (sa.sa.sa_family == AF_INET) { - r = setsockopt(fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); - if (r < 0) - return -errno; - } else if (sa.sa.sa_family == AF_INET6) { - r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one)); - if (r < 0) - return -errno; - } - } - - r = connect(fd, &sa.sa, salen); - if (r < 0 && errno != EINPROGRESS) - return -errno; - - ret = fd; - fd = -1; - - return ret; -} - -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_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) { - DnsSearchDomain *d; - - assert(s); - assert(domain); - - /* Checks if the specified domain is something to look up on - * this scope. Note that this accepts non-qualified hostnames, - * i.e. those without any search path prefixed yet. */ - - if (ifindex != 0 && (!s->link || s->link->ifindex != ifindex)) - return DNS_SCOPE_NO; - - if ((SD_RESOLVED_FLAGS_MAKE(s->protocol, s->family, 0) & flags) == 0) - return DNS_SCOPE_NO; - - /* Never resolve any loopback hostname or IP address via DNS, - * LLMNR or mDNS. Instead, always rely on synthesized RRs for - * these. */ - if (is_localhost(domain) || - dns_name_endswith(domain, "127.in-addr.arpa") > 0 || - dns_name_equal(domain, "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 DNS_SCOPE_NO; - - /* Never respond to some of the domains listed in RFC6303 */ - if (dns_name_endswith(domain, "0.in-addr.arpa") > 0 || - dns_name_equal(domain, "255.255.255.255.in-addr.arpa") > 0 || - dns_name_equal(domain, "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.0.ip6.arpa") > 0) - return DNS_SCOPE_NO; - - /* Never respond to some of the domains listed in RFC6761 */ - if (dns_name_endswith(domain, "invalid") > 0) - return DNS_SCOPE_NO; - - /* Always honour search domains for routing queries. Note that - * we return DNS_SCOPE_YES here, rather than just - * DNS_SCOPE_MAYBE, which means wildcard scopes won't be - * considered anymore. */ - LIST_FOREACH(domains, d, dns_scope_get_search_domains(s)) - if (dns_name_endswith(domain, d->name) > 0) - return DNS_SCOPE_YES; - - switch (s->protocol) { - - case DNS_PROTOCOL_DNS: - - /* Exclude link-local IP ranges */ - if (dns_name_endswith(domain, "254.169.in-addr.arpa") == 0 && - dns_name_endswith(domain, "8.e.f.ip6.arpa") == 0 && - dns_name_endswith(domain, "9.e.f.ip6.arpa") == 0 && - dns_name_endswith(domain, "a.e.f.ip6.arpa") == 0 && - dns_name_endswith(domain, "b.e.f.ip6.arpa") == 0 && - /* If networks use .local in their private setups, they are supposed to also add .local to their search - * domains, which we already checked above. Otherwise, we consider .local specific to mDNS and won't - * send such queries ordinary DNS servers. */ - dns_name_endswith(domain, "local") == 0) - return DNS_SCOPE_MAYBE; - - return DNS_SCOPE_NO; - - case DNS_PROTOCOL_MDNS: - if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || - (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || - (dns_name_endswith(domain, "local") > 0 && /* only resolve names ending in .local via mDNS */ - dns_name_equal(domain, "local") == 0 && /* but not the single-label "local" name itself */ - manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via mDNS */ - return DNS_SCOPE_MAYBE; - - return DNS_SCOPE_NO; - - case DNS_PROTOCOL_LLMNR: - if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || - (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || - (dns_name_is_single_label(domain) && /* only resolve single label names via LLMNR */ - !is_gateway_hostname(domain) && /* don't resolve "gateway" with LLMNR, let nss-myhostname handle this */ - manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via LLMNR */ - return DNS_SCOPE_MAYBE; - - return DNS_SCOPE_NO; - - default: - assert_not_reached("Unknown scope protocol"); - } -} - -bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key) { - int key_family; - - assert(s); - assert(key); - - /* Check if it makes sense to resolve the specified key on - * 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, 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.) */ - if (!dns_resource_key_is_address(key)) - return true; - - /* However, we refuse to look up A and AAAA RRs on the - * root and single-label domains, under the assumption - * that those should be resolved via LLMNR or search - * path only, and should not be leaked onto the - * internet. */ - return !(dns_name_is_single_label(DNS_RESOURCE_KEY_NAME(key)) || - dns_name_is_root(DNS_RESOURCE_KEY_NAME(key))); - } - - /* On mDNS and LLMNR, send A and AAAA queries only on the - * respective scopes */ - - key_family = dns_type_to_af(key->type); - if (key_family < 0) - 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) { - int fd; - - assert(s); - assert(s->link); - - if (s->family == AF_INET) { - struct ip_mreqn mreqn = { - .imr_multiaddr = in, - .imr_ifindex = s->link->ifindex, - }; - - fd = manager_llmnr_ipv4_udp_fd(s->manager); - if (fd < 0) - return fd; - - /* Always first try to drop membership before we add - * one. This is necessary on some devices, such as - * veth. */ - if (b) - (void) setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn)); - - if (setsockopt(fd, IPPROTO_IP, b ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn)) < 0) - return -errno; - - } else if (s->family == AF_INET6) { - struct ipv6_mreq mreq = { - .ipv6mr_multiaddr = in6, - .ipv6mr_interface = s->link->ifindex, - }; - - fd = manager_llmnr_ipv6_udp_fd(s->manager); - if (fd < 0) - return fd; - - if (b) - (void) setsockopt(fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); - - if (setsockopt(fd, IPPROTO_IPV6, b ? IPV6_ADD_MEMBERSHIP : IPV6_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) - return -errno; - } else - return -EAFNOSUPPORT; - - return 0; -} - -int dns_scope_llmnr_membership(DnsScope *s, bool b) { - - if (s->protocol != DNS_PROTOCOL_LLMNR) - return 0; - - return dns_scope_multicast_membership(s, b, LLMNR_MULTICAST_IPV4_ADDRESS, LLMNR_MULTICAST_IPV6_ADDRESS); -} - -int dns_scope_mdns_membership(DnsScope *s, bool b) { - - if (s->protocol != DNS_PROTOCOL_MDNS) - return 0; - - return dns_scope_multicast_membership(s, b, MDNS_MULTICAST_IPV4_ADDRESS, MDNS_MULTICAST_IPV6_ADDRESS); -} - -static int dns_scope_make_reply_packet( - DnsScope *s, - uint16_t id, - int rcode, - DnsQuestion *q, - DnsAnswer *answer, - DnsAnswer *soa, - bool tentative, - DnsPacket **ret) { - - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - unsigned i; - int r; - - assert(s); - assert(ret); - - if ((!q || q->n_keys <= 0) - && (!answer || answer->n_rrs <= 0) - && (!soa || soa->n_rrs <= 0)) - return -EINVAL; - - r = dns_packet_new(&p, s->protocol, 0); - if (r < 0) - return r; - - DNS_PACKET_HEADER(p)->id = id; - DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( - 1 /* qr */, - 0 /* opcode */, - 0 /* c */, - 0 /* tc */, - tentative, - 0 /* (ra) */, - 0 /* (ad) */, - 0 /* (cd) */, - rcode)); - - if (q) { - for (i = 0; i < q->n_keys; i++) { - r = dns_packet_append_key(p, q->keys[i], NULL); - if (r < 0) - return r; - } - - DNS_PACKET_HEADER(p)->qdcount = htobe16(q->n_keys); - } - - if (answer) { - for (i = 0; i < answer->n_rrs; i++) { - r = dns_packet_append_rr(p, answer->items[i].rr, NULL, NULL); - if (r < 0) - return r; - } - - DNS_PACKET_HEADER(p)->ancount = htobe16(answer->n_rrs); - } - - if (soa) { - for (i = 0; i < soa->n_rrs; i++) { - r = dns_packet_append_rr(p, soa->items[i].rr, NULL, NULL); - if (r < 0) - return r; - } - - DNS_PACKET_HEADER(p)->arcount = htobe16(soa->n_rrs); - } - - *ret = p; - p = NULL; - - return 0; -} - -static void dns_scope_verify_conflicts(DnsScope *s, DnsPacket *p) { - unsigned n; - - assert(s); - assert(p); - - if (p->question) - for (n = 0; n < p->question->n_keys; n++) - dns_zone_verify_conflicts(&s->zone, p->question->keys[n]); - if (p->answer) - for (n = 0; n < p->answer->n_rrs; n++) - dns_zone_verify_conflicts(&s->zone, p->answer->items[n].rr->key); -} - -void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) { - _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; - DnsResourceKey *key = NULL; - bool tentative = false; - int r, fd; - - assert(s); - assert(p); - - if (p->protocol != DNS_PROTOCOL_LLMNR) - return; - - if (p->ipproto == IPPROTO_UDP) { - /* Don't accept UDP queries directed to anything but - * the LLMNR multicast addresses. See RFC 4795, - * section 2.5. */ - - if (p->family == AF_INET && !in_addr_equal(AF_INET, &p->destination, (union in_addr_union*) &LLMNR_MULTICAST_IPV4_ADDRESS)) - return; - - if (p->family == AF_INET6 && !in_addr_equal(AF_INET6, &p->destination, (union in_addr_union*) &LLMNR_MULTICAST_IPV6_ADDRESS)) - return; - } - - r = dns_packet_extract(p); - if (r < 0) { - log_debug_errno(r, "Failed to extract resources from incoming packet: %m"); - return; - } - - if (DNS_PACKET_LLMNR_C(p)) { - /* Somebody notified us about a possible conflict */ - dns_scope_verify_conflicts(s, p); - return; - } - - assert(p->question->n_keys == 1); - key = p->question->keys[0]; - - r = dns_zone_lookup(&s->zone, key, &answer, &soa, &tentative); - if (r < 0) { - log_debug_errno(r, "Failed to lookup key: %m"); - return; - } - if (r == 0) - return; - - if (answer) - dns_answer_order_by_scope(answer, in_addr_is_link_local(p->family, &p->sender) > 0); - - r = dns_scope_make_reply_packet(s, DNS_PACKET_ID(p), DNS_RCODE_SUCCESS, p->question, answer, soa, tentative, &reply); - if (r < 0) { - log_debug_errno(r, "Failed to build reply packet: %m"); - return; - } - - if (stream) - r = dns_stream_write_packet(stream, reply); - else { - if (!ratelimit_test(&s->ratelimit)) - return; - - if (p->family == AF_INET) - fd = manager_llmnr_ipv4_udp_fd(s->manager); - else if (p->family == AF_INET6) - fd = manager_llmnr_ipv6_udp_fd(s->manager); - else { - log_debug("Unknown protocol"); - return; - } - if (fd < 0) { - log_debug_errno(fd, "Failed to get reply socket: %m"); - return; - } - - /* Note that we always immediately reply to all LLMNR - * requests, and do not wait any time, since we - * verified uniqueness for all records. Also see RFC - * 4795, Section 2.7 */ - - r = manager_send(s->manager, fd, p->ifindex, p->family, &p->sender, p->sender_port, reply); - } - - if (r < 0) { - log_debug_errno(r, "Failed to send reply packet: %m"); - return; - } -} - -DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok) { - DnsTransaction *t; - - assert(scope); - assert(key); - - /* Try to find an ongoing transaction that is a equal to the - * specified question */ - t = hashmap_get(scope->transactions_by_key, key); - if (!t) - return NULL; - - /* 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_RCODE_FAILURE) && - t->answer_source != DNS_TRANSACTION_NETWORK) - return NULL; - - return t; -} - -static int dns_scope_make_conflict_packet( - DnsScope *s, - DnsResourceRecord *rr, - DnsPacket **ret) { - - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - int r; - - assert(s); - assert(rr); - assert(ret); - - r = dns_packet_new(&p, s->protocol, 0); - if (r < 0) - return r; - - DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( - 0 /* qr */, - 0 /* opcode */, - 1 /* conflict */, - 0 /* tc */, - 0 /* t */, - 0 /* (ra) */, - 0 /* (ad) */, - 0 /* (cd) */, - 0)); - - /* For mDNS, the transaction ID should always be 0 */ - if (s->protocol != DNS_PROTOCOL_MDNS) - random_bytes(&DNS_PACKET_HEADER(p)->id, sizeof(uint16_t)); - - DNS_PACKET_HEADER(p)->qdcount = htobe16(1); - DNS_PACKET_HEADER(p)->arcount = htobe16(1); - - r = dns_packet_append_key(p, rr->key, NULL); - if (r < 0) - return r; - - r = dns_packet_append_rr(p, rr, NULL, NULL); - if (r < 0) - return r; - - *ret = p; - p = NULL; - - return 0; -} - -static int on_conflict_dispatch(sd_event_source *es, usec_t usec, void *userdata) { - DnsScope *scope = userdata; - int r; - - assert(es); - assert(scope); - - scope->conflict_event_source = sd_event_source_unref(scope->conflict_event_source); - - for (;;) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - - rr = ordered_hashmap_steal_first(scope->conflict_queue); - if (!rr) - break; - - r = dns_scope_make_conflict_packet(scope, rr, &p); - if (r < 0) { - log_error_errno(r, "Failed to make conflict packet: %m"); - return 0; - } - - r = dns_scope_emit_udp(scope, -1, p); - if (r < 0) - log_debug_errno(r, "Failed to send conflict packet: %m"); - } - - return 0; -} - -int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr) { - usec_t jitter; - int r; - - assert(scope); - assert(rr); - - /* We don't send these queries immediately. Instead, we queue - * them, and send them after some jitter delay. */ - r = ordered_hashmap_ensure_allocated(&scope->conflict_queue, &dns_resource_key_hash_ops); - if (r < 0) { - log_oom(); - return r; - } - - /* We only place one RR per key in the conflict - * messages, not all of them. That should be enough to - * indicate where there might be a conflict */ - r = ordered_hashmap_put(scope->conflict_queue, rr->key, rr); - if (r == -EEXIST || r == 0) - return 0; - if (r < 0) - return log_debug_errno(r, "Failed to queue conflicting RR: %m"); - - dns_resource_record_ref(rr); - - if (scope->conflict_event_source) - return 0; - - random_bytes(&jitter, sizeof(jitter)); - jitter %= LLMNR_JITTER_INTERVAL_USEC; - - r = sd_event_add_time(scope->manager->event, - &scope->conflict_event_source, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + jitter, - LLMNR_JITTER_INTERVAL_USEC, - on_conflict_dispatch, scope); - 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; -} - -void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p) { - unsigned i; - int r; - - assert(scope); - assert(p); - - if (p->protocol != DNS_PROTOCOL_LLMNR) - return; - - if (DNS_PACKET_RRCOUNT(p) <= 0) - return; - - if (DNS_PACKET_LLMNR_C(p) != 0) - return; - - if (DNS_PACKET_LLMNR_T(p) != 0) - return; - - if (manager_our_packet(scope->manager, p)) - return; - - r = dns_packet_extract(p); - if (r < 0) { - log_debug_errno(r, "Failed to extract packet: %m"); - return; - } - - log_debug("Checking for conflicts..."); - - for (i = 0; i < p->answer->n_rrs; i++) { - - /* Check for conflicts against the local zone. If we - * found one, we won't check any further */ - r = dns_zone_check_conflicts(&scope->zone, p->answer->items[i].rr); - if (r != 0) - continue; - - /* Check for conflicts against the local cache. If so, - * send out an advisory query, to inform everybody */ - r = dns_cache_check_conflicts(&scope->cache, p->answer->items[i].rr, p->family, &p->sender); - if (r <= 0) - continue; - - dns_scope_notify_conflict(scope, p->answer->items[i].rr); - } -} - -void dns_scope_dump(DnsScope *s, FILE *f) { - assert(s); - - if (!f) - f = stdout; - - fputs("[Scope protocol=", f); - fputs(dns_protocol_to_string(s->protocol), f); - - if (s->link) { - fputs(" interface=", f); - fputs(s->link->name, f); - } - - if (s->family != AF_UNSPEC) { - fputs(" family=", f); - fputs(af_to_name(s->family), f); - } - - fputs("]\n", f); - - if (!dns_zone_is_empty(&s->zone)) { - fputs("ZONE:\n", f); - dns_zone_dump(&s->zone, f); - } - - if (!dns_cache_is_empty(&s->cache)) { - fputs("CACHE:\n", f); - dns_cache_dump(&s->cache, f); - } -} - -DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_DNS) - return NULL; - - if (s->link) - return s->link->search_domains; - - return s->manager->search_domains; -} - -bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_DNS) - return false; - - 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 deleted file mode 100644 index 291e5817d0..0000000000 --- a/src/resolve/resolved-dns-scope.h +++ /dev/null @@ -1,109 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "list.h" - -typedef struct DnsScope DnsScope; - -#include "resolved-dns-cache.h" -#include "resolved-dns-dnssec.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-server.h" -#include "resolved-dns-zone.h" -#include "resolved-link.h" - -typedef enum DnsScopeMatch { - DNS_SCOPE_NO, - DNS_SCOPE_MAYBE, - DNS_SCOPE_YES, - _DNS_SCOPE_MATCH_MAX, - _DNS_SCOPE_INVALID = -1 -} DnsScopeMatch; - -struct DnsScope { - Manager *manager; - - DnsProtocol protocol; - int family; - DnssecMode dnssec_mode; - - Link *link; - - DnsCache cache; - DnsZone zone; - - OrderedHashmap *conflict_queue; - sd_event_source *conflict_event_source; - - RateLimit ratelimit; - - usec_t resend_timeout; - usec_t max_rtt; - - LIST_HEAD(DnsQueryCandidate, query_candidates); - - /* Note that we keep track of ongoing transactions in two - * ways: once in a hashmap, indexed by the rr key, and once in - * a linked list. We use the hashmap to quickly find - * transactions we can reuse for a key. But note that there - * might be multiple transactions for the same key (because - * the zone probing can't reuse a transaction answered from - * the zone or the cache), and the hashmap only tracks the - * most recent entry. */ - Hashmap *transactions_by_key; - LIST_HEAD(DnsTransaction, transactions); - - LIST_FIELDS(DnsScope, scopes); -}; - -int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol p, int family); -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_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); -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); - -int dns_scope_llmnr_membership(DnsScope *s, bool b); -int dns_scope_mdns_membership(DnsScope *s, bool b); - -void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p); - -DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok); - -int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr); -void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p); - -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 deleted file mode 100644 index 732471027b..0000000000 --- a/src/resolve/resolved-dns-search-domain.c +++ /dev/null @@ -1,227 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include "alloc-util.h" -#include "dns-domain.h" -#include "resolved-dns-search-domain.h" - -int dns_search_domain_new( - Manager *m, - DnsSearchDomain **ret, - DnsSearchDomainType type, - Link *l, - const char *name) { - - _cleanup_free_ char *normalized = NULL; - DnsSearchDomain *d; - int r; - - assert(m); - assert((type == DNS_SEARCH_DOMAIN_LINK) == !!l); - assert(name); - - r = dns_name_normalize(name, &normalized); - if (r < 0) - return r; - - if (l) { - if (l->n_search_domains >= LINK_SEARCH_DOMAINS_MAX) - return -E2BIG; - } else { - if (m->n_search_domains >= MANAGER_SEARCH_DOMAINS_MAX) - return -E2BIG; - } - - d = new0(DnsSearchDomain, 1); - if (!d) - return -ENOMEM; - - d->n_ref = 1; - d->manager = m; - d->type = type; - d->name = normalized; - normalized = NULL; - - switch (type) { - - case DNS_SEARCH_DOMAIN_LINK: - d->link = l; - LIST_APPEND(domains, l->search_domains, d); - l->n_search_domains++; - break; - - case DNS_SERVER_SYSTEM: - LIST_APPEND(domains, m->search_domains, d); - m->n_search_domains++; - break; - - default: - assert_not_reached("Unknown search domain type"); - } - - d->linked = true; - - if (ret) - *ret = d; - - return 0; -} - -DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d) { - if (!d) - return NULL; - - assert(d->n_ref > 0); - d->n_ref++; - - return d; -} - -DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d) { - if (!d) - return NULL; - - assert(d->n_ref > 0); - d->n_ref--; - - if (d->n_ref > 0) - return NULL; - - free(d->name); - free(d); - - return NULL; -} - -void dns_search_domain_unlink(DnsSearchDomain *d) { - assert(d); - assert(d->manager); - - if (!d->linked) - return; - - switch (d->type) { - - case DNS_SEARCH_DOMAIN_LINK: - assert(d->link); - assert(d->link->n_search_domains > 0); - LIST_REMOVE(domains, d->link->search_domains, d); - d->link->n_search_domains--; - break; - - case DNS_SEARCH_DOMAIN_SYSTEM: - assert(d->manager->n_search_domains > 0); - LIST_REMOVE(domains, d->manager->search_domains, d); - d->manager->n_search_domains--; - break; - } - - d->linked = false; - - dns_search_domain_unref(d); -} - -void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d) { - DnsSearchDomain *tail; - - assert(d); - - if (!d->marked) - return; - - d->marked = false; - - if (!d->linked || !d->domains_next) - return; - - switch (d->type) { - - case DNS_SEARCH_DOMAIN_LINK: - assert(d->link); - LIST_FIND_TAIL(domains, d, tail); - LIST_REMOVE(domains, d->link->search_domains, d); - LIST_INSERT_AFTER(domains, d->link->search_domains, tail, d); - break; - - case DNS_SEARCH_DOMAIN_SYSTEM: - LIST_FIND_TAIL(domains, d, tail); - LIST_REMOVE(domains, d->manager->search_domains, d); - LIST_INSERT_AFTER(domains, d->manager->search_domains, tail, d); - break; - - default: - assert_not_reached("Unknown search domain type"); - } -} - -void dns_search_domain_unlink_all(DnsSearchDomain *first) { - DnsSearchDomain *next; - - if (!first) - return; - - next = first->domains_next; - dns_search_domain_unlink(first); - - dns_search_domain_unlink_all(next); -} - -void dns_search_domain_unlink_marked(DnsSearchDomain *first) { - DnsSearchDomain *next; - - if (!first) - return; - - next = first->domains_next; - - if (first->marked) - dns_search_domain_unlink(first); - - dns_search_domain_unlink_marked(next); -} - -void dns_search_domain_mark_all(DnsSearchDomain *first) { - if (!first) - return; - - first->marked = true; - dns_search_domain_mark_all(first->domains_next); -} - -int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret) { - DnsSearchDomain *d; - int r; - - assert(name); - assert(ret); - - LIST_FOREACH(domains, d, first) { - - r = dns_name_equal(name, d->name); - if (r < 0) - return r; - if (r > 0) { - *ret = d; - return 1; - } - } - - *ret = NULL; - return 0; -} diff --git a/src/resolve/resolved-dns-search-domain.h b/src/resolve/resolved-dns-search-domain.h deleted file mode 100644 index eaacef4edc..0000000000 --- a/src/resolve/resolved-dns-search-domain.h +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include "macro.h" - -typedef struct DnsSearchDomain DnsSearchDomain; - -typedef enum DnsSearchDomainType { - DNS_SEARCH_DOMAIN_SYSTEM, - DNS_SEARCH_DOMAIN_LINK, -} DnsSearchDomainType; - -#include "resolved-link.h" -#include "resolved-manager.h" - -struct DnsSearchDomain { - Manager *manager; - - unsigned n_ref; - - DnsSearchDomainType type; - Link *link; - - char *name; - - bool marked:1; - bool route_only:1; - - bool linked:1; - LIST_FIELDS(DnsSearchDomain, domains); -}; - -int dns_search_domain_new( - Manager *m, - DnsSearchDomain **ret, - DnsSearchDomainType type, - Link *link, - const char *name); - -DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d); -DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d); - -void dns_search_domain_unlink(DnsSearchDomain *d); -void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d); - -void dns_search_domain_unlink_all(DnsSearchDomain *first); -void dns_search_domain_unlink_marked(DnsSearchDomain *first); -void dns_search_domain_mark_all(DnsSearchDomain *first); - -int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret); - -static inline const char* DNS_SEARCH_DOMAIN_NAME(DnsSearchDomain *d) { - return d ? d->name : NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsSearchDomain*, dns_search_domain_unref); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c deleted file mode 100644 index 27342a0e04..0000000000 --- a/src/resolve/resolved-dns-server.c +++ /dev/null @@ -1,740 +0,0 @@ -/*** - 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 . -***/ - -#include - -#include "alloc-util.h" -#include "resolved-dns-server.h" -#include "resolved-resolv-conf.h" -#include "siphash24.h" -#include "string-table.h" -#include "string-util.h" - -/* After how much time to repeat classic DNS requests */ -#define DNS_TIMEOUT_MIN_USEC (500 * USEC_PER_MSEC) -#define DNS_TIMEOUT_MAX_USEC (5 * USEC_PER_SEC) - -/* The amount of time to wait before retrying with a full feature set */ -#define DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC (6 * USEC_PER_HOUR) -#define DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC (5 * USEC_PER_MINUTE) - -/* The number of times we will attempt a certain feature set before degrading */ -#define DNS_SERVER_FEATURE_RETRY_ATTEMPTS 3 - -int dns_server_new( - Manager *m, - DnsServer **ret, - DnsServerType type, - Link *l, - int family, - const union in_addr_union *in_addr) { - - DnsServer *s; - - assert(m); - assert((type == DNS_SERVER_LINK) == !!l); - assert(in_addr); - - if (!IN_SET(family, AF_INET, AF_INET6)) - return -EAFNOSUPPORT; - - if (l) { - if (l->n_dns_servers >= LINK_DNS_SERVERS_MAX) - return -E2BIG; - } else { - if (m->n_dns_servers >= MANAGER_DNS_SERVERS_MAX) - return -E2BIG; - } - - s = new0(DnsServer, 1); - if (!s) - return -ENOMEM; - - s->n_ref = 1; - s->manager = m; - 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; - s->family = family; - s->address = *in_addr; - s->resend_timeout = DNS_TIMEOUT_MIN_USEC; - - switch (type) { - - case DNS_SERVER_LINK: - s->link = l; - LIST_APPEND(servers, l->dns_servers, s); - l->n_dns_servers++; - break; - - case DNS_SERVER_SYSTEM: - LIST_APPEND(servers, m->dns_servers, s); - m->n_dns_servers++; - break; - - case DNS_SERVER_FALLBACK: - LIST_APPEND(servers, m->fallback_dns_servers, s); - m->n_dns_servers++; - break; - - default: - assert_not_reached("Unknown server type"); - } - - s->linked = true; - - /* A new DNS server that isn't fallback is added and the one - * we used so far was a fallback one? Then let's try to pick - * the new one */ - if (type != DNS_SERVER_FALLBACK && - m->current_dns_server && - m->current_dns_server->type == DNS_SERVER_FALLBACK) - manager_set_dns_server(m, NULL); - - if (ret) - *ret = s; - - return 0; -} - -DnsServer* dns_server_ref(DnsServer *s) { - if (!s) - return NULL; - - assert(s->n_ref > 0); - s->n_ref ++; - - return s; -} - -DnsServer* dns_server_unref(DnsServer *s) { - if (!s) - return NULL; - - assert(s->n_ref > 0); - s->n_ref --; - - if (s->n_ref > 0) - return NULL; - - free(s->server_string); - free(s); - return NULL; -} - -void dns_server_unlink(DnsServer *s) { - assert(s); - assert(s->manager); - - /* This removes the specified server from the linked list of - * servers, but any server might still stay around if it has - * refs, for example from an ongoing transaction. */ - - if (!s->linked) - return; - - switch (s->type) { - - case DNS_SERVER_LINK: - assert(s->link); - assert(s->link->n_dns_servers > 0); - LIST_REMOVE(servers, s->link->dns_servers, s); - break; - - case DNS_SERVER_SYSTEM: - assert(s->manager->n_dns_servers > 0); - LIST_REMOVE(servers, s->manager->dns_servers, s); - s->manager->n_dns_servers--; - break; - - case DNS_SERVER_FALLBACK: - assert(s->manager->n_dns_servers > 0); - LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); - s->manager->n_dns_servers--; - break; - } - - s->linked = false; - - if (s->link && s->link->current_dns_server == s) - link_set_dns_server(s->link, NULL); - - if (s->manager->current_dns_server == s) - manager_set_dns_server(s->manager, NULL); - - dns_server_unref(s); -} - -void dns_server_move_back_and_unmark(DnsServer *s) { - DnsServer *tail; - - assert(s); - - if (!s->marked) - return; - - s->marked = false; - - if (!s->linked || !s->servers_next) - return; - - /* Move us to the end of the list, so that the order is - * strictly kept, if we are not at the end anyway. */ - - switch (s->type) { - - case DNS_SERVER_LINK: - assert(s->link); - LIST_FIND_TAIL(servers, s, tail); - LIST_REMOVE(servers, s->link->dns_servers, s); - LIST_INSERT_AFTER(servers, s->link->dns_servers, tail, s); - break; - - case DNS_SERVER_SYSTEM: - LIST_FIND_TAIL(servers, s, tail); - LIST_REMOVE(servers, s->manager->dns_servers, s); - LIST_INSERT_AFTER(servers, s->manager->dns_servers, tail, s); - break; - - case DNS_SERVER_FALLBACK: - LIST_FIND_TAIL(servers, s, tail); - LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); - LIST_INSERT_AFTER(servers, s->manager->fallback_dns_servers, tail, s); - break; - - default: - assert_not_reached("Unknown server type"); - } -} - -static void dns_server_verified(DnsServer *s, DnsServerFeatureLevel level) { - assert(s); - - 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; - } - - 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 (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 = CLAMP(s->max_rtt * 2, DNS_TIMEOUT_MIN_USEC, DNS_TIMEOUT_MAX_USEC); - } -} - -void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec) { - assert(s); - assert(s->manager); - - if (s->possible_feature_level == level) { - if (protocol == IPPROTO_UDP) - s->n_failed_udp ++; - else if (protocol == IPPROTO_TCP) - s->n_failed_tcp ++; - } - - if (s->resend_timeout > usec) - return; - - s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC); -} - -void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level) { - assert(s); - - /* 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; - - /* 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) { - usec_t ts; - - assert(s); - assert(s->manager); - - if (s->verified_usec == 0) - return false; - - assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - - if (s->verified_usec + s->features_grace_period_usec > ts) - return false; - - s->features_grace_period_usec = MIN(s->features_grace_period_usec * 2, DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC); - - return true; -} - -static void dns_server_reset_counters(DnsServer *s) { - assert(s); - - s->n_failed_udp = 0; - s->n_failed_tcp = 0; - s->packet_failed = false; - s->packet_truncated = false; - s->verified_usec = 0; - - /* Note that we do not reset s->packet_bad_opt and s->packet_rrsig_missing here. We reset them only when the - * grace period ends, but not when lowering the possible feature level, as a lower level feature level should - * not make RRSIGs appear or OPT appear, but rather make them disappear. If the reappear anyway, then that's - * indication for a differently broken OPT/RRSIG implementation, and we really don't want to support that - * either. - * - * This is particularly important to deal with certain Belkin routers which break OPT for certain lookups (A), - * but pass traffic through for others (AAAA). If we detect the broken behaviour on one lookup we should not - * reenable it for another, because we cannot validate things anyway, given that the RRSIG/OPT data will be - * incomplete. */ -} - -DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { - assert(s); - - if (s->possible_feature_level != DNS_SERVER_FEATURE_LEVEL_BEST && - dns_server_grace_period_expired(s)) { - - 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_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) { - const DnsServer *s = p; - - assert(s); - - siphash24_compress(&s->family, sizeof(s->family), state); - siphash24_compress(&s->address, FAMILY_ADDRESS_SIZE(s->family), state); -} - -static int dns_server_compare_func(const void *a, const void *b) { - const DnsServer *x = a, *y = b; - - if (x->family < y->family) - return -1; - if (x->family > y->family) - return 1; - - return memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family)); -} - -const struct hash_ops dns_server_hash_ops = { - .hash = dns_server_hash_func, - .compare = dns_server_compare_func -}; - -void dns_server_unlink_all(DnsServer *first) { - DnsServer *next; - - if (!first) - return; - - next = first->servers_next; - dns_server_unlink(first); - - dns_server_unlink_all(next); -} - -void dns_server_unlink_marked(DnsServer *first) { - DnsServer *next; - - if (!first) - return; - - next = first->servers_next; - - if (first->marked) - dns_server_unlink(first); - - dns_server_unlink_marked(next); -} - -void dns_server_mark_all(DnsServer *first) { - if (!first) - return; - - first->marked = true; - dns_server_mark_all(first->servers_next); -} - -DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr) { - DnsServer *s; - - LIST_FOREACH(servers, s, first) - if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0) - return s; - - return NULL; -} - -DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t) { - assert(m); - - switch (t) { - - case DNS_SERVER_SYSTEM: - return m->dns_servers; - - case DNS_SERVER_FALLBACK: - return m->fallback_dns_servers; - - default: - return NULL; - } -} - -DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) { - assert(m); - - if (m->current_dns_server == s) - return s; - - 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); - - if (m->unicast_scope) - dns_cache_flush(&m->unicast_scope->cache); - - return s; -} - -DnsServer *manager_get_dns_server(Manager *m) { - Link *l; - assert(m); - - /* Try to read updates resolv.conf */ - manager_read_resolv_conf(m); - - /* 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); - - if (!m->current_dns_server) { - bool found = false; - Iterator i; - - /* No DNS servers configured, let's see if there are - * any on any links. If not, we use the fallback - * servers */ - - HASHMAP_FOREACH(l, m->links, i) - if (l->dns_servers) { - found = true; - break; - } - - if (!found) - manager_set_dns_server(m, m->fallback_dns_servers); - } - - return m->current_dns_server; -} - -void manager_next_dns_server(Manager *m) { - assert(m); - - /* If there's currently no DNS server set, then the next - * manager_get_dns_server() will find one */ - if (!m->current_dns_server) - return; - - /* Change to the next one, but make sure to follow the linked - * list only if the server is still linked. */ - if (m->current_dns_server->linked && m->current_dns_server->servers_next) { - manager_set_dns_server(m, m->current_dns_server->servers_next); - return; - } - - /* If there was no next one, then start from the beginning of - * the list */ - if (m->current_dns_server->type == DNS_SERVER_FALLBACK) - manager_set_dns_server(m, m->fallback_dns_servers); - else - 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", - [DNS_SERVER_FEATURE_LEVEL_EDNS0] = "UDP+EDNS0", - [DNS_SERVER_FEATURE_LEVEL_DO] = "UDP+EDNS0+DO", - [DNS_SERVER_FEATURE_LEVEL_LARGE] = "UDP+EDNS0+DO+LARGE", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_server_feature_level, DnsServerFeatureLevel); diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h deleted file mode 100644 index 9f4a69c37a..0000000000 --- a/src/resolve/resolved-dns-server.h +++ /dev/null @@ -1,143 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "in-addr-util.h" - -typedef struct DnsServer DnsServer; - -typedef enum DnsServerType { - DNS_SERVER_SYSTEM, - 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, - DNS_SERVER_FEATURE_LEVEL_UDP, - DNS_SERVER_FEATURE_LEVEL_EDNS0, - DNS_SERVER_FEATURE_LEVEL_DO, - DNS_SERVER_FEATURE_LEVEL_LARGE, - _DNS_SERVER_FEATURE_LEVEL_MAX, - _DNS_SERVER_FEATURE_LEVEL_INVALID = -1 -} DnsServerFeatureLevel; - -#define DNS_SERVER_FEATURE_LEVEL_WORST 0 -#define DNS_SERVER_FEATURE_LEVEL_BEST (_DNS_SERVER_FEATURE_LEVEL_MAX - 1) - -const char* dns_server_feature_level_to_string(int i) _const_; -int dns_server_feature_level_from_string(const char *s) _pure_; - -#include "resolved-link.h" -#include "resolved-manager.h" - -struct DnsServer { - Manager *manager; - - unsigned n_ref; - - DnsServerType type; - Link *link; - - int family; - union in_addr_union address; - - char *server_string; - - usec_t resend_timeout; - usec_t max_rtt; - - DnsServerFeatureLevel verified_feature_level; - DnsServerFeatureLevel possible_feature_level; - - size_t received_udp_packet_max; - - unsigned n_failed_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); -}; - -int dns_server_new( - Manager *m, - DnsServer **ret, - DnsServerType type, - Link *link, - int family, - const union in_addr_union *address); - -DnsServer* dns_server_ref(DnsServer *s); -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, 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); - -void dns_server_unlink_all(DnsServer *first); -void dns_server_unlink_marked(DnsServer *first); -void dns_server_mark_all(DnsServer *first); - -DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t); - -DnsServer *manager_set_dns_server(Manager *m, DnsServer *s); -DnsServer *manager_get_dns_server(Manager *m); -void manager_next_dns_server(Manager *m); - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref); - -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 deleted file mode 100644 index a1040aeff4..0000000000 --- a/src/resolve/resolved-dns-stream.c +++ /dev/null @@ -1,403 +0,0 @@ -/*** - 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 . -***/ - -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "io-util.h" -#include "missing.h" -#include "resolved-dns-stream.h" - -#define DNS_STREAM_TIMEOUT_USEC (10 * USEC_PER_SEC) -#define DNS_STREAMS_MAX 128 - -static void dns_stream_stop(DnsStream *s) { - assert(s); - - s->io_event_source = sd_event_source_unref(s->io_event_source); - s->timeout_event_source = sd_event_source_unref(s->timeout_event_source); - s->fd = safe_close(s->fd); -} - -static int dns_stream_update_io(DnsStream *s) { - int f = 0; - - assert(s); - - if (s->write_packet && s->n_written < sizeof(s->write_size) + s->write_packet->size) - f |= EPOLLOUT; - if (!s->read_packet || s->n_read < sizeof(s->read_size) + s->read_packet->size) - f |= EPOLLIN; - - return sd_event_source_set_io_events(s->io_event_source, f); -} - -static int dns_stream_complete(DnsStream *s, int error) { - assert(s); - - dns_stream_stop(s); - - if (s->complete) - s->complete(s, error); - else - dns_stream_free(s); - - return 0; -} - -static int dns_stream_identify(DnsStream *s) { - union { - struct cmsghdr header; /* For alignment */ - uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo)) - + EXTRA_CMSG_SPACE /* kernel appears to require extra space */]; - } control; - struct msghdr mh = {}; - struct cmsghdr *cmsg; - socklen_t sl; - int r; - - assert(s); - - if (s->identified) - return 0; - - /* Query the local side */ - s->local_salen = sizeof(s->local); - r = getsockname(s->fd, &s->local.sa, &s->local_salen); - if (r < 0) - return -errno; - if (s->local.sa.sa_family == AF_INET6 && s->ifindex <= 0) - s->ifindex = s->local.in6.sin6_scope_id; - - /* Query the remote side */ - s->peer_salen = sizeof(s->peer); - r = getpeername(s->fd, &s->peer.sa, &s->peer_salen); - if (r < 0) - return -errno; - if (s->peer.sa.sa_family == AF_INET6 && s->ifindex <= 0) - s->ifindex = s->peer.in6.sin6_scope_id; - - /* Check consistency */ - assert(s->peer.sa.sa_family == s->local.sa.sa_family); - assert(IN_SET(s->peer.sa.sa_family, AF_INET, AF_INET6)); - - /* Query connection meta information */ - sl = sizeof(control); - if (s->peer.sa.sa_family == AF_INET) { - r = getsockopt(s->fd, IPPROTO_IP, IP_PKTOPTIONS, &control, &sl); - if (r < 0) - return -errno; - } else if (s->peer.sa.sa_family == AF_INET6) { - - r = getsockopt(s->fd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, &control, &sl); - if (r < 0) - return -errno; - } else - return -EAFNOSUPPORT; - - mh.msg_control = &control; - mh.msg_controllen = sl; - - CMSG_FOREACH(cmsg, &mh) { - - if (cmsg->cmsg_level == IPPROTO_IPV6) { - assert(s->peer.sa.sa_family == AF_INET6); - - switch (cmsg->cmsg_type) { - - case IPV6_PKTINFO: { - struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); - - if (s->ifindex <= 0) - s->ifindex = i->ipi6_ifindex; - break; - } - - case IPV6_HOPLIMIT: - s->ttl = *(int *) CMSG_DATA(cmsg); - break; - } - - } else if (cmsg->cmsg_level == IPPROTO_IP) { - assert(s->peer.sa.sa_family == AF_INET); - - switch (cmsg->cmsg_type) { - - case IP_PKTINFO: { - struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); - - if (s->ifindex <= 0) - s->ifindex = i->ipi_ifindex; - break; - } - - case IP_TTL: - s->ttl = *(int *) CMSG_DATA(cmsg); - break; - } - } - } - - /* The Linux kernel sets the interface index to the loopback - * device if the connection came from the local host since it - * avoids the routing table in such a case. Let's unset the - * interface index in such a case. */ - if (s->ifindex == LOOPBACK_IFINDEX) - s->ifindex = 0; - - /* If we don't know the interface index still, we look for the - * first local interface with a matching address. Yuck! */ - if (s->ifindex <= 0) - s->ifindex = manager_find_ifindex(s->manager, s->local.sa.sa_family, s->local.sa.sa_family == AF_INET ? (union in_addr_union*) &s->local.in.sin_addr : (union in_addr_union*) &s->local.in6.sin6_addr); - - if (s->protocol == DNS_PROTOCOL_LLMNR && s->ifindex > 0) { - uint32_t ifindex = htobe32(s->ifindex); - - /* Make sure all packets for this connection are sent on the same interface */ - if (s->local.sa.sa_family == AF_INET) { - r = setsockopt(s->fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex)); - if (r < 0) - log_debug_errno(errno, "Failed to invoke IP_UNICAST_IF: %m"); - } else if (s->local.sa.sa_family == AF_INET6) { - r = setsockopt(s->fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex)); - if (r < 0) - log_debug_errno(errno, "Failed to invoke IPV6_UNICAST_IF: %m"); - } - } - - s->identified = true; - - return 0; -} - -static int on_stream_timeout(sd_event_source *es, usec_t usec, void *userdata) { - DnsStream *s = userdata; - - assert(s); - - return dns_stream_complete(s, ETIMEDOUT); -} - -static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *userdata) { - DnsStream *s = userdata; - int r; - - assert(s); - - r = dns_stream_identify(s); - if (r < 0) - return dns_stream_complete(s, -r); - - if ((revents & EPOLLOUT) && - s->write_packet && - s->n_written < sizeof(s->write_size) + s->write_packet->size) { - - struct iovec iov[2]; - ssize_t ss; - - iov[0].iov_base = &s->write_size; - iov[0].iov_len = sizeof(s->write_size); - iov[1].iov_base = DNS_PACKET_DATA(s->write_packet); - iov[1].iov_len = s->write_packet->size; - - IOVEC_INCREMENT(iov, 2, s->n_written); - - ss = writev(fd, iov, 2); - if (ss < 0) { - if (errno != EINTR && errno != EAGAIN) - return dns_stream_complete(s, errno); - } else - s->n_written += ss; - - /* Are we done? If so, disable the event source for EPOLLOUT */ - if (s->n_written >= sizeof(s->write_size) + s->write_packet->size) { - r = dns_stream_update_io(s); - if (r < 0) - return dns_stream_complete(s, -r); - } - } - - if ((revents & (EPOLLIN|EPOLLHUP|EPOLLRDHUP)) && - (!s->read_packet || - s->n_read < sizeof(s->read_size) + s->read_packet->size)) { - - if (s->n_read < sizeof(s->read_size)) { - ssize_t ss; - - ss = read(fd, (uint8_t*) &s->read_size + s->n_read, sizeof(s->read_size) - s->n_read); - if (ss < 0) { - if (errno != EINTR && errno != EAGAIN) - return dns_stream_complete(s, errno); - } else if (ss == 0) - return dns_stream_complete(s, ECONNRESET); - else - s->n_read += ss; - } - - if (s->n_read >= sizeof(s->read_size)) { - - if (be16toh(s->read_size) < DNS_PACKET_HEADER_SIZE) - return dns_stream_complete(s, EBADMSG); - - if (s->n_read < sizeof(s->read_size) + be16toh(s->read_size)) { - ssize_t ss; - - if (!s->read_packet) { - r = dns_packet_new(&s->read_packet, s->protocol, be16toh(s->read_size)); - if (r < 0) - return dns_stream_complete(s, -r); - - s->read_packet->size = be16toh(s->read_size); - s->read_packet->ipproto = IPPROTO_TCP; - s->read_packet->family = s->peer.sa.sa_family; - s->read_packet->ttl = s->ttl; - s->read_packet->ifindex = s->ifindex; - - if (s->read_packet->family == AF_INET) { - s->read_packet->sender.in = s->peer.in.sin_addr; - s->read_packet->sender_port = be16toh(s->peer.in.sin_port); - s->read_packet->destination.in = s->local.in.sin_addr; - s->read_packet->destination_port = be16toh(s->local.in.sin_port); - } else { - assert(s->read_packet->family == AF_INET6); - s->read_packet->sender.in6 = s->peer.in6.sin6_addr; - s->read_packet->sender_port = be16toh(s->peer.in6.sin6_port); - s->read_packet->destination.in6 = s->local.in6.sin6_addr; - s->read_packet->destination_port = be16toh(s->local.in6.sin6_port); - - if (s->read_packet->ifindex == 0) - s->read_packet->ifindex = s->peer.in6.sin6_scope_id; - if (s->read_packet->ifindex == 0) - s->read_packet->ifindex = s->local.in6.sin6_scope_id; - } - } - - ss = read(fd, - (uint8_t*) DNS_PACKET_DATA(s->read_packet) + s->n_read - sizeof(s->read_size), - sizeof(s->read_size) + be16toh(s->read_size) - s->n_read); - if (ss < 0) { - if (errno != EINTR && errno != EAGAIN) - return dns_stream_complete(s, errno); - } else if (ss == 0) - return dns_stream_complete(s, ECONNRESET); - else - s->n_read += ss; - } - - /* Are we done? If so, disable the event source for EPOLLIN */ - if (s->n_read >= sizeof(s->read_size) + be16toh(s->read_size)) { - r = dns_stream_update_io(s); - if (r < 0) - return dns_stream_complete(s, -r); - - /* If there's a packet handler - * installed, call that. Note that - * this is optional... */ - if (s->on_packet) - return s->on_packet(s); - } - } - } - - if ((s->write_packet && s->n_written >= sizeof(s->write_size) + s->write_packet->size) && - (s->read_packet && s->n_read >= sizeof(s->read_size) + s->read_packet->size)) - return dns_stream_complete(s, 0); - - return 0; -} - -DnsStream *dns_stream_free(DnsStream *s) { - if (!s) - return NULL; - - dns_stream_stop(s); - - if (s->manager) { - LIST_REMOVE(streams, s->manager->dns_streams, s); - s->manager->n_dns_streams--; - } - - dns_packet_unref(s->write_packet); - dns_packet_unref(s->read_packet); - - free(s); - - return 0; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free); - -int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { - _cleanup_(dns_stream_freep) DnsStream *s = NULL; - int r; - - assert(m); - assert(fd >= 0); - - if (m->n_dns_streams > DNS_STREAMS_MAX) - return -EBUSY; - - s = new0(DnsStream, 1); - if (!s) - return -ENOMEM; - - s->fd = -1; - s->protocol = protocol; - - 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, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + DNS_STREAM_TIMEOUT_USEC, 0, - on_stream_timeout, s); - 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; - m->n_dns_streams++; - - *ret = s; - s = NULL; - - return 0; -} - -int dns_stream_write_packet(DnsStream *s, DnsPacket *p) { - assert(s); - - if (s->write_packet) - return -EBUSY; - - s->write_packet = dns_packet_ref(p); - s->write_size = htobe16(p->size); - s->n_written = 0; - - return dns_stream_update_io(s); -} diff --git a/src/resolve/resolved-dns-stream.h b/src/resolve/resolved-dns-stream.h deleted file mode 100644 index 5ccc842249..0000000000 --- a/src/resolve/resolved-dns-stream.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "socket-util.h" - -typedef struct DnsStream DnsStream; - -#include "resolved-dns-packet.h" -#include "resolved-dns-transaction.h" - -struct DnsStream { - Manager *manager; - - DnsProtocol protocol; - - int fd; - union sockaddr_union peer; - socklen_t peer_salen; - union sockaddr_union local; - socklen_t local_salen; - int ifindex; - uint32_t ttl; - bool identified; - - sd_event_source *io_event_source; - sd_event_source *timeout_event_source; - - be16_t write_size, read_size; - DnsPacket *write_packet, *read_packet; - size_t n_written, n_read; - - int (*on_packet)(DnsStream *s); - int (*complete)(DnsStream *s, int error); - - DnsTransaction *transaction; - - LIST_FIELDS(DnsStream, streams); -}; - -int dns_stream_new(Manager *m, DnsStream **s, DnsProtocol protocol, int fd); -DnsStream *dns_stream_free(DnsStream *s); - -int dns_stream_write_packet(DnsStream *s, DnsPacket *p); diff --git a/src/resolve/resolved-dns-synthesize.c b/src/resolve/resolved-dns-synthesize.c deleted file mode 100644 index f4a43dee8c..0000000000 --- a/src/resolve/resolved-dns-synthesize.c +++ /dev/null @@ -1,413 +0,0 @@ -/*** - 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 . -***/ - -#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 deleted file mode 100644 index 5d829bb2e7..0000000000 --- a/src/resolve/resolved-dns-synthesize.h +++ /dev/null @@ -1,30 +0,0 @@ -#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 . -***/ - -#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 deleted file mode 100644 index d48fdd1281..0000000000 --- a/src/resolve/resolved-dns-transaction.c +++ /dev/null @@ -1,3026 +0,0 @@ -/*** - 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 . -***/ - -#include - -#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" -#include "resolved-dns-transaction.h" -#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; - DnsTransaction *z; - - if (!t) - return NULL; - - log_debug("Freeing transaction %" PRIu16 ".", t->id); - - dns_transaction_close_connection(t); - dns_transaction_stop_timeout(t); - - dns_packet_unref(t->sent); - dns_transaction_reset_answer(t); - - dns_server_unref(t->server); - - if (t->scope) { - hashmap_remove_value(t->scope->transactions_by_key, t->key, t); - LIST_REMOVE(transactions_by_scope, t->scope->transactions, t); - - if (t->id != 0) - hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id)); - } - - while ((c = set_steal_first(t->notify_query_candidates))) - set_remove(c->transactions, t); - set_free(t->notify_query_candidates); - - while ((i = set_steal_first(t->notify_zone_items))) - i->probe_transaction = NULL; - set_free(t->notify_zone_items); - - while ((z = set_steal_first(t->notify_transactions))) - set_remove(z->dnssec_transactions, t); - set_free(t->notify_transactions); - - 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; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsTransaction*, dns_transaction_free); - -bool dns_transaction_gc(DnsTransaction *t) { - assert(t); - - if (t->block_gc > 0) - return true; - - if (set_isempty(t->notify_query_candidates) && - set_isempty(t->notify_zone_items) && - 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) { - _cleanup_(dns_transaction_freep) DnsTransaction *t = NULL; - int r; - - assert(ret); - assert(s); - assert(key); - - /* Don't allow looking up invalid or pseudo RRs */ - if (!dns_type_is_valid_query(key->type)) - return -EINVAL; - if (dns_type_is_obsolete(key->type)) - return -EOPNOTSUPP; - - /* We only support the IN class */ - if (key->class != DNS_CLASS_IN && key->class != DNS_CLASS_ANY) - return -EOPNOTSUPP; - - if (hashmap_size(s->manager->dns_transactions) >= TRANSACTIONS_MAX) - return -EBUSY; - - r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&s->transactions_by_key, &dns_resource_key_hash_ops); - if (r < 0) - return r; - - t = new0(DnsTransaction, 1); - if (!t) - return -ENOMEM; - - t->dns_udp_fd = -1; - t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; - t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; - t->answer_nsec_ttl = (uint32_t) -1; - t->key = dns_resource_key_ref(key); - t->current_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; - - t->id = pick_new_id(s->manager); - - r = hashmap_put(s->manager->dns_transactions, UINT_TO_PTR(t->id), t); - if (r < 0) { - t->id = 0; - return r; - } - - r = hashmap_replace(s->transactions_by_key, t->key, t); - if (r < 0) { - hashmap_remove(s->manager->dns_transactions, UINT_TO_PTR(t->id)); - return r; - } - - LIST_PREPEND(transactions_by_scope, s->transactions, t); - t->scope = s; - - s->manager->n_transactions_total ++; - - if (ret) - *ret = t; - - t = NULL; - - return 0; -} - -static void dns_transaction_shuffle_id(DnsTransaction *t) { - uint16_t new_id; - assert(t); - - /* Pick a new ID for this transaction. */ - - new_id = pick_new_id(t->scope->manager); - assert_se(hashmap_remove_and_put(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id), UINT_TO_PTR(new_id), t) >= 0); - - log_debug("Transaction %" PRIu16 " is now %" PRIu16 ".", t->id, new_id); - t->id = new_id; - - /* Make sure we generate a new packet with the new ID */ - t->sent = dns_packet_unref(t->sent); -} - -static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { - _cleanup_free_ char *pretty = NULL; - DnsZoneItem *z; - - assert(t); - assert(p); - - if (manager_our_packet(t->scope->manager, p) != 0) - return; - - in_addr_to_string(p->family, &p->sender, &pretty); - - 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), - pretty); - - /* RFC 4795, Section 4.1 says that the peer with the - * lexicographically smaller IP address loses */ - if (memcmp(&p->sender, &p->destination, FAMILY_ADDRESS_SIZE(p->family)) >= 0) { - log_debug("Peer has lexicographically larger IP address and thus lost in the conflict."); - return; - } - - log_debug("We have the lexicographically larger IP address and thus lost in the conflict."); - - t->block_gc++; - while ((z = set_first(t->notify_zone_items))) { - /* First, make sure the zone item drops the reference - * to us */ - dns_zone_item_probe_stop(z); - - /* Secondly, report this as conflict, so that we might - * look for a different hostname */ - dns_zone_item_conflict(z); - } - t->block_gc--; - - dns_transaction_gc(t); -} - -void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { - DnsQueryCandidate *c; - 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. */ - - 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), - st, - t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source), - t->answer_authenticated ? "authenticated" : "unsigned"); - - t->state = state; - - 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); - - 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; - - assert(s); - assert(s->transaction); - - /* Copy the data we care about out of the stream before we - * destroy it. */ - t = s->transaction; - p = dns_packet_ref(s->read_packet); - - 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) { - t->answer_errno = error; - dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); - return 0; - } - - if (dns_packet_validate_reply(p) <= 0) { - log_debug("Invalid TCP reply packet."); - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return 0; - } - - dns_scope_check_conflicts(t->scope, p); - - t->block_gc++; - dns_transaction_process_reply(t, p); - t->block_gc--; - - /* 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) { - _cleanup_close_ int fd = -1; - int r; - - assert(t); - - dns_transaction_close_connection(t); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_DNS: - 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_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; - - /* Otherwise, try to talk to the owner of a - * the IP address, in case this is a reverse - * PTR lookup */ - - r = dns_name_address(DNS_RESOURCE_KEY_NAME(t->key), &family, &address); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - if (family != t->scope->family) - return -ESRCH; - - fd = dns_scope_socket_tcp(t->scope, family, &address, NULL, LLMNR_PORT); - } - - break; - - default: - return -EAFNOSUPPORT; - } - - if (fd < 0) - return fd; - - 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); - if (r < 0) { - t->stream = dns_stream_free(t->stream); - return r; - } - - t->stream->complete = on_stream_complete; - t->stream->transaction = t; - - /* The interface index is difficult to determine if we are - * connecting to the local host, hence fill this in right away - * instead of determining it from the socket */ - if (t->scope->link) - t->stream->ifindex = t->scope->link->ifindex; - - dns_transaction_reset_answer(t); - - t->tried_stream = true; - - return 0; -} - -static void dns_transaction_cache_answer(DnsTransaction *t) { - assert(t); - - /* For mDNS we cache whenever we get the packet, rather than - * in each transaction. */ - if (!IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) - return; - - /* We never cache if this packet is from the local host, under - * the assumption that a locally running DNS server would - * cache this anyway, and probably knows better when to flush - * the cache then we could. */ - if (!DNS_PACKET_SHALL_CACHE(t->received)) - return; - - dns_cache_put(&t->scope->cache, - t->key, - t->answer_rcode, - t->answer, - 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. */ - 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 == -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->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_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) { - usec_t ts; - int r; - - assert(t); - assert(p); - 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. */ - - log_debug("Processing incoming packet on transaction %" PRIu16".", t->id); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_LLMNR: - assert(t->scope->link); - - /* For LLMNR we will not accept any packets from other - * interfaces */ - - if (p->ifindex != t->scope->link->ifindex) - return; - - if (p->family != t->scope->family) - return; - - /* Tentative packets are not full responses but still - * useful for identifying uniqueness conflicts during - * probing. */ - if (DNS_PACKET_LLMNR_T(p)) { - dns_transaction_tentative(t, p); - return; - } - - break; - - case DNS_PROTOCOL_MDNS: - assert(t->scope->link); - - /* For mDNS we will not accept any packets from other interfaces */ - if (p->ifindex != t->scope->link->ifindex) - return; - - if (p->family != t->scope->family) - return; - - 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: - assert_not_reached("Invalid DNS protocol."); - } - - if (t->received != p) { - dns_packet_unref(t->received); - t->received = dns_packet_ref(p); - } - - t->answer_source = DNS_TRANSACTION_NETWORK; - - if (p->ipproto == IPPROTO_TCP) { - if (DNS_PACKET_TC(p)) { - /* Truncated via TCP? Somebody must be fucking with us */ - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - - if (DNS_PACKET_ID(p) != t->id) { - /* Not the reply to our query? Somebody must be fucking with us */ - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - } - - assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_DNS: - assert(t->server); - - if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) { - - /* 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_feature_level); - dns_transaction_retry(t); - return; - } else if (DNS_PACKET_TC(p)) - dns_server_packet_truncated(t->server, t->current_feature_level); - - break; - - case DNS_PROTOCOL_LLMNR: - case DNS_PROTOCOL_MDNS: - dns_scope_packet_received(t->scope, ts - t->start_usec); - break; - - default: - assert_not_reached("Invalid DNS protocol."); - } - - if (DNS_PACKET_TC(p)) { - - /* Truncated packets for mDNS are not allowed. Give up immediately. */ - if (t->scope->protocol == DNS_PROTOCOL_MDNS) { - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - 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) { - /* No servers found? Damn! */ - 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_DNS) - goto fail; - - /* On DNS, couldn't send? Try immediately again, with a new server */ - dns_transaction_retry(t); - } - - return; - } - - /* 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) - goto fail; - if (r == 0) { - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - - /* Install the answer as answer to the transaction */ - dns_answer_unref(t->answer); - t->answer = dns_answer_ref(p->answer); - t->answer_rcode = DNS_PACKET_RCODE(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); - 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) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - DnsTransaction *t = userdata; - int r; - - assert(t); - assert(t->scope); - - r = manager_recv(t->scope->manager, fd, DNS_PROTOCOL_DNS, &p); - if (ERRNO_IS_DISCONNECT(-r)) { - usec_t usec; - - /* 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_udp(DnsTransaction *t) { - int r; - - assert(t); - - if (t->scope->protocol == DNS_PROTOCOL_DNS) { - - r = dns_transaction_pick_server(t); - if (r < 0) - return r; - - if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_UDP) - return -EAGAIN; - - if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) - return -EOPNOTSUPP; - - if (r > 0 || t->dns_udp_fd < 0) { /* Server changed, or no connection yet. */ - int fd; - - 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; - - dns_transaction_reset_answer(t); - - return 0; -} - -static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) { - DnsTransaction *t = userdata; - - assert(s); - assert(t); - - 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->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."); - } - - if (t->initial_jitter_scheduled) - t->initial_jitter_elapsed = true; - } - - log_debug("Timeout reached on transaction %" PRIu16 ".", t->id); - - dns_transaction_retry(t); - return 0; -} - -static usec_t transaction_get_resend_timeout(DnsTransaction *t) { - assert(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) { - int r; - - assert(t); - - dns_transaction_stop_timeout(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 && 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); - return 0; - } - - t->n_attempts++; - t->start_usec = ts; - - dns_transaction_reset_answer(t); - dns_transaction_flush_dnssec_transactions(t); - - /* Check the trust anchor. Do so only on classic DNS, since DNSSEC does not apply otherwise. */ - if (t->scope->protocol == DNS_PROTOCOL_DNS) { - r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, t->key, &t->answer); - if (r < 0) - return r; - if (r > 0) { - t->answer_rcode = DNS_RCODE_SUCCESS; - t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR; - t->answer_authenticated = true; - 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 - * for probing or verifying a zone item. */ - if (set_isempty(t->notify_zone_items)) { - - r = dns_zone_lookup(&t->scope->zone, t->key, &t->answer, NULL, NULL); - if (r < 0) - return r; - if (r > 0) { - t->answer_rcode = DNS_RCODE_SUCCESS; - t->answer_source = DNS_TRANSACTION_ZONE; - t->answer_authenticated = true; - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - return 0; - } - } - - /* Check the cache, but only if this transaction is not used - * for probing or verifying a zone item. */ - if (set_isempty(t->notify_zone_items)) { - - /* Before trying the cache, let's make sure we figured out a - * server to use. Should this cause a change of server this - * might flush the cache. */ - dns_scope_get_dns_server(t->scope); - - /* Let's then prune all outdated entries */ - dns_cache_prune(&t->scope->cache); - - r = dns_cache_lookup(&t->scope->cache, t->key, &t->answer_rcode, &t->answer, &t->answer_authenticated); - if (r < 0) - return r; - if (r > 0) { - t->answer_source = DNS_TRANSACTION_CACHE; - if (t->answer_rcode == DNS_RCODE_SUCCESS) - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - else - dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); - return 0; - } - } - - return 1; -} - -static int dns_transaction_make_packet_mdns(DnsTransaction *t) { - - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - bool add_known_answers = false; - DnsTransaction *other; - unsigned qdcount; - usec_t ts; - int r; - - assert(t); - assert(t->scope->protocol == DNS_PROTOCOL_MDNS); - - /* 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); - if (r < 0) - return r; - - r = dns_packet_append_key(p, t->key, NULL); - if (r < 0) - return r; - - qdcount = 1; - - if (dns_key_is_shared(t->key)) - add_known_answers = true; - - /* - * For mDNS, we want to coalesce as many open queries in pending transactions into one single - * query packet on the wire as possible. To achieve that, we iterate through all pending transactions - * in our current scope, and see whether their timing contraints allow them to be sent. - */ - - assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - - LIST_FOREACH(transactions_by_scope, other, t->scope->transactions) { - - /* Skip ourselves */ - if (other == t) - continue; - - if (other->state != DNS_TRANSACTION_PENDING) - continue; - - if (other->next_attempt_after > ts) - continue; - - if (qdcount >= UINT16_MAX) - break; - - r = dns_packet_append_key(p, other->key, NULL); - - /* - * If we can't stuff more questions into the packet, just give up. - * One of the 'other' transactions will fire later and take care of the rest. - */ - if (r == -EMSGSIZE) - break; - - if (r < 0) - return r; - - r = dns_transaction_prepare(other, ts); - if (r <= 0) - continue; - - ts += transaction_get_resend_timeout(other); - - r = sd_event_add_time( - other->scope->manager->event, - &other->timeout_event_source, - clock_boottime_or_monotonic(), - ts, 0, - on_transaction_timeout, other); - 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; - - qdcount ++; - - if (dns_key_is_shared(other->key)) - add_known_answers = true; - } - - DNS_PACKET_HEADER(p)->qdcount = htobe16(qdcount); - - /* Append known answer section if we're asking for any shared record */ - if (add_known_answers) { - r = dns_cache_export_shared_to_packet(&t->scope->cache, p); - if (r < 0) - return r; - } - - t->sent = p; - p = NULL; - - return 0; -} - -static int dns_transaction_make_packet(DnsTransaction *t) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - int r; - - assert(t); - - if (t->scope->protocol == DNS_PROTOCOL_MDNS) - return dns_transaction_make_packet_mdns(t); - - if (t->sent) - return 0; - - r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode != DNSSEC_NO); - if (r < 0) - return r; - - r = dns_packet_append_key(p, t->key, NULL); - if (r < 0) - return r; - - DNS_PACKET_HEADER(p)->qdcount = htobe16(1); - DNS_PACKET_HEADER(p)->id = t->id; - - t->sent = p; - p = NULL; - - return 0; -} - -int dns_transaction_go(DnsTransaction *t) { - usec_t ts; - int r; - - assert(t); - - assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - - r = dns_transaction_prepare(t, ts); - if (r <= 0) - return r; - - 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 || - t->scope->protocol == DNS_PROTOCOL_MDNS)) { - usec_t jitter, accuracy; - - /* RFC 4795 Section 2.7 suggests all queries should be - * delayed by a random time from 0 to JITTER_INTERVAL. */ - - t->initial_jitter_scheduled = true; - - 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; - accuracy = MDNS_JITTER_RANGE_USEC; - break; - default: - assert_not_reached("bad protocol"); - } - - r = sd_event_add_time( - t->scope->manager->event, - &t->timeout_event_source, - clock_boottime_or_monotonic(), - ts + jitter, accuracy, - on_transaction_timeout, 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; - - log_debug("Delaying %s transaction for " USEC_FMT "us.", dns_protocol_to_string(t->scope->protocol), jitter); - return 0; - } - - /* Otherwise, we need to ask the network */ - r = dns_transaction_make_packet(t); - if (r < 0) - return r; - - if (t->scope->protocol == DNS_PROTOCOL_LLMNR && - (dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), "in-addr.arpa") > 0 || - dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), "ip6.arpa") > 0)) { - - /* RFC 4795, Section 2.4. says reverse lookups shall - * always be made via TCP on LLMNR */ - r = dns_transaction_open_tcp(t); - } else { - /* Try via UDP, and if that fails due to large size or lack of - * support try via TCP */ - r = dns_transaction_emit_udp(t); - if (r == -EMSGSIZE) - log_debug("Sending query via TCP since it is too large."); - if (r == -EAGAIN) - log_debug("Sending query via TCP since server doesn't support UDP."); - if (r == -EMSGSIZE || r == -EAGAIN) - r = dns_transaction_open_tcp(t); - } - - if (r == -ESRCH) { - /* No servers to send this to? */ - dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); - 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_scope_next_dns_server(t->scope); - - return dns_transaction_go(t); - } - - ts += transaction_get_resend_timeout(t); - - r = sd_event_add_time( - t->scope->manager->event, - &t->timeout_event_source, - clock_boottime_or_monotonic(), - ts, 0, - on_transaction_timeout, 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; - - assert(t); - assert(ret); - assert(key); - - aux = dns_scope_find_transaction(t->scope, key, true); - if (!aux) { - r = dns_transaction_new(&aux, t->scope, key); - if (r < 0) - return r; - } else { - if (set_contains(t->dnssec_transactions, aux)) { - *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); - if (r < 0) - goto gc; - - r = set_ensure_allocated(&aux->notify_transactions, NULL); - if (r < 0) - goto gc; - - r = set_put(t->dnssec_transactions, aux); - if (r < 0) - goto gc; - - r = set_put(aux->notify_transactions, t); - if (r < 0) { - (void) set_remove(t->dnssec_transactions, aux); - goto gc; - } - - *ret = aux; - return 1; - -gc: - dns_transaction_gc(aux); - return r; -} - -static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *key) { - _cleanup_(dns_answer_unrefp) DnsAnswer *a = NULL; - DnsTransaction *aux; - int r; - - assert(t); - assert(key); - - /* Try to get the data from the trust anchor */ - r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, key, &a); - if (r < 0) - return r; - if (r > 0) { - r = dns_answer_extend(&t->validated_keys, a); - if (r < 0) - return r; - - return 0; - } - - /* 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; - - if (aux->state == DNS_TRANSACTION_NULL) { - r = dns_transaction_go(aux); - if (r < 0) - return r; - } - - 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); - - /* - * 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: { - /* For each RRSIG we request the matching DNSKEY */ - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *dnskey = NULL; - - /* If this RRSIG is about a DNSKEY RR and the - * signer is the same as the owner, then we - * already have the DNSKEY, and we don't have - * to look for more. */ - if (rr->rrsig.type_covered == DNS_TYPE_DNSKEY) { - r = dns_name_equal(rr->rrsig.signer, DNS_RESOURCE_KEY_NAME(rr->key)); - if (r < 0) - return r; - if (r > 0) - continue; - } - - /* 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) - continue; - - dnskey = dns_resource_key_new(rr->key->class, DNS_TYPE_DNSKEY, rr->rrsig.signer); - if (!dnskey) - return -ENOMEM; - - 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; - break; - } - - case DNS_TYPE_DNSKEY: { - /* 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" (%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; - }} - } - - /* 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(rr); - - /* Checks if the RR we are looking for must be signed with an - * RRSIG. This is used for positive responses. */ - - 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; - } - - default: { - DnsTransaction *dt; - Iterator i; - - /* 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; - DnsAnswerFlags flags; - int r; - - assert(t); - - /* We have now collected all DS and DNSKEY RRs in - * t->validated_keys, let's see which RRs we can now - * authenticate with that. */ - - if (t->scope->dnssec_mode == DNSSEC_NO) - return 0; - - /* Already validated */ - 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->answer_dnssec_result = DNSSEC_VALIDATED; - t->answer_authenticated = true; - return 0; - } - - /* Cached stuff is not affected by validation. */ - if (t->answer_source != DNS_TRANSACTION_NETWORK) - return 0; - - 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; - } - - log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, dns_transaction_key_string(t)); - - /* First, see if this response contains any revoked trust - * anchors we care about */ - r = dns_transaction_check_revoked_trust_anchors(t); - if (r < 0) - return r; - - /* Third, copy all RRs we acquired successfully from auxiliary RRs over. */ - r = dns_transaction_copy_validated(t); - if (r < 0) - return r; - - /* Second, see if there are DNSKEYs we already know a - * validated DS for. */ - r = dns_transaction_validate_dnskey_by_ds(t); - if (r < 0) - return r; - - /* Fourth, remove all DNSKEY and DS RRs again that our trust - * anchor says are revoked. After all we might have marked - * some keys revoked above, but they might still be lingering - * in our validated_keys list. */ - r = dns_transaction_invalidate_revoked_keys(t); - if (r < 0) - return r; - - phase = PHASE_DNSKEY; - for (;;) { - bool changed = false, have_nsec = false; - - DNS_ANSWER_FOREACH(rr, t->answer) { - DnsResourceRecord *rrsig = NULL; - DnssecResult result; - - switch (rr->key->type) { - - case DNS_TYPE_RRSIG: - continue; - - case DNS_TYPE_DNSKEY: - /* We validate DNSKEYs only in the DNSKEY and ALL phases */ - if (phase == PHASE_NSEC) - continue; - break; - - case DNS_TYPE_NSEC: - case DNS_TYPE_NSEC3: - have_nsec = true; - - /* We validate NSEC/NSEC3 only in the NSEC and ALL phases */ - if (phase == PHASE_DNSKEY) - continue; - - break; - - default: - /* We validate all other RRs only in the ALL phases */ - if (phase != PHASE_ALL) - continue; - - break; - } - - r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result, &rrsig); - if (r < 0) - return r; - - log_debug("Looking at %s: %s", strna(dns_resource_record_to_string(rr)), dnssec_result_to_string(result)); - - if (result == DNSSEC_VALIDATED) { - - if (rr->key->type == DNS_TYPE_DNSKEY) { - /* If we just validated a - * DNSKEY RRset, then let's - * add these keys to the set - * of validated keys for this - * transaction. */ - - r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - /* some of the DNSKEYs we just - * added might already have - * been revoked, remove them - * again in that case. */ - r = dns_transaction_invalidate_revoked_keys(t); - if (r < 0) - return r; - } - - /* Add the validated RRset to the new - * 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; - - if (result == DNSSEC_VALIDATED_WILDCARD) { - bool authenticated = false; - const char *source; - - /* 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; - - 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; - } - - r = dns_transaction_known_signed(t, rr); - if (r < 0) - return r; - if (r > 0) { - /* This is an RR we know has to be signed. If it isn't this means - * the server is not attaching RRSIGs, hence complain. */ - - 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; - } - - r = dns_transaction_in_private_tld(t, rr->key); - if (r < 0) - return r; - if (r > 0) { - _cleanup_free_ char *s = NULL; - - /* The data is from a TLD that is proven not to exist, and we are in downgrade - * mode, hence ignore the fact that this was not signed. */ - - (void) dns_resource_key_to_string(rr->key, &s); - log_info("Detected RRset %s is in a private DNS zone, permitting unsigned RRs.", strna(s ? strstrip(s) : NULL)); - - r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); - changed = true; - break; - } - } - - if (IN_SET(result, - DNSSEC_MISSING_KEY, - DNSSEC_SIGNATURE_EXPIRED, - DNSSEC_UNSUPPORTED_ALGORITHM)) { - - r = dns_transaction_dnskey_authenticated(t, rr); - if (r < 0 && r != -ENXIO) - return r; - if (r == 0) { - /* The DNSKEY transaction was not authenticated, this means there's - * no DS for this, which means it's OK if no keys are found for this signature. */ - - r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); - if (r < 0) - return r; - - 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) { - /* Also look among the stuff we already validated */ - r = dns_answer_has_dname_for_cname(validated, rr); - if (r < 0) - return r; - } - - if (r == 0) { - 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; - } - - /* This is a primary response, but we do have a DNAME RR in the RR that can replay this - * CNAME, hence rely on that, and we can remove the CNAME in favour of it. */ - } - - /* This is just some auxiliary data. Just remove the RRset and continue. */ - r = dns_answer_remove_by_key(&t->answer, rr->key); - if (r < 0) - return r; - - /* Exit the loop, we dropped something from the answer, start from the beginning */ - changed = true; - break; - } - - /* Restart the inner loop as long as we managed to achieve something */ - if (changed) - continue; - - if (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; - } - - dns_answer_unref(t->answer); - t->answer = validated; - validated = NULL; - - /* 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_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_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); - -static const char* const dns_transaction_source_table[_DNS_TRANSACTION_SOURCE_MAX] = { - [DNS_TRANSACTION_NETWORK] = "network", - [DNS_TRANSACTION_CACHE] = "cache", - [DNS_TRANSACTION_ZONE] = "zone", - [DNS_TRANSACTION_TRUST_ANCHOR] = "trust-anchor", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_transaction_source, DnsTransactionSource); diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h deleted file mode 100644 index 4617194711..0000000000 --- a/src/resolve/resolved-dns-transaction.h +++ /dev/null @@ -1,177 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -typedef struct DnsTransaction DnsTransaction; -typedef enum DnsTransactionState DnsTransactionState; -typedef enum DnsTransactionSource DnsTransactionSource; - -enum DnsTransactionState { - DNS_TRANSACTION_NULL, - DNS_TRANSACTION_PENDING, - DNS_TRANSACTION_VALIDATING, - 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_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 -}; - -#define DNS_TRANSACTION_IS_LIVE(state) IN_SET((state), DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING) - -enum DnsTransactionSource { - DNS_TRANSACTION_NETWORK, - DNS_TRANSACTION_CACHE, - DNS_TRANSACTION_ZONE, - DNS_TRANSACTION_TRUST_ANCHOR, - _DNS_TRANSACTION_SOURCE_MAX, - _DNS_TRANSACTION_SOURCE_INVALID = -1 -}; - -#include "resolved-dns-answer.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-question.h" -#include "resolved-dns-scope.h" - -struct DnsTransaction { - DnsScope *scope; - - DnsResourceKey *key; - char *key_string; - - DnsTransactionState state; - - uint16_t id; - - 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 DNSKEY, DS, SOA RRs we already verified and need - * to authenticate this reply */ - DnsAnswer *validated_keys; - - usec_t start_usec; - usec_t next_attempt_after; - 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_feature_level; - - /* Query candidates this transaction is referenced by and that - * shall be notified about this specific transaction - * completing. */ - Set *notify_query_candidates; - - /* Zone items this transaction is referenced by and that shall - * be notified about completion. */ - Set *notify_zone_items; - - /* Other transactions that this transactions is referenced by - * and that shall be notified about completion. This is used - * when transactions want to validate their RRsets, but need - * another DNSKEY or DS RR to do so. */ - Set *notify_transactions; - - /* The opposite direction: the transactions this transaction - * created in order to request DNSKEY or DS RRs. */ - Set *dnssec_transactions; - - unsigned block_gc; - - LIST_FIELDS(DnsTransaction, transactions_by_scope); -}; - -int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key); -DnsTransaction* dns_transaction_free(DnsTransaction *t); - -bool dns_transaction_gc(DnsTransaction *t); -int dns_transaction_go(DnsTransaction *t); - -void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p); -void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state); - -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_; - -const char* dns_transaction_source_to_string(DnsTransactionSource p) _const_; -DnsTransactionSource dns_transaction_source_from_string(const char *s) _pure_; - -/* LLMNR Jitter interval, see RFC 4795 Section 7 */ -#define LLMNR_JITTER_INTERVAL_USEC (100 * USEC_PER_MSEC) - -/* mDNS Jitter interval, see RFC 6762 Section 5.2 */ -#define MDNS_JITTER_MIN_USEC (20 * USEC_PER_MSEC) -#define MDNS_JITTER_RANGE_USEC (100 * USEC_PER_MSEC) - -/* Maximum attempts to send DNS requests, across all DNS servers */ -#define DNS_TRANSACTION_ATTEMPTS_MAX 16 - -/* Maximum attempts to send LLMNR requests, see RFC 4795 Section 2.7 */ -#define LLMNR_TRANSACTION_ATTEMPTS_MAX 3 - -#define TRANSACTION_ATTEMPTS_MAX(p) ((p) == DNS_PROTOCOL_LLMNR ? LLMNR_TRANSACTION_ATTEMPTS_MAX : DNS_TRANSACTION_ATTEMPTS_MAX) diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c deleted file mode 100644 index a75337eb6a..0000000000 --- a/src/resolve/resolved-dns-trust-anchor.c +++ /dev/null @@ -1,743 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include - -#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, retrieved December 2015 */ -static const uint8_t root_digest[] = - { 0x49, 0xAA, 0xC1, 0x1D, 0x7B, 0x6F, 0x64, 0x46, 0x70, 0x2E, 0x54, 0xA1, 0x60, 0x73, 0x71, 0x60, - 0x7A, 0x1A, 0x41, 0x85, 0x52, 0x00, 0xFD, 0x2C, 0xE1, 0xCD, 0xDE, 0x32, 0xF2, 0x4E, 0x8F, 0xB5 }; - -static 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->positive_by_key, &dns_resource_key_hash_ops); - if (r < 0) - return r; - - /* 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 */ - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, ""); - if (!rr) - return -ENOMEM; - - rr->ds.key_tag = 19036; - rr->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; - rr->ds.digest_type = DNSSEC_DIGEST_SHA256; - rr->ds.digest_size = sizeof(root_digest); - rr->ds.digest = memdup(root_digest, rr->ds.digest_size); - if (!rr->ds.digest) - return -ENOMEM; - - answer = dns_answer_new(1); - if (!answer) - return -ENOMEM; - - 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; - - 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->positive_by_key))) - dns_answer_unref(a); - d->positive_by_key = hashmap_free(d->positive_by_key); - - while ((rr = set_steal_first(d->revoked_by_rr))) - dns_resource_record_unref(rr); - d->revoked_by_rr = set_free(d->revoked_by_rr); - - d->negative_by_name = set_free_free(d->negative_by_name); -} - -int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey *key, DnsAnswer **ret) { - DnsAnswer *a; - - assert(d); - assert(key); - assert(ret); - - /* We only serve DS and DNSKEY RRs. */ - if (!IN_SET(key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY)) - return 0; - - 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 deleted file mode 100644 index 635c75fde5..0000000000 --- a/src/resolve/resolved-dns-trust-anchor.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -typedef struct DnsTrustAnchor DnsTrustAnchor; - -#include "hashmap.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-rr.h" - -/* This contains a fixed database mapping domain names to DS or DNSKEY records. */ - -struct DnsTrustAnchor { - 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_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 deleted file mode 100644 index f52383cfd1..0000000000 --- a/src/resolve/resolved-dns-zone.c +++ /dev/null @@ -1,656 +0,0 @@ -/*** - 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 . -***/ - -#include "alloc-util.h" -#include "dns-domain.h" -#include "list.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-zone.h" -#include "string-util.h" - -/* Never allow more than 1K entries */ -#define ZONE_MAX 1024 - -void dns_zone_item_probe_stop(DnsZoneItem *i) { - DnsTransaction *t; - assert(i); - - if (!i->probe_transaction) - return; - - t = i->probe_transaction; - i->probe_transaction = NULL; - - set_remove(t->notify_zone_items, i); - dns_transaction_gc(t); -} - -static void dns_zone_item_free(DnsZoneItem *i) { - if (!i) - return; - - dns_zone_item_probe_stop(i); - dns_resource_record_unref(i->rr); - - free(i); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free); - -static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) { - DnsZoneItem *first; - - assert(z); - - if (!i) - return; - - first = hashmap_get(z->by_key, i->rr->key); - LIST_REMOVE(by_key, first, i); - if (first) - assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0); - else - hashmap_remove(z->by_key, i->rr->key); - - first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key)); - LIST_REMOVE(by_name, first, i); - if (first) - assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0); - else - hashmap_remove(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key)); - - dns_zone_item_free(i); -} - -void dns_zone_flush(DnsZone *z) { - DnsZoneItem *i; - - assert(z); - - while ((i = hashmap_first(z->by_key))) - dns_zone_item_remove_and_free(z, i); - - assert(hashmap_size(z->by_key) == 0); - assert(hashmap_size(z->by_name) == 0); - - z->by_key = hashmap_free(z->by_key); - z->by_name = hashmap_free(z->by_name); -} - -static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) { - DnsZoneItem *i; - - assert(z); - assert(rr); - - LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key)) - if (dns_resource_record_equal(i->rr, rr) > 0) - return i; - - return NULL; -} - -void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) { - DnsZoneItem *i; - - assert(z); - assert(rr); - - i = dns_zone_get(z, rr); - if (i) - dns_zone_item_remove_and_free(z, i); -} - -static int dns_zone_init(DnsZone *z) { - int r; - - assert(z); - - r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops); - if (r < 0) - return r; - - return 0; -} - -static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) { - DnsZoneItem *first; - int r; - - first = hashmap_get(z->by_key, i->rr->key); - if (first) { - LIST_PREPEND(by_key, first, i); - assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0); - } else { - r = hashmap_put(z->by_key, i->rr->key, i); - if (r < 0) - return r; - } - - first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key)); - if (first) { - LIST_PREPEND(by_name, first, i); - assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0); - } else { - r = hashmap_put(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key), i); - if (r < 0) - return r; - } - - return 0; -} - -static int dns_zone_item_probe_start(DnsZoneItem *i) { - DnsTransaction *t; - int r; - - assert(i); - - if (i->probe_transaction) - return 0; - - t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key)), false); - if (!t) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key)); - if (!key) - return -ENOMEM; - - r = dns_transaction_new(&t, i->scope, key); - if (r < 0) - return r; - } - - r = set_ensure_allocated(&t->notify_zone_items, NULL); - if (r < 0) - goto gc; - - r = set_put(t->notify_zone_items, i); - if (r < 0) - goto gc; - - i->probe_transaction = t; - - if (t->state == DNS_TRANSACTION_NULL) { - - i->block_ready++; - r = dns_transaction_go(t); - i->block_ready--; - - if (r < 0) { - dns_zone_item_probe_stop(i); - return r; - } - } - - dns_zone_item_notify(i); - return 0; - -gc: - dns_transaction_gc(t); - return r; -} - -int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) { - _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL; - DnsZoneItem *existing; - int r; - - assert(z); - assert(s); - assert(rr); - - if (dns_class_is_pseudo(rr->key->class)) - return -EINVAL; - if (dns_type_is_pseudo(rr->key->type)) - return -EINVAL; - - existing = dns_zone_get(z, rr); - if (existing) - return 0; - - r = dns_zone_init(z); - if (r < 0) - return r; - - i = new0(DnsZoneItem, 1); - if (!i) - return -ENOMEM; - - i->scope = s; - i->rr = dns_resource_record_ref(rr); - i->probing_enabled = probe; - - r = dns_zone_link_item(z, i); - if (r < 0) - return r; - - if (probe) { - DnsZoneItem *first, *j; - bool established = false; - - /* Check if there's already an RR with the same name - * established. If so, it has been probed already, and - * we don't ned to probe again. */ - - LIST_FIND_HEAD(by_name, i, first); - LIST_FOREACH(by_name, j, first) { - if (i == j) - continue; - - if (j->state == DNS_ZONE_ITEM_ESTABLISHED) - established = true; - } - - if (established) - i->state = DNS_ZONE_ITEM_ESTABLISHED; - else { - i->state = DNS_ZONE_ITEM_PROBING; - - r = dns_zone_item_probe_start(i); - if (r < 0) { - dns_zone_item_remove_and_free(z, i); - i = NULL; - return r; - } - } - } else - i->state = DNS_ZONE_ITEM_ESTABLISHED; - - i = NULL; - return 0; -} - -int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) { - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; - unsigned n_answer = 0; - DnsZoneItem *j, *first; - bool tentative = true, need_soa = false; - int r; - - assert(z); - assert(key); - assert(ret_answer); - - /* First iteration, count what we have */ - - if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { - bool found = false, added = false; - int k; - - /* If this is a generic match, then we have to - * go through the list by the name and look - * for everything manually */ - - first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key)); - LIST_FOREACH(by_name, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - found = true; - - k = dns_resource_key_match_rr(key, j->rr, NULL); - if (k < 0) - return k; - if (k > 0) { - n_answer++; - added = true; - } - - } - - if (found && !added) - need_soa = true; - - } else { - bool found = false; - - /* If this is a specific match, then look for - * the right key immediately */ - - first = hashmap_get(z->by_key, key); - LIST_FOREACH(by_key, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - found = true; - n_answer++; - } - - if (!found) { - first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key)); - LIST_FOREACH(by_name, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - need_soa = true; - break; - } - } - } - - if (n_answer <= 0 && !need_soa) - goto return_empty; - - if (n_answer > 0) { - answer = dns_answer_new(n_answer); - if (!answer) - return -ENOMEM; - } - - if (need_soa) { - soa = dns_answer_new(1); - if (!soa) - return -ENOMEM; - } - - /* Second iteration, actually add the RRs to the answers */ - if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { - bool found = false, added = false; - int k; - - first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key)); - LIST_FOREACH(by_name, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - found = true; - - if (j->state != DNS_ZONE_ITEM_PROBING) - tentative = false; - - k = dns_resource_key_match_rr(key, j->rr, NULL); - if (k < 0) - return k; - if (k > 0) { - r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - added = true; - } - } - - if (found && !added) { - r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(key), LLMNR_DEFAULT_TTL); - if (r < 0) - return r; - } - } else { - bool found = false; - - first = hashmap_get(z->by_key, key); - LIST_FOREACH(by_key, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - found = true; - - if (j->state != DNS_ZONE_ITEM_PROBING) - tentative = false; - - r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - if (!found) { - bool add_soa = false; - - first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key)); - LIST_FOREACH(by_name, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - if (j->state != DNS_ZONE_ITEM_PROBING) - tentative = false; - - add_soa = true; - } - - if (add_soa) { - r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(key), LLMNR_DEFAULT_TTL); - if (r < 0) - return r; - } - } - } - - /* If the caller sets ret_tentative to NULL, then use this as - * indication to not return tentative entries */ - - if (!ret_tentative && tentative) - goto return_empty; - - *ret_answer = answer; - answer = NULL; - - if (ret_soa) { - *ret_soa = soa; - soa = NULL; - } - - if (ret_tentative) - *ret_tentative = tentative; - - return 1; - -return_empty: - *ret_answer = NULL; - - if (ret_soa) - *ret_soa = NULL; - - if (ret_tentative) - *ret_tentative = false; - - return 0; -} - -void dns_zone_item_conflict(DnsZoneItem *i) { - assert(i); - - if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED)) - return; - - log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr))); - - dns_zone_item_probe_stop(i); - - /* Withdraw the conflict item */ - i->state = DNS_ZONE_ITEM_WITHDRAWN; - - /* Maybe change the hostname */ - if (manager_is_own_hostname(i->scope->manager, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0) - manager_next_hostname(i->scope->manager); -} - -void dns_zone_item_notify(DnsZoneItem *i) { - assert(i); - assert(i->probe_transaction); - - if (i->block_ready > 0) - return; - - if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)) - return; - - if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) { - bool we_lost = false; - - /* The probe got a successful reply. If we so far - * weren't established we just give up. If we already - * were established, and the peer has the - * lexicographically larger IP address we continue - * and defend it. */ - - if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) { - log_debug("Got a successful probe for not yet established RR, we lost."); - we_lost = true; - } else { - assert(i->probe_transaction->received); - we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0; - if (we_lost) - log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost."); - } - - if (we_lost) { - dns_zone_item_conflict(i); - return; - } - - log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost."); - } - - 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) { - int r; - - assert(i); - - if (i->state != DNS_ZONE_ITEM_ESTABLISHED) - return 0; - - 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); - if (r < 0) { - log_error_errno(r, "Failed to start probing for verifying RR: %m"); - i->state = DNS_ZONE_ITEM_ESTABLISHED; - return r; - } - - return 0; -} - -int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) { - DnsZoneItem *i, *first; - int c = 0; - - assert(zone); - assert(rr); - - /* This checks whether a response RR we received from somebody - * else is one that we actually thought was uniquely ours. If - * so, we'll verify our RRs. */ - - /* No conflict if we don't have the name at all. */ - first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(rr->key)); - if (!first) - return 0; - - /* No conflict if we have the exact same RR */ - if (dns_zone_get(zone, rr)) - return 0; - - /* OK, somebody else has RRs for the same name. Yuck! Let's - * start probing again */ - - LIST_FOREACH(by_name, i, first) { - if (dns_resource_record_equal(i->rr, rr)) - continue; - - dns_zone_item_verify(i); - c++; - } - - return c; -} - -int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) { - DnsZoneItem *i, *first; - int c = 0; - - assert(zone); - - /* Somebody else notified us about a possible conflict. Let's - * verify if that's true. */ - - first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(key)); - if (!first) - return 0; - - LIST_FOREACH(by_name, i, first) { - dns_zone_item_verify(i); - c++; - } - - return c; -} - -void dns_zone_verify_all(DnsZone *zone) { - DnsZoneItem *i; - Iterator iterator; - - assert(zone); - - HASHMAP_FOREACH(i, zone->by_key, iterator) { - DnsZoneItem *j; - - LIST_FOREACH(by_key, j, i) - dns_zone_item_verify(j); - } -} - -void dns_zone_dump(DnsZone *zone, FILE *f) { - Iterator iterator; - DnsZoneItem *i; - - if (!zone) - return; - - if (!f) - f = stdout; - - HASHMAP_FOREACH(i, zone->by_key, iterator) { - DnsZoneItem *j; - - LIST_FOREACH(by_key, j, i) { - const char *t; - - t = dns_resource_record_to_string(j->rr); - if (!t) { - log_oom(); - continue; - } - - fputc('\t', f); - fputs(t, f); - fputc('\n', f); - } - } -} - -bool dns_zone_is_empty(DnsZone *zone) { - if (!zone) - return true; - - return hashmap_isempty(zone->by_key); -} diff --git a/src/resolve/resolved-dns-zone.h b/src/resolve/resolved-dns-zone.h deleted file mode 100644 index 408833c359..0000000000 --- a/src/resolve/resolved-dns-zone.h +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "hashmap.h" - -typedef struct DnsZone { - Hashmap *by_key; - Hashmap *by_name; -} DnsZone; - -typedef struct DnsZoneItem DnsZoneItem; -typedef enum DnsZoneItemState DnsZoneItemState; - -#include "resolved-dns-answer.h" -#include "resolved-dns-question.h" -#include "resolved-dns-rr.h" -#include "resolved-dns-transaction.h" - -/* RFC 4795 Section 2.8. suggests a TTL of 30s by default */ -#define LLMNR_DEFAULT_TTL (30) - -enum DnsZoneItemState { - DNS_ZONE_ITEM_PROBING, - DNS_ZONE_ITEM_ESTABLISHED, - DNS_ZONE_ITEM_VERIFYING, - DNS_ZONE_ITEM_WITHDRAWN, -}; - -struct DnsZoneItem { - DnsScope *scope; - DnsResourceRecord *rr; - - DnsZoneItemState state; - - unsigned block_ready; - - bool probing_enabled; - - LIST_FIELDS(DnsZoneItem, by_key); - LIST_FIELDS(DnsZoneItem, by_name); - - DnsTransaction *probe_transaction; -}; - -void dns_zone_flush(DnsZone *z); - -int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe); -void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr); - -int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **answer, DnsAnswer **soa, bool *tentative); - -void dns_zone_item_conflict(DnsZoneItem *i); -void dns_zone_item_notify(DnsZoneItem *i); - -int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr); -int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key); - -void dns_zone_verify_all(DnsZone *zone); - -void dns_zone_item_probe_stop(DnsZoneItem *i); - -void dns_zone_dump(DnsZone *zone, FILE *f); -bool dns_zone_is_empty(DnsZone *zone); diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c deleted file mode 100644 index ee82c96822..0000000000 --- a/src/resolve/resolved-etc-hosts.c +++ /dev/null @@ -1,448 +0,0 @@ -/*** - 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 . -***/ - -#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 deleted file mode 100644 index 9d5a175f18..0000000000 --- a/src/resolve/resolved-etc-hosts.h +++ /dev/null @@ -1,28 +0,0 @@ -#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 . -***/ - -#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 deleted file mode 100644 index 82f26215df..0000000000 --- a/src/resolve/resolved-gperf.gperf +++ /dev/null @@ -1,21 +0,0 @@ -%{ -#include -#include "conf-parser.h" -#include "resolved-conf.h" -%} -struct ConfigPerfItem; -%null_strings -%language=ANSI-C -%define slot-name section_and_lvalue -%define hash-function-name resolved_gperf_hash -%define lookup-function-name resolved_gperf_lookup -%readonly-tables -%omit-struct-type -%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_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 deleted file mode 100644 index df7516f4f4..0000000000 --- a/src/resolve/resolved-link-bus.c +++ /dev/null @@ -1,550 +0,0 @@ -/*** - 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 . -***/ - -#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 deleted file mode 100644 index 31e6cd2b45..0000000000 --- a/src/resolve/resolved-link-bus.h +++ /dev/null @@ -1,38 +0,0 @@ -#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 . -***/ - -#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 deleted file mode 100644 index c5863b3aa2..0000000000 --- a/src/resolve/resolved-link.c +++ /dev/null @@ -1,840 +0,0 @@ -/*** - 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 . -***/ - -#include - -#include "sd-network.h" - -#include "alloc-util.h" -#include "missing.h" -#include "parse-util.h" -#include "resolved-link.h" -#include "string-util.h" -#include "strv.h" - -int link_new(Manager *m, Link **ret, int ifindex) { - _cleanup_(link_freep) Link *l = NULL; - int r; - - assert(m); - assert(ifindex > 0); - - r = hashmap_ensure_allocated(&m->links, NULL); - if (r < 0) - return r; - - l = new0(Link, 1); - if (!l) - return -ENOMEM; - - l->ifindex = ifindex; - 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) - return r; - - l->manager = m; - - if (ret) - *ret = l; - l = NULL; - - 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; - - link_flush_settings(l); - - while (l->addresses) - (void) link_address_free(l->addresses); - - if (l->manager) - hashmap_remove(l->manager->links, INT_TO_PTR(l->ifindex)); - - dns_scope_free(l->unicast_scope); - dns_scope_free(l->llmnr_ipv4_scope); - dns_scope_free(l->llmnr_ipv6_scope); - dns_scope_free(l->mdns_ipv4_scope); - dns_scope_free(l->mdns_ipv6_scope); - - free(l); - return NULL; -} - -void link_allocate_scopes(Link *l) { - int r; - - assert(l); - - 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) - log_warning_errno(r, "Failed to allocate DNS scope: %m"); - } - } else - l->unicast_scope = dns_scope_free(l->unicast_scope); - - 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) - log_warning_errno(r, "Failed to allocate LLMNR IPv4 scope: %m"); - } - } else - l->llmnr_ipv4_scope = dns_scope_free(l->llmnr_ipv4_scope); - - 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); - if (r < 0) - log_warning_errno(r, "Failed to allocate LLMNR IPv6 scope: %m"); - } - } else - l->llmnr_ipv6_scope = dns_scope_free(l->llmnr_ipv6_scope); - - 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) - log_warning_errno(r, "Failed to allocate mDNS IPv4 scope: %m"); - } - } else - l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope); - - 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) - log_warning_errno(r, "Failed to allocate mDNS IPv6 scope: %m"); - } - } else - l->mdns_ipv6_scope = dns_scope_free(l->mdns_ipv6_scope); -} - -void link_add_rrs(Link *l, bool force_remove) { - LinkAddress *a; - - LIST_FOREACH(addresses, a, l->addresses) - link_address_add_rrs(a, force_remove); -} - -int link_update_rtnl(Link *l, sd_netlink_message *m) { - const char *n = NULL; - int r; - - assert(l); - assert(m); - - r = sd_rtnl_message_link_get_flags(m, &l->flags); - if (r < 0) - return r; - - (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); - char_array_0(l->name); - } - - link_allocate_scopes(l); - link_add_rrs(l, false); - - return 0; -} - -static int link_update_dns_servers(Link *l) { - _cleanup_strv_free_ char **nameservers = NULL; - char **nameserver; - int r; - - assert(l); - - r = sd_network_link_get_dns(l->ifindex, &nameservers); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - dns_server_mark_all(l->dns_servers); - - STRV_FOREACH(nameserver, nameservers) { - union in_addr_union a; - DnsServer *s; - int family; - - r = in_addr_from_string_auto(*nameserver, &family, &a); - if (r < 0) - goto clear; - - s = dns_server_find(l->dns_servers, family, &a); - if (s) - dns_server_move_back_and_unmark(s); - else { - r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a); - if (r < 0) - goto clear; - } - } - - dns_server_unlink_marked(l->dns_servers); - return 0; - -clear: - dns_server_unlink_all(l->dns_servers); - return r; -} - -static int link_update_llmnr_support(Link *l) { - _cleanup_free_ char *b = NULL; - int r; - - assert(l); - - r = sd_network_link_get_llmnr(l->ifindex, &b); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - l->llmnr_support = resolve_support_from_string(b); - if (l->llmnr_support < 0) { - r = -EINVAL; - goto clear; - } - - return 0; - -clear: - l->llmnr_support = RESOLVE_SUPPORT_YES; - return r; -} - -static int link_update_mdns_support(Link *l) { - _cleanup_free_ char *b = NULL; - int r; - - assert(l); - - r = sd_network_link_get_mdns(l->ifindex, &b); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - l->mdns_support = resolve_support_from_string(b); - if (l->mdns_support < 0) { - r = -EINVAL; - goto clear; - } - - return 0; - -clear: - l->mdns_support = RESOLVE_SUPPORT_NO; - return r; -} - -void link_set_dnssec_mode(Link *l, DnssecMode mode) { - - assert(l); - - if (l->dnssec_mode == mode) - return; - - if ((l->dnssec_mode == _DNSSEC_MODE_INVALID) || - (l->dnssec_mode == DNSSEC_NO && mode != DNSSEC_NO) || - (l->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE && mode == DNSSEC_YES)) { - - /* When switching from non-DNSSEC mode to DNSSEC mode, flush the cache. Also when switching from the - * allow-downgrade mode to full DNSSEC mode, flush it too. */ - if (l->unicast_scope) - dns_cache_flush(&l->unicast_scope->cache); - } - - l->dnssec_mode = mode; -} - -static int link_update_dnssec_mode(Link *l) { - _cleanup_free_ char *m = NULL; - DnssecMode mode; - int r; - - assert(l); - - r = sd_network_link_get_dnssec(l->ifindex, &m); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - mode = dnssec_mode_from_string(m); - if (mode < 0) { - r = -EINVAL; - goto clear; - } - - link_set_dnssec_mode(l, mode); - - return 0; - -clear: - l->dnssec_mode = _DNSSEC_MODE_INVALID; - return r; -} - -static int link_update_dnssec_negative_trust_anchors(Link *l) { - _cleanup_strv_free_ char **ntas = NULL; - _cleanup_set_free_free_ Set *ns = NULL; - char **i; - int r; - - assert(l); - - r = sd_network_link_get_dnssec_negative_trust_anchors(l->ifindex, &ntas); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - ns = set_new(&dns_name_hash_ops); - if (!ns) - return -ENOMEM; - - STRV_FOREACH(i, ntas) { - r = set_put_strdup(ns, *i); - if (r < 0) - return r; - } - - set_free_free(l->dnssec_negative_trust_anchors); - l->dnssec_negative_trust_anchors = ns; - ns = NULL; - - return 0; - -clear: - l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors); - return r; -} - -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; - } - - d->route_only = route_only; - return 0; -} - -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; - } - - 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); - return 0; - -clear: - dns_search_domain_unlink_all(l->search_domains); - return r; -} - -static int link_is_unmanaged(Link *l) { - _cleanup_free_ char *state = NULL; - int r; - - assert(l); - - r = sd_network_link_get_setup_state(l->ifindex, &state); - if (r == -ENODATA) - return 1; - if (r < 0) - return r; - - return STR_IN_SET(state, "pending", "unmanaged"); -} - -static void link_read_settings(Link *l) { - int r; - - assert(l); - - /* Read settings from networkd, except when networkd is not managing this interface. */ - - r = link_is_unmanaged(l); - if (r < 0) { - log_warning_errno(r, "Failed to determine whether interface %s is managed: %m", l->name); - return; - } - if (r > 0) { - - /* If this link used to be managed, but is now unmanaged, flush all our settings -- but only once. */ - if (l->is_managed) - link_flush_settings(l); - - l->is_managed = false; - return; - } - - l->is_managed = true; - - r = link_update_dns_servers(l); - if (r < 0) - log_warning_errno(r, "Failed to read DNS servers for interface %s, ignoring: %m", l->name); - - 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 local_multicast) { - _cleanup_free_ char *state = NULL; - LinkAddress *a; - - assert(l); - - /* 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_DORMANT)) - return false; - - if ((l->flags & (IFF_UP|IFF_LOWER_UP)) != (IFF_UP|IFF_LOWER_UP)) - return false; - - 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 ((family == AF_UNSPEC || a->family == family) && link_address_relevant(a, local_multicast)) - return true; - - return false; -} - -LinkAddress *link_find_address(Link *l, int family, const union in_addr_union *in_addr) { - LinkAddress *a; - - assert(l); - - LIST_FOREACH(addresses, a, l->addresses) - if (a->family == family && in_addr_equal(family, &a->in_addr, in_addr)) - return a; - - return NULL; -} - -DnsServer* link_set_dns_server(Link *l, DnsServer *s) { - assert(l); - - if (l->current_dns_server == s) - return s; - - 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); - - if (l->unicast_scope) - dns_cache_flush(&l->unicast_scope->cache); - - return s; -} - -DnsServer *link_get_dns_server(Link *l) { - assert(l); - - if (!l->current_dns_server) - link_set_dns_server(l, l->dns_servers); - - return l->current_dns_server; -} - -void link_next_dns_server(Link *l) { - assert(l); - - if (!l->current_dns_server) - return; - - /* Change to the next one, but make sure to follow the linked - * list only if this server is actually still linked. */ - if (l->current_dns_server->linked && l->current_dns_server->servers_next) { - link_set_dns_server(l, l->current_dns_server->servers_next); - return; - } - - 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; - - assert(l); - assert(in_addr); - - a = new0(LinkAddress, 1); - if (!a) - return -ENOMEM; - - a->family = family; - a->in_addr = *in_addr; - - a->link = l; - LIST_PREPEND(addresses, l->addresses, a); - - if (ret) - *ret = a; - - return 0; -} - -LinkAddress *link_address_free(LinkAddress *a) { - if (!a) - return NULL; - - if (a->link) { - LIST_REMOVE(addresses, a->link->addresses, a); - - if (a->llmnr_address_rr) { - if (a->family == AF_INET && a->link->llmnr_ipv4_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr); - else if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_address_rr); - } - - if (a->llmnr_ptr_rr) { - if (a->family == AF_INET && a->link->llmnr_ipv4_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_ptr_rr); - else if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr); - } - } - - dns_resource_record_unref(a->llmnr_address_rr); - dns_resource_record_unref(a->llmnr_ptr_rr); - - free(a); - return NULL; -} - -void link_address_add_rrs(LinkAddress *a, bool force_remove) { - int r; - - assert(a); - - if (a->family == AF_INET) { - - if (!force_remove && - link_address_relevant(a, true) && - a->link->llmnr_ipv4_scope && - 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); - if (!a->link->manager->llmnr_host_ipv4_key) { - r = -ENOMEM; - goto fail; - } - } - - if (!a->llmnr_address_rr) { - a->llmnr_address_rr = dns_resource_record_new(a->link->manager->llmnr_host_ipv4_key); - if (!a->llmnr_address_rr) { - r = -ENOMEM; - goto fail; - } - - a->llmnr_address_rr->a.in_addr = a->in_addr.in; - a->llmnr_address_rr->ttl = LLMNR_DEFAULT_TTL; - } - - if (!a->llmnr_ptr_rr) { - r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->llmnr_hostname); - if (r < 0) - goto fail; - - a->llmnr_ptr_rr->ttl = LLMNR_DEFAULT_TTL; - } - - r = dns_zone_put(&a->link->llmnr_ipv4_scope->zone, a->link->llmnr_ipv4_scope, a->llmnr_address_rr, true); - if (r < 0) - log_warning_errno(r, "Failed to add A record to LLMNR zone: %m"); - - r = dns_zone_put(&a->link->llmnr_ipv4_scope->zone, a->link->llmnr_ipv4_scope, a->llmnr_ptr_rr, false); - if (r < 0) - log_warning_errno(r, "Failed to add IPv6 PTR record to LLMNR zone: %m"); - } else { - if (a->llmnr_address_rr) { - if (a->link->llmnr_ipv4_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr); - a->llmnr_address_rr = dns_resource_record_unref(a->llmnr_address_rr); - } - - if (a->llmnr_ptr_rr) { - if (a->link->llmnr_ipv4_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_ptr_rr); - a->llmnr_ptr_rr = dns_resource_record_unref(a->llmnr_ptr_rr); - } - } - } - - if (a->family == AF_INET6) { - - if (!force_remove && - link_address_relevant(a, true) && - a->link->llmnr_ipv6_scope && - 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); - if (!a->link->manager->llmnr_host_ipv6_key) { - r = -ENOMEM; - goto fail; - } - } - - if (!a->llmnr_address_rr) { - a->llmnr_address_rr = dns_resource_record_new(a->link->manager->llmnr_host_ipv6_key); - if (!a->llmnr_address_rr) { - r = -ENOMEM; - goto fail; - } - - a->llmnr_address_rr->aaaa.in6_addr = a->in_addr.in6; - a->llmnr_address_rr->ttl = LLMNR_DEFAULT_TTL; - } - - if (!a->llmnr_ptr_rr) { - r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->llmnr_hostname); - if (r < 0) - goto fail; - - a->llmnr_ptr_rr->ttl = LLMNR_DEFAULT_TTL; - } - - r = dns_zone_put(&a->link->llmnr_ipv6_scope->zone, a->link->llmnr_ipv6_scope, a->llmnr_address_rr, true); - if (r < 0) - log_warning_errno(r, "Failed to add AAAA record to LLMNR zone: %m"); - - r = dns_zone_put(&a->link->llmnr_ipv6_scope->zone, a->link->llmnr_ipv6_scope, a->llmnr_ptr_rr, false); - if (r < 0) - log_warning_errno(r, "Failed to add IPv6 PTR record to LLMNR zone: %m"); - } else { - if (a->llmnr_address_rr) { - if (a->link->llmnr_ipv6_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_address_rr); - a->llmnr_address_rr = dns_resource_record_unref(a->llmnr_address_rr); - } - - if (a->llmnr_ptr_rr) { - if (a->link->llmnr_ipv6_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr); - a->llmnr_ptr_rr = dns_resource_record_unref(a->llmnr_ptr_rr); - } - } - } - - return; - -fail: - log_debug_errno(r, "Failed to update address RRs: %m"); -} - -int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m) { - int r; - assert(a); - assert(m); - - r = sd_rtnl_message_addr_get_flags(m, &a->flags); - if (r < 0) - return r; - - sd_rtnl_message_addr_get_scope(m, &a->scope); - - link_allocate_scopes(a->link); - link_add_rrs(a->link, false); - - return 0; -} - -bool link_address_relevant(LinkAddress *a, bool local_multicast) { - assert(a); - - if (a->flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE)) - return false; - - 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 deleted file mode 100644 index f534c12824..0000000000 --- a/src/resolve/resolved-link.h +++ /dev/null @@ -1,111 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include - -#include "in-addr-util.h" -#include "ratelimit.h" -#include "resolve-util.h" - -typedef struct Link Link; -typedef struct LinkAddress LinkAddress; - -#include "resolved-dns-rr.h" -#include "resolved-dns-search-domain.h" -#include "resolved-dns-server.h" -#include "resolved-manager.h" - -#define LINK_SEARCH_DOMAINS_MAX 32 -#define LINK_DNS_SERVERS_MAX 32 - -struct LinkAddress { - Link *link; - - int family; - union in_addr_union in_addr; - - unsigned char flags, scope; - - DnsResourceRecord *llmnr_address_rr; - DnsResourceRecord *llmnr_ptr_rr; - - LIST_FIELDS(LinkAddress, addresses); -}; - -struct Link { - Manager *manager; - - int ifindex; - unsigned flags; - - LIST_HEAD(LinkAddress, addresses); - - LIST_HEAD(DnsServer, dns_servers); - DnsServer *current_dns_server; - unsigned n_dns_servers; - - LIST_HEAD(DnsSearchDomain, search_domains); - unsigned n_search_domains; - - ResolveSupport llmnr_support; - ResolveSupport mdns_support; - DnssecMode dnssec_mode; - Set *dnssec_negative_trust_anchors; - - DnsScope *unicast_scope; - DnsScope *llmnr_ipv4_scope; - DnsScope *llmnr_ipv6_scope; - 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 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 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 deleted file mode 100644 index ef12abfbb5..0000000000 --- a/src/resolve/resolved-llmnr.c +++ /dev/null @@ -1,476 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . - ***/ - -#include -#include - -#include "fd-util.h" -#include "resolved-llmnr.h" -#include "resolved-manager.h" - -void manager_llmnr_stop(Manager *m) { - assert(m); - - m->llmnr_ipv4_udp_event_source = sd_event_source_unref(m->llmnr_ipv4_udp_event_source); - m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); - - m->llmnr_ipv6_udp_event_source = sd_event_source_unref(m->llmnr_ipv6_udp_event_source); - m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); - - m->llmnr_ipv4_tcp_event_source = sd_event_source_unref(m->llmnr_ipv4_tcp_event_source); - m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); - - m->llmnr_ipv6_tcp_event_source = sd_event_source_unref(m->llmnr_ipv6_tcp_event_source); - m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); -} - -int manager_llmnr_start(Manager *m) { - int r; - - assert(m); - - if (m->llmnr_support == RESOLVE_SUPPORT_NO) - return 0; - - r = manager_llmnr_ipv4_udp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - r = manager_llmnr_ipv4_tcp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - if (socket_ipv6_is_supported()) { - r = manager_llmnr_ipv6_udp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - r = manager_llmnr_ipv6_tcp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - } - - return 0; - -eaddrinuse: - log_warning("There appears to be another LLMNR responder running. Turning off LLMNR support."); - m->llmnr_support = RESOLVE_SUPPORT_NO; - manager_llmnr_stop(m); - - return 0; -} - -static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - DnsTransaction *t = NULL; - Manager *m = userdata; - DnsScope *scope; - int r; - - r = manager_recv(m, fd, DNS_PROTOCOL_LLMNR, &p); - if (r <= 0) - return r; - - scope = manager_find_scope(m, p); - if (!scope) { - log_warning("Got LLMNR UDP packet on unknown scope. Ignoring."); - return 0; - } - - if (dns_packet_validate_reply(p) > 0) { - log_debug("Got LLMNR reply packet for id %u", DNS_PACKET_ID(p)); - - dns_scope_check_conflicts(scope, p); - - t = hashmap_get(m->dns_transactions, UINT_TO_PTR(DNS_PACKET_ID(p))); - if (t) - dns_transaction_process_reply(t, p); - - } else if (dns_packet_validate_query(p) > 0) { - log_debug("Got LLMNR query packet for id %u", DNS_PACKET_ID(p)); - - dns_scope_process_query(scope, NULL, p); - } else - log_debug("Invalid LLMNR UDP packet, ignoring."); - - return 0; -} - -int manager_llmnr_ipv4_udp_fd(Manager *m) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(LLMNR_PORT), - }; - static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; - int r; - - assert(m); - - if (m->llmnr_ipv4_udp_fd >= 0) - return m->llmnr_ipv4_udp_fd; - - m->llmnr_ipv4_udp_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv4_udp_fd < 0) - return -errno; - - /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* Disable Don't-Fragment bit in the IP header */ - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv4_udp_fd, &sa.sa, sizeof(sa.in)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv4_udp_event_source, m->llmnr_ipv4_udp_fd, EPOLLIN, on_llmnr_packet, 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: - m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); - return r; -} - -int manager_llmnr_ipv6_udp_fd(Manager *m) { - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - .in6.sin6_port = htobe16(LLMNR_PORT), - }; - static const int one = 1, ttl = 255; - int r; - - assert(m); - - if (m->llmnr_ipv6_udp_fd >= 0) - return m->llmnr_ipv6_udp_fd; - - m->llmnr_ipv6_udp_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv6_udp_fd < 0) - return -errno; - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv6_udp_fd, &sa.sa, sizeof(sa.in6)); - if (r < 0) { - r = -errno; - goto fail; - } - - 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) - goto fail; - - (void) sd_event_source_set_description(m->llmnr_ipv6_udp_event_source, "llmnr-ipv6-udp"); - - return m->llmnr_ipv6_udp_fd; - -fail: - m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); - return r; -} - -static int on_llmnr_stream_packet(DnsStream *s) { - DnsScope *scope; - - assert(s); - - scope = manager_find_scope(s->manager, s->read_packet); - if (!scope) { - log_warning("Got LLMNR TCP packet on unknown scope. Ignroing."); - return 0; - } - - if (dns_packet_validate_query(s->read_packet) > 0) { - log_debug("Got query packet for id %u", DNS_PACKET_ID(s->read_packet)); - - dns_scope_process_query(scope, s, s->read_packet); - - /* If no reply packet was set, we free the stream */ - if (s->write_packet) - return 0; - } else - log_debug("Invalid LLMNR TCP packet."); - - dns_stream_free(s); - return 0; -} - -static int on_llmnr_stream(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - DnsStream *stream; - Manager *m = userdata; - int cfd, r; - - cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); - if (cfd < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return -errno; - } - - r = dns_stream_new(m, &stream, DNS_PROTOCOL_LLMNR, cfd); - if (r < 0) { - safe_close(cfd); - return r; - } - - stream->on_packet = on_llmnr_stream_packet; - return 0; -} - -int manager_llmnr_ipv4_tcp_fd(Manager *m) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(LLMNR_PORT), - }; - static const int one = 1, pmtu = IP_PMTUDISC_DONT; - int r; - - assert(m); - - if (m->llmnr_ipv4_tcp_fd >= 0) - return m->llmnr_ipv4_tcp_fd; - - m->llmnr_ipv4_tcp_fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv4_tcp_fd < 0) - return -errno; - - /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* Disable Don't-Fragment bit in the IP header */ - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv4_tcp_fd, &sa.sa, sizeof(sa.in)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = listen(m->llmnr_ipv4_tcp_fd, SOMAXCONN); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv4_tcp_event_source, m->llmnr_ipv4_tcp_fd, EPOLLIN, on_llmnr_stream, 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: - m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); - return r; -} - -int manager_llmnr_ipv6_tcp_fd(Manager *m) { - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - .in6.sin6_port = htobe16(LLMNR_PORT), - }; - static const int one = 1; - int r; - - assert(m); - - if (m->llmnr_ipv6_tcp_fd >= 0) - return m->llmnr_ipv6_tcp_fd; - - m->llmnr_ipv6_tcp_fd = socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv6_tcp_fd < 0) - return -errno; - - /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv6_tcp_fd, &sa.sa, sizeof(sa.in6)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = listen(m->llmnr_ipv6_tcp_fd, SOMAXCONN); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv6_tcp_event_source, m->llmnr_ipv6_tcp_fd, EPOLLIN, on_llmnr_stream, 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: - m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); - return r; -} diff --git a/src/resolve/resolved-llmnr.h b/src/resolve/resolved-llmnr.h deleted file mode 100644 index 8133582fa7..0000000000 --- a/src/resolve/resolved-llmnr.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "resolved-manager.h" - -#define LLMNR_PORT 5355 - -int manager_llmnr_ipv4_udp_fd(Manager *m); -int manager_llmnr_ipv6_udp_fd(Manager *m); -int manager_llmnr_ipv4_tcp_fd(Manager *m); -int manager_llmnr_ipv6_tcp_fd(Manager *m); - -void manager_llmnr_stop(Manager *m); -int manager_llmnr_start(Manager *m); diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c deleted file mode 100644 index e82c6ec563..0000000000 --- a/src/resolve/resolved-manager.c +++ /dev/null @@ -1,1239 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . - ***/ - -#include -#include -#include - -#include "af-list.h" -#include "alloc-util.h" -#include "dns-domain.h" -#include "fd-util.h" -#include "fileio-label.h" -#include "hostname-util.h" -#include "io-util.h" -#include "netlink-util.h" -#include "network-internal.h" -#include "ordered-set.h" -#include "parse-util.h" -#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-mdns.h" -#include "resolved-resolv-conf.h" -#include "socket-util.h" -#include "string-table.h" -#include "string-util.h" -#include "utf8.h" - -#define SEND_TIMEOUT_USEC (200 * USEC_PER_MSEC) - -static int manager_process_link(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { - Manager *m = userdata; - uint16_t type; - Link *l; - int ifindex, r; - - assert(rtnl); - assert(m); - assert(mm); - - r = sd_netlink_message_get_type(mm, &type); - if (r < 0) - goto fail; - - r = sd_rtnl_message_link_get_ifindex(mm, &ifindex); - if (r < 0) - goto fail; - - l = hashmap_get(m->links, INT_TO_PTR(ifindex)); - - switch (type) { - - case RTM_NEWLINK:{ - bool is_new = !l; - - if (!l) { - r = link_new(m, &l, ifindex); - if (r < 0) - goto fail; - } - - r = link_update_rtnl(l, mm); - if (r < 0) - goto fail; - - r = link_update_monitor(l); - if (r < 0) - goto fail; - - if (is_new) - log_debug("Found new link %i/%s", ifindex, l->name); - - break; - } - - case RTM_DELLINK: - if (l) { - log_debug("Removing link %i/%s", l->ifindex, l->name); - link_free(l); - } - - break; - } - - return 0; - -fail: - log_warning_errno(r, "Failed to process RTNL link message: %m"); - return 0; -} - -static int manager_process_address(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { - Manager *m = userdata; - union in_addr_union address; - uint16_t type; - int r, ifindex, family; - LinkAddress *a; - Link *l; - - assert(rtnl); - assert(mm); - assert(m); - - r = sd_netlink_message_get_type(mm, &type); - if (r < 0) - goto fail; - - r = sd_rtnl_message_addr_get_ifindex(mm, &ifindex); - if (r < 0) - goto fail; - - l = hashmap_get(m->links, INT_TO_PTR(ifindex)); - if (!l) - return 0; - - r = sd_rtnl_message_addr_get_family(mm, &family); - if (r < 0) - goto fail; - - switch (family) { - - case AF_INET: - r = sd_netlink_message_read_in_addr(mm, IFA_LOCAL, &address.in); - if (r < 0) { - r = sd_netlink_message_read_in_addr(mm, IFA_ADDRESS, &address.in); - if (r < 0) - goto fail; - } - - break; - - case AF_INET6: - r = sd_netlink_message_read_in6_addr(mm, IFA_LOCAL, &address.in6); - if (r < 0) { - r = sd_netlink_message_read_in6_addr(mm, IFA_ADDRESS, &address.in6); - if (r < 0) - goto fail; - } - - break; - - default: - return 0; - } - - a = link_find_address(l, family, &address); - - switch (type) { - - case RTM_NEWADDR: - - if (!a) { - r = link_address_new(l, &a, family, &address); - if (r < 0) - return r; - } - - r = link_address_update_rtnl(a, mm); - if (r < 0) - return r; - - break; - - case RTM_DELADDR: - link_address_free(a); - break; - } - - return 0; - -fail: - log_warning_errno(r, "Failed to process RTNL address message: %m"); - return 0; -} - -static int manager_rtnl_listen(Manager *m) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; - sd_netlink_message *i; - int r; - - assert(m); - - /* First, subscribe to interfaces coming and going */ - r = sd_netlink_open(&m->rtnl); - if (r < 0) - return r; - - r = sd_netlink_attach_event(m->rtnl, m->event, SD_EVENT_PRIORITY_IMPORTANT); - if (r < 0) - return r; - - r = sd_netlink_add_match(m->rtnl, RTM_NEWLINK, manager_process_link, m); - if (r < 0) - return r; - - r = sd_netlink_add_match(m->rtnl, RTM_DELLINK, manager_process_link, m); - if (r < 0) - return r; - - r = sd_netlink_add_match(m->rtnl, RTM_NEWADDR, manager_process_address, m); - if (r < 0) - return r; - - r = sd_netlink_add_match(m->rtnl, RTM_DELADDR, manager_process_address, m); - if (r < 0) - return r; - - /* Then, enumerate all links */ - r = sd_rtnl_message_new_link(m->rtnl, &req, RTM_GETLINK, 0); - if (r < 0) - return r; - - r = sd_netlink_message_request_dump(req, true); - if (r < 0) - return r; - - r = sd_netlink_call(m->rtnl, req, 0, &reply); - if (r < 0) - return r; - - for (i = reply; i; i = sd_netlink_message_next(i)) { - r = manager_process_link(m->rtnl, i, m); - if (r < 0) - return r; - } - - req = sd_netlink_message_unref(req); - reply = sd_netlink_message_unref(reply); - - /* Finally, enumerate all addresses, too */ - r = sd_rtnl_message_new_addr(m->rtnl, &req, RTM_GETADDR, 0, AF_UNSPEC); - if (r < 0) - return r; - - r = sd_netlink_message_request_dump(req, true); - if (r < 0) - return r; - - r = sd_netlink_call(m->rtnl, req, 0, &reply); - if (r < 0) - return r; - - for (i = reply; i; i = sd_netlink_message_next(i)) { - r = manager_process_address(m->rtnl, i, m); - if (r < 0) - return r; - } - - return r; -} - -static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - Iterator i; - Link *l; - int r; - - assert(m); - - sd_network_monitor_flush(m->network_monitor); - - HASHMAP_FOREACH(l, m->links, i) { - r = link_update_monitor(l); - if (r < 0) - log_warning_errno(r, "Failed to update monitor information for %i: %m", l->ifindex); - } - - r = manager_write_resolv_conf(m); - if (r < 0) - log_warning_errno(r, "Could not update "PRIVATE_RESOLV_CONF": %m"); - - return 0; -} - -static int manager_network_monitor_listen(Manager *m) { - int r, fd, events; - - assert(m); - - r = sd_network_monitor_new(&m->network_monitor, NULL); - if (r < 0) - return r; - - fd = sd_network_monitor_get_fd(m->network_monitor); - if (fd < 0) - return fd; - - events = sd_network_monitor_get_events(m->network_monitor); - if (events < 0) - return events; - - r = sd_event_add_io(m->event, &m->network_event_source, fd, events, &on_network_event, 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; -} - -static int determine_hostname(char **llmnr_hostname, char **mdns_hostname) { - _cleanup_free_ char *h = NULL, *n = NULL; - char label[DNS_LABEL_MAX]; - const char *p; - int r, k; - - assert(llmnr_hostname); - assert(mdns_hostname); - - /* Extract and normalize the first label of the locally - * configured hostname, and check it's not "localhost". */ - - h = gethostname_malloc(); - if (!h) - return log_oom(); - - p = h; - r = dns_label_unescape(&p, label, sizeof(label)); - if (r < 0) - return log_error_errno(r, "Failed to unescape host name: %m"); - if (r == 0) { - log_error("Couldn't find a single label in hosntame."); - return -EINVAL; - } - - k = dns_label_undo_idna(label, r, label, sizeof(label)); - if (k < 0) - return log_error_errno(k, "Failed to undo IDNA: %m"); - if (k > 0) - r = k; - - if (!utf8_is_valid(label)) { - log_error("System hostname is not UTF-8 clean."); - return -EINVAL; - } - - r = dns_label_escape_new(label, r, &n); - if (r < 0) - return log_error_errno(r, "Failed to escape host name: %m"); - - if (is_localhost(n)) { - log_debug("System hostname is 'localhost', ignoring."); - return -EINVAL; - } - - r = dns_name_concat(n, "local", mdns_hostname); - if (r < 0) - return log_error_errno(r, "Failed to determine mDNS hostname: %m"); - - *llmnr_hostname = n; - n = NULL; - - return 0; -} - -static int on_hostname_change(sd_event_source *es, int fd, uint32_t revents, void *userdata) { - _cleanup_free_ char *llmnr_hostname = NULL, *mdns_hostname = NULL; - Manager *m = userdata; - int r; - - assert(m); - - r = determine_hostname(&llmnr_hostname, &mdns_hostname); - if (r < 0) - return 0; /* ignore invalid hostnames */ - - if (streq(llmnr_hostname, m->llmnr_hostname) && streq(mdns_hostname, m->mdns_hostname)) - return 0; - - log_info("System hostname changed to '%s'.", llmnr_hostname); - - free(m->llmnr_hostname); - free(m->mdns_hostname); - - m->llmnr_hostname = llmnr_hostname; - m->mdns_hostname = mdns_hostname; - - llmnr_hostname = mdns_hostname = NULL; - - manager_refresh_rrs(m); - - return 0; -} - -static int manager_watch_hostname(Manager *m) { - int r; - - assert(m); - - m->hostname_fd = open("/proc/sys/kernel/hostname", O_RDONLY|O_CLOEXEC|O_NDELAY|O_NOCTTY); - if (m->hostname_fd < 0) { - log_warning_errno(errno, "Failed to watch hostname: %m"); - return 0; - } - - r = sd_event_add_io(m->event, &m->hostname_event_source, m->hostname_fd, 0, on_hostname_change, m); - if (r < 0) { - if (r == -EPERM) - /* kernels prior to 3.2 don't support polling this file. Ignore the failure. */ - m->hostname_fd = safe_close(m->hostname_fd); - else - 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 'gnu-linux'."); - m->llmnr_hostname = strdup("gnu-linux"); - if (!m->llmnr_hostname) - return log_oom(); - - m->mdns_hostname = strdup("gnu-linux.local"); - if (!m->mdns_hostname) - return log_oom(); - } else - log_info("Using system hostname '%s'.", m->llmnr_hostname); - - return 0; -} - -static int manager_sigusr1(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - _cleanup_free_ char *buffer = NULL; - _cleanup_fclose_ FILE *f = NULL; - Manager *m = userdata; - size_t size = 0; - DnsScope *scope; - - assert(s); - assert(si); - assert(m); - - f = open_memstream(&buffer, &size); - if (!f) - return log_oom(); - - LIST_FOREACH(scopes, scope, m->dns_scopes) - dns_scope_dump(scope, f); - - if (fflush_and_check(f) < 0) - return log_oom(); - - log_dump(LOG_INFO, buffer); - return 0; -} - -int manager_new(Manager **ret) { - _cleanup_(manager_freep) Manager *m = NULL; - int r; - - assert(ret); - - m = new0(Manager, 1); - if (!m) - return -ENOMEM; - - m->llmnr_ipv4_udp_fd = m->llmnr_ipv6_udp_fd = -1; - m->llmnr_ipv4_tcp_fd = m->llmnr_ipv6_tcp_fd = -1; - m->mdns_ipv4_fd = m->mdns_ipv6_fd = -1; - m->hostname_fd = -1; - - 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; - - sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); - sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); - - sd_event_set_watchdog(m->event, true); - - r = manager_watch_hostname(m); - if (r < 0) - return r; - - r = dns_scope_new(m, &m->unicast_scope, NULL, DNS_PROTOCOL_DNS, AF_UNSPEC); - if (r < 0) - return r; - - r = manager_network_monitor_listen(m); - if (r < 0) - return r; - - r = manager_rtnl_listen(m); - if (r < 0) - return r; - - r = manager_connect_bus(m); - if (r < 0) - return r; - - (void) sd_event_add_signal(m->event, &m->sigusr1_event_source, SIGUSR1, manager_sigusr1, m); - - *ret = m; - m = NULL; - - return 0; -} - -int manager_start(Manager *m) { - int r; - - assert(m); - - r = manager_llmnr_start(m); - if (r < 0) - return r; - - r = manager_mdns_start(m); - if (r < 0) - return r; - - return 0; -} - -Manager *manager_free(Manager *m) { - Link *l; - - if (!m) - return NULL; - - dns_server_unlink_all(m->dns_servers); - dns_server_unlink_all(m->fallback_dns_servers); - dns_search_domain_unlink_all(m->search_domains); - - while ((l = hashmap_first(m->links))) - link_free(l); - - while (m->dns_queries) - dns_query_free(m->dns_queries); - - dns_scope_free(m->unicast_scope); - - hashmap_free(m->links); - hashmap_free(m->dns_transactions); - - sd_event_source_unref(m->network_event_source); - sd_network_monitor_unref(m->network_monitor); - - sd_netlink_unref(m->rtnl); - sd_event_source_unref(m->rtnl_event_source); - - manager_llmnr_stop(m); - manager_mdns_stop(m); - - sd_bus_slot_unref(m->prepare_for_sleep_slot); - sd_event_source_unref(m->bus_retry_event_source); - sd_bus_unref(m->bus); - - sd_event_source_unref(m->sigusr1_event_source); - - sd_event_unref(m->event); - - dns_resource_key_unref(m->llmnr_host_ipv4_key); - dns_resource_key_unref(m->llmnr_host_ipv6_key); - - sd_event_source_unref(m->hostname_event_source); - safe_close(m->hostname_fd); - free(m->llmnr_hostname); - free(m->mdns_hostname); - - dns_trust_anchor_flush(&m->trust_anchor); - manager_etc_hosts_flush(m); - - free(m); - - return NULL; -} - -int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - union { - struct cmsghdr header; /* For alignment */ - uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo)) - + CMSG_SPACE(int) /* ttl/hoplimit */ - + EXTRA_CMSG_SPACE /* kernel appears to require extra buffer space */]; - } control; - union sockaddr_union sa; - struct msghdr mh = {}; - struct cmsghdr *cmsg; - struct iovec iov; - ssize_t ms, l; - int r; - - assert(m); - assert(fd >= 0); - assert(ret); - - ms = next_datagram_size_fd(fd); - if (ms < 0) - return ms; - - r = dns_packet_new(&p, protocol, ms); - if (r < 0) - return r; - - iov.iov_base = DNS_PACKET_DATA(p); - iov.iov_len = p->allocated; - - mh.msg_name = &sa.sa; - mh.msg_namelen = sizeof(sa); - mh.msg_iov = &iov; - mh.msg_iovlen = 1; - mh.msg_control = &control; - mh.msg_controllen = sizeof(control); - - l = recvmsg(fd, &mh, 0); - if (l < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return -errno; - } - - if (l <= 0) - return -EIO; - - assert(!(mh.msg_flags & MSG_CTRUNC)); - assert(!(mh.msg_flags & MSG_TRUNC)); - - p->size = (size_t) l; - - p->family = sa.sa.sa_family; - p->ipproto = IPPROTO_UDP; - if (p->family == AF_INET) { - p->sender.in = sa.in.sin_addr; - p->sender_port = be16toh(sa.in.sin_port); - } else if (p->family == AF_INET6) { - p->sender.in6 = sa.in6.sin6_addr; - p->sender_port = be16toh(sa.in6.sin6_port); - p->ifindex = sa.in6.sin6_scope_id; - } else - return -EAFNOSUPPORT; - - CMSG_FOREACH(cmsg, &mh) { - - if (cmsg->cmsg_level == IPPROTO_IPV6) { - assert(p->family == AF_INET6); - - switch (cmsg->cmsg_type) { - - case IPV6_PKTINFO: { - struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); - - if (p->ifindex <= 0) - p->ifindex = i->ipi6_ifindex; - - p->destination.in6 = i->ipi6_addr; - break; - } - - case IPV6_HOPLIMIT: - p->ttl = *(int *) CMSG_DATA(cmsg); - break; - - } - } else if (cmsg->cmsg_level == IPPROTO_IP) { - assert(p->family == AF_INET); - - switch (cmsg->cmsg_type) { - - case IP_PKTINFO: { - struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); - - if (p->ifindex <= 0) - p->ifindex = i->ipi_ifindex; - - p->destination.in = i->ipi_addr; - break; - } - - case IP_TTL: - p->ttl = *(int *) CMSG_DATA(cmsg); - break; - } - } - } - - /* The Linux kernel sets the interface index to the loopback - * device if the packet came from the local host since it - * avoids the routing table in such a case. Let's unset the - * interface index in such a case. */ - if (p->ifindex == LOOPBACK_IFINDEX) - p->ifindex = 0; - - if (protocol != DNS_PROTOCOL_DNS) { - /* If we don't know the interface index still, we look for the - * first local interface with a matching address. Yuck! */ - if (p->ifindex <= 0) - p->ifindex = manager_find_ifindex(m, p->family, &p->destination); - } - - *ret = p; - p = NULL; - - return 1; -} - -static int sendmsg_loop(int fd, struct msghdr *mh, int flags) { - int r; - - assert(fd >= 0); - assert(mh); - - for (;;) { - if (sendmsg(fd, mh, flags) >= 0) - return 0; - - if (errno == EINTR) - continue; - - if (errno != EAGAIN) - return -errno; - - r = fd_wait_for_event(fd, POLLOUT, SEND_TIMEOUT_USEC); - if (r < 0) - return r; - if (r == 0) - return -ETIMEDOUT; - } -} - -static int write_loop(int fd, void *message, size_t length) { - int r; - - assert(fd >= 0); - assert(message); - - for (;;) { - if (write(fd, message, length) >= 0) - return 0; - - if (errno == EINTR) - continue; - - if (errno != EAGAIN) - return -errno; - - r = fd_wait_for_event(fd, POLLOUT, SEND_TIMEOUT_USEC); - if (r < 0) - return r; - if (r == 0) - return -ETIMEDOUT; - } -} - -int manager_write(Manager *m, int fd, DnsPacket *p) { - int r; - - 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) - return r; - - return 0; -} - -static int manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_addr *addr, uint16_t port, DnsPacket *p) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - }; - union { - struct cmsghdr header; /* For alignment */ - uint8_t buffer[CMSG_SPACE(sizeof(struct in_pktinfo))]; - } control; - struct msghdr mh = {}; - struct iovec iov; - - assert(m); - assert(fd >= 0); - assert(addr); - assert(port > 0); - assert(p); - - iov.iov_base = DNS_PACKET_DATA(p); - iov.iov_len = p->size; - - sa.in.sin_addr = *addr; - sa.in.sin_port = htobe16(port), - - mh.msg_iov = &iov; - mh.msg_iovlen = 1; - mh.msg_name = &sa.sa; - mh.msg_namelen = sizeof(sa.in); - - if (ifindex > 0) { - struct cmsghdr *cmsg; - struct in_pktinfo *pi; - - zero(control); - - mh.msg_control = &control; - mh.msg_controllen = CMSG_LEN(sizeof(struct in_pktinfo)); - - cmsg = CMSG_FIRSTHDR(&mh); - cmsg->cmsg_len = mh.msg_controllen; - cmsg->cmsg_level = IPPROTO_IP; - cmsg->cmsg_type = IP_PKTINFO; - - pi = (struct in_pktinfo*) CMSG_DATA(cmsg); - pi->ipi_ifindex = ifindex; - } - - return sendmsg_loop(fd, &mh, 0); -} - -static int manager_ipv6_send(Manager *m, int fd, int ifindex, const struct in6_addr *addr, uint16_t port, DnsPacket *p) { - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - }; - union { - struct cmsghdr header; /* For alignment */ - uint8_t buffer[CMSG_SPACE(sizeof(struct in6_pktinfo))]; - } control; - struct msghdr mh = {}; - struct iovec iov; - - assert(m); - assert(fd >= 0); - assert(addr); - assert(port > 0); - assert(p); - - iov.iov_base = DNS_PACKET_DATA(p); - iov.iov_len = p->size; - - sa.in6.sin6_addr = *addr; - sa.in6.sin6_port = htobe16(port), - sa.in6.sin6_scope_id = ifindex; - - mh.msg_iov = &iov; - mh.msg_iovlen = 1; - mh.msg_name = &sa.sa; - mh.msg_namelen = sizeof(sa.in6); - - if (ifindex > 0) { - struct cmsghdr *cmsg; - struct in6_pktinfo *pi; - - zero(control); - - mh.msg_control = &control; - mh.msg_controllen = CMSG_LEN(sizeof(struct in6_pktinfo)); - - cmsg = CMSG_FIRSTHDR(&mh); - cmsg->cmsg_len = mh.msg_controllen; - cmsg->cmsg_level = IPPROTO_IPV6; - cmsg->cmsg_type = IPV6_PKTINFO; - - pi = (struct in6_pktinfo*) CMSG_DATA(cmsg); - pi->ipi6_ifindex = ifindex; - } - - return sendmsg_loop(fd, &mh, 0); -} - -int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p) { - assert(m); - assert(fd >= 0); - assert(addr); - assert(port > 0); - assert(p); - - 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); - else if (family == AF_INET6) - return manager_ipv6_send(m, fd, ifindex, &addr->in6, port, p); - - return -EAFNOSUPPORT; -} - -uint32_t manager_find_mtu(Manager *m) { - uint32_t mtu = 0; - Link *l; - Iterator i; - - /* If we don't know on which link a DNS packet would be - * delivered, let's find the largest MTU that works on all - * interfaces we know of */ - - HASHMAP_FOREACH(l, m->links, i) { - if (l->mtu <= 0) - continue; - - if (mtu <= 0 || l->mtu < mtu) - mtu = l->mtu; - } - - return mtu; -} - -int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr) { - LinkAddress *a; - - assert(m); - - a = manager_find_link_address(m, family, in_addr); - if (a) - return a->link->ifindex; - - return 0; -} - -void manager_refresh_rrs(Manager *m) { - Iterator i; - Link *l; - - assert(m); - - m->llmnr_host_ipv4_key = dns_resource_key_unref(m->llmnr_host_ipv4_key); - m->llmnr_host_ipv6_key = dns_resource_key_unref(m->llmnr_host_ipv6_key); - - HASHMAP_FOREACH(l, m->links, i) { - link_add_rrs(l, true); - link_add_rrs(l, false); - } -} - -int manager_next_hostname(Manager *m) { - const char *p; - uint64_t u, a; - char *h, *k; - int r; - - assert(m); - - p = strchr(m->llmnr_hostname, 0); - assert(p); - - while (p > m->llmnr_hostname) { - if (!strchr("0123456789", p[-1])) - break; - - p--; - } - - if (*p == 0 || safe_atou64(p, &u) < 0 || u <= 0) - u = 1; - - /* Add a random number to the old value. This way we can avoid - * that two hosts pick the same hostname, win on IPv4 and lose - * on IPv6 (or vice versa), and pick the same hostname - * replacement hostname, ad infinitum. We still want the - * numbers to go up monotonically, hence we just add a random - * value 1..10 */ - - random_bytes(&a, sizeof(a)); - u += 1 + a % 10; - - if (asprintf(&h, "%.*s%" PRIu64, (int) (p - m->llmnr_hostname), m->llmnr_hostname, u) < 0) - return -ENOMEM; - - r = dns_name_concat(h, "local", &k); - if (r < 0) { - free(h); - return r; - } - - log_info("Hostname conflict, changing published hostname from '%s' to '%s'.", m->llmnr_hostname, h); - - free(m->llmnr_hostname); - m->llmnr_hostname = h; - - free(m->mdns_hostname); - m->mdns_hostname = k; - - manager_refresh_rrs(m); - - return 0; -} - -LinkAddress* manager_find_link_address(Manager *m, int family, const union in_addr_union *in_addr) { - Iterator i; - Link *l; - - assert(m); - - HASHMAP_FOREACH(l, m->links, i) { - LinkAddress *a; - - a = link_find_address(l, family, in_addr); - if (a) - return a; - } - - return NULL; -} - -bool manager_our_packet(Manager *m, DnsPacket *p) { - assert(m); - assert(p); - - return !!manager_find_link_address(m, p->family, &p->sender); -} - -DnsScope* manager_find_scope(Manager *m, DnsPacket *p) { - Link *l; - - assert(m); - assert(p); - - l = hashmap_get(m->links, INT_TO_PTR(p->ifindex)); - if (!l) - return NULL; - - switch (p->protocol) { - case DNS_PROTOCOL_LLMNR: - if (p->family == AF_INET) - return l->llmnr_ipv4_scope; - else if (p->family == AF_INET6) - return l->llmnr_ipv6_scope; - - break; - - case DNS_PROTOCOL_MDNS: - if (p->family == AF_INET) - return l->mdns_ipv4_scope; - else if (p->family == AF_INET6) - return l->mdns_ipv6_scope; - - break; - - default: - break; - } - - return NULL; -} - -void manager_verify_all(Manager *m) { - DnsScope *s; - - assert(m); - - LIST_FOREACH(scopes, s, m->dns_scopes) - dns_zone_verify_all(&s->zone); -} - -int manager_is_own_hostname(Manager *m, const char *name) { - int r; - - assert(m); - assert(name); - - if (m->llmnr_hostname) { - r = dns_name_equal(name, m->llmnr_hostname); - if (r != 0) - return r; - } - - if (m->mdns_hostname) - return dns_name_equal(name, m->mdns_hostname); - - return 0; -} - -int manager_compile_dns_servers(Manager *m, OrderedSet **dns) { - DnsServer *s; - Iterator i; - Link *l; - int r; - - assert(m); - assert(dns); - - r = ordered_set_ensure_allocated(dns, &dns_server_hash_ops); - if (r < 0) - return r; - - /* First add the system-wide servers and domains */ - LIST_FOREACH(servers, s, m->dns_servers) { - r = ordered_set_put(*dns, s); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - - /* Then, add the per-link servers */ - HASHMAP_FOREACH(l, m->links, i) { - LIST_FOREACH(servers, s, l->dns_servers) { - r = ordered_set_put(*dns, s); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - } - - /* If we found nothing, add the fallback servers */ - if (ordered_set_isempty(*dns)) { - LIST_FOREACH(servers, s, m->fallback_dns_servers) { - r = ordered_set_put(*dns, s); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - } - - return 0; -} - -int manager_compile_search_domains(Manager *m, OrderedSet **domains) { - DnsSearchDomain *d; - Iterator i; - Link *l; - int r; - - assert(m); - assert(domains); - - r = ordered_set_ensure_allocated(domains, &dns_name_hash_ops); - if (r < 0) - return r; - - LIST_FOREACH(domains, d, m->search_domains) { - r = ordered_set_put(*domains, d->name); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - - HASHMAP_FOREACH(l, m->links, i) { - - LIST_FOREACH(domains, d, l->search_domains) { - r = ordered_set_put(*domains, d->name); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - } - - return 0; -} - -DnssecMode manager_get_dnssec_mode(Manager *m) { - assert(m); - - if (m->dnssec_mode != _DNSSEC_MODE_INVALID) - return m->dnssec_mode; - - return DNSSEC_NO; -} - -bool manager_dnssec_supported(Manager *m) { - DnsServer *server; - Iterator i; - Link *l; - - assert(m); - - if (manager_get_dnssec_mode(m) == DNSSEC_NO) - return false; - - server = manager_get_dns_server(m); - if (server && !dns_server_dnssec_supported(server)) - return false; - - HASHMAP_FOREACH(l, m->links, i) - if (!link_dnssec_supported(l)) - return false; - - return true; -} - -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 deleted file mode 100644 index e82a824f29..0000000000 --- a/src/resolve/resolved-manager.h +++ /dev/null @@ -1,171 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include "sd-event.h" -#include "sd-netlink.h" -#include "sd-network.h" - -#include "hashmap.h" -#include "list.h" -#include "ordered-set.h" -#include "resolve-util.h" - -typedef struct Manager Manager; - -#include "resolved-dns-query.h" -#include "resolved-dns-search-domain.h" -#include "resolved-dns-server.h" -#include "resolved-dns-stream.h" -#include "resolved-dns-trust-anchor.h" -#include "resolved-link.h" - -#define MANAGER_SEARCH_DOMAINS_MAX 32 -#define MANAGER_DNS_SERVERS_MAX 32 - -struct Manager { - sd_event *event; - - ResolveSupport llmnr_support; - ResolveSupport mdns_support; - DnssecMode dnssec_mode; - - /* Network */ - Hashmap *links; - - sd_netlink *rtnl; - sd_event_source *rtnl_event_source; - - sd_network_monitor *network_monitor; - sd_event_source *network_event_source; - - /* DNS query management */ - Hashmap *dns_transactions; - LIST_HEAD(DnsQuery, dns_queries); - unsigned n_dns_queries; - - LIST_HEAD(DnsStream, dns_streams); - unsigned n_dns_streams; - - /* Unicast dns */ - LIST_HEAD(DnsServer, dns_servers); - LIST_HEAD(DnsServer, fallback_dns_servers); - unsigned n_dns_servers; /* counts both main and fallback */ - DnsServer *current_dns_server; - - LIST_HEAD(DnsSearchDomain, search_domains); - unsigned n_search_domains; - bool permit_domain_search; - - bool need_builtin_fallbacks:1; - - bool read_resolv_conf:1; - usec_t resolv_conf_mtime; - - DnsTrustAnchor trust_anchor; - - LIST_HEAD(DnsScope, dns_scopes); - DnsScope *unicast_scope; - - /* LLMNR */ - int llmnr_ipv4_udp_fd; - int llmnr_ipv6_udp_fd; - int llmnr_ipv4_tcp_fd; - int llmnr_ipv6_tcp_fd; - - sd_event_source *llmnr_ipv4_udp_event_source; - sd_event_source *llmnr_ipv6_udp_event_source; - sd_event_source *llmnr_ipv4_tcp_event_source; - sd_event_source *llmnr_ipv6_tcp_event_source; - - /* mDNS */ - int mdns_ipv4_fd; - int mdns_ipv6_fd; - - sd_event_source *mdns_ipv4_event_source; - sd_event_source *mdns_ipv6_event_source; - - /* dbus */ - sd_bus *bus; - sd_event_source *bus_retry_event_source; - - /* The hostname we publish on LLMNR and mDNS */ - char *llmnr_hostname; - char *mdns_hostname; - DnsResourceKey *llmnr_host_ipv4_key; - DnsResourceKey *llmnr_host_ipv6_key; - - /* Watch the system hostname */ - int hostname_fd; - sd_event_source *hostname_event_source; - - /* Watch for system suspends */ - 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 */ - -int manager_new(Manager **ret); -Manager* manager_free(Manager *m); - -int manager_start(Manager *m); - -uint32_t manager_find_mtu(Manager *m); - -int manager_write(Manager *m, int fd, DnsPacket *p); -int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p); -int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret); - -int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr); -LinkAddress* manager_find_link_address(Manager *m, int family, const union in_addr_union *in_addr); - -void manager_refresh_rrs(Manager *m); -int manager_next_hostname(Manager *m); - -bool manager_our_packet(Manager *m, DnsPacket *p); -DnsScope* manager_find_scope(Manager *m, DnsPacket *p); - -void manager_verify_all(Manager *m); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); - -#define EXTRA_CMSG_SPACE 1024 - -int manager_is_own_hostname(Manager *m, const char *name); - -int manager_compile_dns_servers(Manager *m, OrderedSet **servers); -int manager_compile_search_domains(Manager *m, OrderedSet **domains); - -DnssecMode manager_get_dnssec_mode(Manager *m); -bool manager_dnssec_supported(Manager *m); - -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 deleted file mode 100644 index bc8b8b809b..0000000000 --- a/src/resolve/resolved-mdns.c +++ /dev/null @@ -1,287 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 Daniel Mack - - 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 . - ***/ - -#include -#include -#include - -#include "fd-util.h" -#include "resolved-manager.h" -#include "resolved-mdns.h" - -void manager_mdns_stop(Manager *m) { - assert(m); - - m->mdns_ipv4_event_source = sd_event_source_unref(m->mdns_ipv4_event_source); - m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); - - m->mdns_ipv6_event_source = sd_event_source_unref(m->mdns_ipv6_event_source); - m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); -} - -int manager_mdns_start(Manager *m) { - int r; - - assert(m); - - if (m->mdns_support == RESOLVE_SUPPORT_NO) - return 0; - - r = manager_mdns_ipv4_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - if (socket_ipv6_is_supported()) { - r = manager_mdns_ipv6_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - } - - return 0; - -eaddrinuse: - log_warning("There appears to be another mDNS responder running. Turning off mDNS support."); - m->mdns_support = RESOLVE_SUPPORT_NO; - manager_mdns_stop(m); - - return 0; -} - -static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - Manager *m = userdata; - DnsScope *scope; - int r; - - r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p); - if (r <= 0) - return r; - - scope = manager_find_scope(m, p); - if (!scope) { - log_warning("Got mDNS UDP packet on unknown scope. Ignoring."); - return 0; - } - - if (dns_packet_validate_reply(p) > 0) { - DnsResourceRecord *rr; - - log_debug("Got mDNS reply packet"); - - /* - * mDNS is different from regular DNS and LLMNR with regard to handling responses. - * While on other protocols, we can ignore every answer that doesn't match a question - * we broadcast earlier, RFC6762, section 18.1 recommends looking at and caching all - * incoming information, regardless of the DNS packet ID. - * - * Hence, extract the packet here, and try to find a transaction for answer the we got - * and complete it. Also store the new information in scope's cache. - */ - r = dns_packet_extract(p); - if (r < 0) { - log_debug("mDNS packet extraction failed."); - return 0; - } - - dns_scope_check_conflicts(scope, p); - - DNS_ANSWER_FOREACH(rr, p->answer) { - const char *name = DNS_RESOURCE_KEY_NAME(rr->key); - DnsTransaction *t; - - /* If the received reply packet contains ANY record that is not .local or .in-addr.arpa, - * we assume someone's playing tricks on us and discard the packet completely. */ - if (!(dns_name_endswith(name, "in-addr.arpa") > 0 || - dns_name_endswith(name, "local") > 0)) - return 0; - - t = dns_scope_find_transaction(scope, rr->key, false); - if (t) - dns_transaction_process_reply(t, p); - } - - 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)); - - dns_scope_process_query(scope, NULL, p); - } else - log_debug("Invalid mDNS UDP packet."); - - return 0; -} - -int manager_mdns_ipv4_fd(Manager *m) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(MDNS_PORT), - }; - static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; - int r; - - assert(m); - - if (m->mdns_ipv4_fd >= 0) - return m->mdns_ipv4_fd; - - m->mdns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->mdns_ipv4_fd < 0) - return -errno; - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* Disable Don't-Fragment bit in the IP header */ - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->mdns_ipv4_fd, &sa.sa, sizeof(sa.in)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->mdns_ipv4_event_source, m->mdns_ipv4_fd, EPOLLIN, on_mdns_packet, m); - if (r < 0) - goto fail; - - return m->mdns_ipv4_fd; - -fail: - m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); - return r; -} - -int manager_mdns_ipv6_fd(Manager *m) { - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - .in6.sin6_port = htobe16(MDNS_PORT), - }; - static const int one = 1, ttl = 255; - int r; - - assert(m); - - if (m->mdns_ipv6_fd >= 0) - return m->mdns_ipv6_fd; - - m->mdns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->mdns_ipv6_fd < 0) - return -errno; - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->mdns_ipv6_fd, &sa.sa, sizeof(sa.in6)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->mdns_ipv6_event_source, m->mdns_ipv6_fd, EPOLLIN, on_mdns_packet, m); - if (r < 0) - goto fail; - - return m->mdns_ipv6_fd; - -fail: - m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); - return r; -} diff --git a/src/resolve/resolved-mdns.h b/src/resolve/resolved-mdns.h deleted file mode 100644 index 5d274648f4..0000000000 --- a/src/resolve/resolved-mdns.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 Daniel Mack - - 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 . -***/ - -#include "resolved-manager.h" - -#define MDNS_PORT 5353 - -int manager_mdns_ipv4_fd(Manager *m); -int manager_mdns_ipv6_fd(Manager *m); - -void manager_mdns_stop(Manager *m); -int manager_mdns_start(Manager *m); diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c deleted file mode 100644 index 065427b690..0000000000 --- a/src/resolve/resolved-resolv-conf.c +++ /dev/null @@ -1,267 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . - ***/ - -#include - -#include "alloc-util.h" -#include "dns-domain.h" -#include "fd-util.h" -#include "fileio-label.h" -#include "fileio.h" -#include "ordered-set.h" -#include "resolved-conf.h" -#include "resolved-resolv-conf.h" -#include "string-util.h" -#include "strv.h" - -int manager_read_resolv_conf(Manager *m) { - _cleanup_fclose_ FILE *f = NULL; - struct stat st, own; - char line[LINE_MAX]; - usec_t t; - int r; - - assert(m); - - /* Reads the system /etc/resolv.conf, if it exists and is not - * symlinked to our own resolv.conf instance */ - - if (!m->read_resolv_conf) - return 0; - - r = stat("/etc/resolv.conf", &st); - if (r < 0) { - if (errno == ENOENT) - return 0; - - r = log_warning_errno(errno, "Failed to stat /etc/resolv.conf: %m"); - goto clear; - } - - /* Have we already seen the file? */ - t = timespec_load(&st.st_mtim); - if (t == m->resolv_conf_mtime) - return 0; - - /* Is it symlinked to our own file? */ - if (stat("/run/systemd/resolve/resolv.conf", &own) >= 0 && - st.st_dev == own.st_dev && - st.st_ino == own.st_ino) - return 0; - - f = fopen("/etc/resolv.conf", "re"); - if (!f) { - if (errno == ENOENT) - return 0; - - r = log_warning_errno(errno, "Failed to open /etc/resolv.conf: %m"); - goto clear; - } - - if (fstat(fileno(f), &st) < 0) { - r = log_error_errno(errno, "Failed to stat open file: %m"); - goto clear; - } - - dns_server_mark_all(m->dns_servers); - dns_search_domain_mark_all(m->search_domains); - - FOREACH_LINE(line, f, r = -errno; goto clear) { - const char *a; - char *l; - - l = strstrip(line); - if (*l == '#' || *l == ';') - continue; - - a = first_word(l, "nameserver"); - if (a) { - r = manager_add_dns_server_by_string(m, DNS_SERVER_SYSTEM, a); - if (r < 0) - log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a); - - continue; - } - - a = first_word(l, "domain"); - if (!a) /* We treat "domain" lines, and "search" lines as equivalent, and add both to our list. */ - a = first_word(l, "search"); - if (a) { - r = manager_parse_search_domains_and_warn(m, a); - if (r < 0) - log_warning_errno(r, "Failed to parse search domain string '%s', ignoring.", a); - } - } - - m->resolv_conf_mtime = t; - - /* Flush out all servers and search domains that are still - * marked. Those are then ones that didn't appear in the new - * /etc/resolv.conf */ - dns_server_unlink_marked(m->dns_servers); - dns_search_domain_unlink_marked(m->search_domains); - - /* Whenever /etc/resolv.conf changes, start using the first - * DNS server of it. This is useful to deal with broken - * network managing implementations (like NetworkManager), - * that when connecting to a VPN place both the VPN DNS - * servers and the local ones in /etc/resolv.conf. Without - * resetting the DNS server to use back to the first entry we - * will continue to use the local one thus being unable to - * resolve VPN domains. */ - manager_set_dns_server(m, m->dns_servers); - - /* Unconditionally flush the cache when /etc/resolv.conf is - * modified, even if the data it contained was completely - * identical to the previous version we used. We do this - * because altering /etc/resolv.conf is typically done when - * the network configuration changes, and that should be - * enough to flush the global unicast DNS cache. */ - if (m->unicast_scope) - dns_cache_flush(&m->unicast_scope->cache); - - return 0; - -clear: - dns_server_unlink_all(m->dns_servers); - dns_search_domain_unlink_all(m->search_domains); - return r; -} - -static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { - assert(s); - assert(f); - assert(count); - - (void) dns_server_string(s); - - if (!s->server_string) { - log_warning("Our of memory, or invalid DNS address. Ignoring server."); - return; - } - - if (*count == MAXNS) - fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f); - (*count) ++; - - fprintf(f, "nameserver %s\n", s->server_string); -} - -static void write_resolv_conf_search( - const char *domain, - FILE *f, - unsigned *count, - unsigned *length) { - - assert(domain); - assert(f); - assert(length); - - if (*count >= MAXDNSRCH || - *length + strlen(domain) > 256) { - if (*count == MAXDNSRCH) - fputs(" # Too many search domains configured, remaining ones ignored.", f); - if (*length <= 256) - fputs(" # Total length of all search domains is too long, remaining ones ignored.", f); - - return; - } - - (*length) += strlen(domain); - (*count) ++; - - fputc(' ', f); - fputs(domain, f); -} - -static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) { - Iterator i; - - fputs("# This file is managed by systemd-resolved(8). Do not edit.\n#\n" - "# Third party programs must not access this file directly, but\n" - "# only through the symlink at /etc/resolv.conf. To manage\n" - "# resolv.conf(5) in a different way, replace the symlink by a\n" - "# static file or a different symlink.\n\n", f); - - if (ordered_set_isempty(dns)) - fputs("# No DNS servers known.\n", f); - else { - unsigned count = 0; - DnsServer *s; - - ORDERED_SET_FOREACH(s, dns, i) - write_resolv_conf_server(s, f, &count); - } - - if (!ordered_set_isempty(domains)) { - unsigned length = 0, count = 0; - char *domain; - - fputs("search", f); - ORDERED_SET_FOREACH(domain, domains, i) - write_resolv_conf_search(domain, f, &count, &length); - fputs("\n", f); - } - - return fflush_and_check(f); -} - -int manager_write_resolv_conf(Manager *m) { - - _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL; - _cleanup_free_ char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(m); - - /* Read the system /etc/resolv.conf first */ - manager_read_resolv_conf(m); - - /* Add the full list to a set, to filter out duplicates */ - r = manager_compile_dns_servers(m, &dns); - if (r < 0) - return r; - - r = manager_compile_search_domains(m, &domains); - if (r < 0) - return r; - - r = fopen_temporary_label(PRIVATE_RESOLV_CONF, PRIVATE_RESOLV_CONF, &f, &temp_path); - if (r < 0) - return r; - - fchmod(fileno(f), 0644); - - r = write_resolv_conf_contents(f, dns, domains); - if (r < 0) - goto fail; - - if (rename(temp_path, PRIVATE_RESOLV_CONF) < 0) { - r = -errno; - goto fail; - } - - return 0; - -fail: - (void) unlink(PRIVATE_RESOLV_CONF); - (void) unlink(temp_path); - return r; -} diff --git a/src/resolve/resolved-resolv-conf.h b/src/resolve/resolved-resolv-conf.h deleted file mode 100644 index 75fa080e4c..0000000000 --- a/src/resolve/resolved-resolv-conf.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#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 deleted file mode 100644 index c7e2ab14d6..0000000000 --- a/src/resolve/resolved.c +++ /dev/null @@ -1,112 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include "sd-daemon.h" -#include "sd-event.h" - -#include "capability-util.h" -#include "mkdir.h" -#include "resolved-conf.h" -#include "resolved-manager.h" -#include "resolved-resolv-conf.h" -#include "selinux-util.h" -#include "signal-util.h" -#include "user-util.h" - -int main(int argc, char *argv[]) { - _cleanup_(manager_freep) Manager *m = NULL; - const char *user = "systemd-resolve"; - uid_t uid; - gid_t gid; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - if (argc != 1) { - log_error("This program takes no arguments."); - r = -EINVAL; - goto finish; - } - - umask(0022); - - r = mac_selinux_init(NULL); - if (r < 0) { - log_error_errno(r, "SELinux setup failed: %m"); - goto finish; - } - - r = get_user_creds(&user, &uid, &gid, NULL, NULL); - if (r < 0) { - log_error_errno(r, "Cannot resolve user name %s: %m", user); - goto finish; - } - - /* Always create the directory where resolv.conf will live */ - r = mkdir_safe_label("/run/systemd/resolve", 0755, uid, gid); - if (r < 0) { - log_error_errno(r, "Could not create runtime directory: %m"); - goto finish; - } - - r = drop_privileges(uid, gid, 0); - if (r < 0) - goto finish; - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGUSR1, -1) >= 0); - - r = manager_new(&m); - if (r < 0) { - log_error_errno(r, "Could not create manager: %m"); - goto finish; - } - - r = manager_start(m); - if (r < 0) { - log_error_errno(r, "Failed to start manager: %m"); - goto finish; - } - - /* Write finish default resolv.conf to avoid a dangling - * symlink */ - r = manager_write_resolv_conf(m); - if (r < 0) - log_warning_errno(r, "Could not create "PRIVATE_RESOLV_CONF": %m"); - - sd_notify(false, - "READY=1\n" - "STATUS=Processing requests..."); - - r = sd_event_loop(m->event); - if (r < 0) { - log_error_errno(r, "Event loop failed: %m"); - goto finish; - } - - sd_event_get_exit_code(m->event, &r); - -finish: - sd_notify(false, - "STOPPING=1\n" - "STATUS=Shutting down..."); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in deleted file mode 100644 index efc9c6733a..0000000000 --- a/src/resolve/resolved.conf.in +++ /dev/null @@ -1,19 +0,0 @@ -# This file is part of systemd. -# -# 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. -# -# Entries in this file show the compile time defaults. -# You can change settings by editing this file. -# Defaults can be restored by simply deleting this file. -# -# See resolved.conf(5) for details - -[Resolve] -#DNS= -#FallbackDNS=@DNS_SERVERS@ -#Domains= -#LLMNR=yes -#DNSSEC=no diff --git a/src/resolve/src/.gitignore b/src/resolve/src/.gitignore new file mode 100644 index 0000000000..f0835923b7 --- /dev/null +++ b/src/resolve/src/.gitignore @@ -0,0 +1,6 @@ +/resolved-gperf.c +/resolved.conf +/dns_type-from-name.gperf +/dns_type-from-name.h +/dns_type-list.txt +/dns_type-to-name.h diff --git a/src/resolve/src/Makefile b/src/resolve/src/Makefile new file mode 100644 index 0000000000..3aa6a73e4c --- /dev/null +++ b/src/resolve/src/Makefile @@ -0,0 +1,204 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +if ENABLE_RESOLVED + +systemd_resolved_SOURCES = \ + src/resolve/resolved.c \ + src/resolve/resolved-manager.c \ + src/resolve/resolved-manager.h \ + src/resolve/resolved-conf.c \ + src/resolve/resolved-conf.h \ + src/resolve/resolved-resolv-conf.c \ + src/resolve/resolved-resolv-conf.h \ + src/resolve/resolved-bus.c \ + 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 \ + src/resolve/resolved-mdns.c \ + src/resolve/resolved-def.h \ + src/resolve/resolved-dns-rr.h \ + src/resolve/resolved-dns-rr.c \ + src/resolve/resolved-dns-question.h \ + src/resolve/resolved-dns-question.c \ + src/resolve/resolved-dns-answer.h \ + src/resolve/resolved-dns-answer.c \ + src/resolve/resolved-dns-packet.h \ + 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 \ + src/resolve/resolved-dns-scope.c \ + src/resolve/resolved-dns-server.h \ + src/resolve/resolved-dns-server.c \ + src/resolve/resolved-dns-search-domain.h \ + src/resolve/resolved-dns-search-domain.c \ + src/resolve/resolved-dns-cache.h \ + src/resolve/resolved-dns-cache.c \ + src/resolve/resolved-dns-zone.h \ + src/resolve/resolved-dns-zone.c \ + src/resolve/resolved-dns-stream.h \ + src/resolve/resolved-dns-stream.c \ + src/resolve/resolved-dns-dnssec.h \ + 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 + +nodist_systemd_resolved_SOURCES = \ + src/resolve/dns_type-from-name.h \ + src/resolve/dns_type-to-name.h \ + src/resolve/resolved-gperf.c + +systemd_resolved_LDADD = \ + libsystemd-network.la \ + libshared.la + +rootlibexec_PROGRAMS += \ + systemd-resolved + +nodist_systemunit_DATA += \ + units/systemd-resolved.service + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.resolve1.busname + +dist_dbuspolicy_DATA += \ + src/resolve/org.freedesktop.resolve1.conf + +dist_dbussystemservice_DATA += \ + src/resolve/org.freedesktop.resolve1.service + +SYSTEM_UNIT_ALIASES += \ + systemd-resolved.service dbus-org.freedesktop.resolve1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.resolve1.busname + +GENERAL_ALIASES += \ + $(systemunitdir)/systemd-resolved.service $(pkgsysconfdir)/system/multi-user.target.wants/systemd-resolved.service + +nodist_pkgsysconf_DATA += \ + src/resolve/resolved.conf + +libnss_resolve_la_SOURCES = \ + src/nss-resolve/nss-resolve.sym \ + src/nss-resolve/nss-resolve.c + +libnss_resolve_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -module \ + -export-dynamic \ + -avoid-version \ + -shared \ + -shrext .so.2 \ + -Wl,--version-script=$(top_srcdir)/src/nss-resolve/nss-resolve.sym + +libnss_resolve_la_LIBADD = \ + libsystemd-internal.la \ + -ldl + +lib_LTLIBRARIES += \ + libnss_resolve.la + +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 \ + src/resolve/resolved-dns-rr.h \ + src/resolve/resolved-dns-answer.c \ + src/resolve/resolved-dns-answer.h \ + src/resolve/resolved-dns-question.c \ + src/resolve/resolved-dns-question.h \ + src/resolve/dns-type.c \ + src/resolve/dns-type.h + +nodist_systemd_resolve_SOURCES = \ + src/resolve/dns_type-from-name.h \ + src/resolve/dns_type-to-name.h + +systemd_resolve_LDADD = \ + libshared.la + +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 \ + src/resolve/resolved-dns-packet.h \ + src/resolve/resolved-dns-rr.c \ + src/resolve/resolved-dns-rr.h \ + src/resolve/resolved-dns-answer.c \ + src/resolve/resolved-dns-answer.h \ + src/resolve/resolved-dns-question.c \ + src/resolve/resolved-dns-question.h \ + src/resolve/resolved-dns-dnssec.c \ + src/resolve/resolved-dns-dnssec.h \ + src/resolve/dns-type.c \ + src/resolve/dns-type.h + +test_dnssec_LDADD = \ + libshared.la + +test_dnssec_complex_SOURCES = \ + src/resolve/test-dnssec-complex.c \ + src/resolve/dns-type.c \ + src/resolve/dns-type.h + +test_dnssec_complex_LDADD = \ + libshared.la + +endif + +gperf_txt_sources += \ + src/resolve/dns_type-list.txt + +gperf_gperf_sources += \ + src/resolve/resolved-gperf.gperf + +EXTRA_DIST += \ + units/systemd-resolved.service.m4.in \ + src/resolve/resolved.conf.in + diff --git a/src/resolve/src/RFCs b/src/resolve/src/RFCs new file mode 100644 index 0000000000..22004a00cd --- /dev/null +++ b/src/resolve/src/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/src/dns-type.c b/src/resolve/src/dns-type.c new file mode 100644 index 0000000000..b2f479cae5 --- /dev/null +++ b/src/resolve/src/dns-type.c @@ -0,0 +1,306 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include "dns-type.h" +#include "parse-util.h" +#include "string-util.h" + +typedef const struct { + uint16_t type; + const char *name; +} dns_type; + +static const struct dns_type_name * +lookup_dns_type (register const char *str, register unsigned int len); + +#include "dns_type-from-name.h" +#include "dns_type-to-name.h" + +int dns_type_from_string(const char *s) { + const struct dns_type_name *sc; + + assert(s); + + sc = lookup_dns_type(s, strlen(s)); + 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 + * . */ + + 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, + + /* 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 */ +} + +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/src/dns-type.h b/src/resolve/src/dns-type.h new file mode 100644 index 0000000000..a6c1630021 --- /dev/null +++ b/src/resolve/src/dns-type.h @@ -0,0 +1,154 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#pragma once + +#include "macro.h" + +/* DNS record types, taken from + * http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml. + */ +enum { + /* Normal records */ + DNS_TYPE_A = 0x01, + DNS_TYPE_NS, + DNS_TYPE_MD, + DNS_TYPE_MF, + DNS_TYPE_CNAME, + DNS_TYPE_SOA, + DNS_TYPE_MB, + DNS_TYPE_MG, + DNS_TYPE_MR, + DNS_TYPE_NULL, + DNS_TYPE_WKS, + DNS_TYPE_PTR, + DNS_TYPE_HINFO, + DNS_TYPE_MINFO, + DNS_TYPE_MX, + DNS_TYPE_TXT, + DNS_TYPE_RP, + DNS_TYPE_AFSDB, + DNS_TYPE_X25, + DNS_TYPE_ISDN, + DNS_TYPE_RT, + DNS_TYPE_NSAP, + DNS_TYPE_NSAP_PTR, + DNS_TYPE_SIG, + DNS_TYPE_KEY, + DNS_TYPE_PX, + DNS_TYPE_GPOS, + DNS_TYPE_AAAA, + DNS_TYPE_LOC, + DNS_TYPE_NXT, + DNS_TYPE_EID, + DNS_TYPE_NIMLOC, + DNS_TYPE_SRV, + DNS_TYPE_ATMA, + DNS_TYPE_NAPTR, + DNS_TYPE_KX, + DNS_TYPE_CERT, + DNS_TYPE_A6, + DNS_TYPE_DNAME, + DNS_TYPE_SINK, + DNS_TYPE_OPT, /* EDNS0 option */ + DNS_TYPE_APL, + DNS_TYPE_DS, + DNS_TYPE_SSHFP, + DNS_TYPE_IPSECKEY, + DNS_TYPE_RRSIG, + DNS_TYPE_NSEC, + DNS_TYPE_DNSKEY, + DNS_TYPE_DHCID, + DNS_TYPE_NSEC3, + DNS_TYPE_NSEC3PARAM, + DNS_TYPE_TLSA, + + DNS_TYPE_HIP = 0x37, + DNS_TYPE_NINFO, + DNS_TYPE_RKEY, + DNS_TYPE_TALINK, + DNS_TYPE_CDS, + DNS_TYPE_CDNSKEY, + DNS_TYPE_OPENPGPKEY, + + DNS_TYPE_SPF = 0x63, + DNS_TYPE_NID, + DNS_TYPE_L32, + DNS_TYPE_L64, + DNS_TYPE_LP, + DNS_TYPE_EUI48, + DNS_TYPE_EUI64, + + DNS_TYPE_TKEY = 0xF9, + DNS_TYPE_TSIG, + DNS_TYPE_IXFR, + DNS_TYPE_AXFR, + DNS_TYPE_MAILB, + DNS_TYPE_MAILA, + DNS_TYPE_ANY, + DNS_TYPE_URI, + DNS_TYPE_CAA, + DNS_TYPE_TA = 0x8000, + DNS_TYPE_DLV, + + _DNS_TYPE_MAX, + _DNS_TYPE_INVALID = -1 +}; + +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/src/org.freedesktop.resolve1.conf b/src/resolve/src/org.freedesktop.resolve1.conf new file mode 100644 index 0000000000..25b09774e5 --- /dev/null +++ b/src/resolve/src/org.freedesktop.resolve1.conf @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/resolve/src/org.freedesktop.resolve1.service b/src/resolve/src/org.freedesktop.resolve1.service new file mode 100644 index 0000000000..7ac5c323f0 --- /dev/null +++ b/src/resolve/src/org.freedesktop.resolve1.service @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# 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. + +[D-BUS Service] +Name=org.freedesktop.resolve1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.resolve1.service diff --git a/src/resolve/src/resolve-tool.c b/src/resolve/src/resolve-tool.c new file mode 100644 index 0000000000..824cb267b5 --- /dev/null +++ b/src/resolve/src/resolve-tool.c @@ -0,0 +1,1272 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include + +#include "sd-bus.h" + +#include "af-list.h" +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "escape.h" +#include "in-addr-util.h" +#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 uint16_t arg_type = 0; +static uint16_t arg_class = 0; +static bool arg_legend = true; +static uint64_t arg_flags = 0; + +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]; + + if (!arg_legend) + return; + + if (flags == 0) + return; + + fputs("\n-- Information acquired via", stdout); + + if (flags != 0) + 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_MDNS_IPV4 ? "mDNS/IPv4" : "", + flags & SD_RESOLVED_MDNS_IPV6 ? "mDNS/IPv6" : ""); + + assert_se(format_timespan(rtt_str, sizeof(rtt_str), rtt, 100)); + + printf(" in %s", rtt_str); + + fputc('.', stdout); + fputc('\n', stdout); + + printf("-- Data is authenticated: %s\n", yes_no(flags & SD_RESOLVED_AUTHENTICATED)); +} + +static int resolve_host(sd_bus *bus, const char *name) { + + _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; + const char *canonical = NULL; + char ifname[IF_NAMESIZE] = ""; + unsigned c = 0; + int r; + uint64_t flags; + usec_t ts; + + assert(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 (family %s, interface %s).", name, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveHostname"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "isit", arg_ifindex, name, arg_family, arg_flags); + if (r < 0) + return bus_log_create_error(r); + + ts = now(CLOCK_MONOTONIC); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) + return log_error_errno(r, "%s: resolve call failed: %s", name, bus_error_message(&error, r)); + + ts = now(CLOCK_MONOTONIC) - ts; + + r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { + _cleanup_free_ char *pretty = NULL; + int ifindex, family; + const void *a; + size_t sz; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(reply, "ii", &ifindex, &family); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (!IN_SET(family, AF_INET, AF_INET6)) { + log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); + continue; + } + + if (sz != FAMILY_ADDRESS_SIZE(family)) { + log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); + return -EINVAL; + } + + ifname[0] = 0; + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + r = in_addr_to_string(family, a, &pretty); + if (r < 0) + return log_error_errno(r, "Failed to print address for %s: %m", name); + + printf("%*s%s %s%s%s\n", + (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", + pretty, + isempty(ifname) ? "" : "%", ifname); + + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "st", &canonical, &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (!streq(name, canonical)) + printf("%*s%s (%s)\n", + (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", + canonical); + + if (c == 0) { + log_error("%s: no addresses found", name); + return -ESRCH; + } + + print_source(flags, ts); + + return 0; +} + +static int resolve_address(sd_bus *bus, int family, const union in_addr_union *address, int ifindex) { + _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 *pretty = NULL; + char ifname[IF_NAMESIZE] = ""; + uint64_t flags; + unsigned c = 0; + usec_t ts; + int r; + + assert(bus); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(address); + + if (ifindex <= 0) + ifindex = arg_ifindex; + + r = in_addr_to_string(family, address, &pretty); + if (r < 0) + return log_oom(); + + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + log_debug("Resolving %s%s%s.", pretty, isempty(ifname) ? "" : "%", ifname); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveAddress"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "ii", ifindex, family); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_array(req, 'y', address, FAMILY_ADDRESS_SIZE(family)); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "t", arg_flags); + if (r < 0) + return bus_log_create_error(r); + + ts = now(CLOCK_MONOTONIC); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) { + log_error("%s: resolve call failed: %s", pretty, bus_error_message(&error, r)); + return r; + } + + ts = now(CLOCK_MONOTONIC) - ts; + + r = sd_bus_message_enter_container(reply, 'a', "(is)"); + if (r < 0) + return bus_log_create_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "is")) > 0) { + const char *n; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(reply, "is", &ifindex, &n); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + + ifname[0] = 0; + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + printf("%*s%*s%*s%s %s\n", + (int) strlen(pretty), c == 0 ? pretty : "", + isempty(ifname) ? 0 : 1, c > 0 || isempty(ifname) ? "" : "%", + (int) strlen(ifname), c == 0 ? ifname : "", + c == 0 ? ":" : " ", + n); + + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "t", &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (c == 0) { + log_error("%s: no names found", pretty); + return -ESRCH; + } + + print_source(flags, ts); + + return 0; +} + +static int parse_address(const char *s, int *family, union in_addr_union *address, int *ifindex) { + const char *percent, *a; + int ifi = 0; + int r; + + percent = strchr(s, '%'); + if (percent) { + if (parse_ifindex(percent+1, &ifi) < 0) { + ifi = if_nametoindex(percent+1); + if (ifi <= 0) + return -EINVAL; + } + + a = strndupa(s, percent - s); + } else + a = s; + + r = in_addr_from_string_auto(a, family, address); + if (r < 0) + return r; + + *ifindex = ifi; + return 0; +} + +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] = ""; + unsigned n = 0; + uint64_t flags; + int r; + usec_t ts; + + assert(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(class), dns_type_to_string(type), isempty(ifname) ? "*" : ifname); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveRecord"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, class, type, arg_flags); + if (r < 0) + return bus_log_create_error(r); + + ts = now(CLOCK_MONOTONIC); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) { + log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r)); + return r; + } + + ts = now(CLOCK_MONOTONIC) - ts; + + r = sd_bus_message_enter_container(reply, 'a', "(iqqay)"); + if (r < 0) + return bus_log_parse_error(r); + + 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; + const char *s; + uint16_t c, t; + int ifindex; + const void *d; + size_t l; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(reply, "iqq", &ifindex, &c, &t); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &d, &l); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0); + if (r < 0) + return log_oom(); + + p->refuse_compression = true; + + r = dns_packet_append_blob(p, d, l, NULL); + if (r < 0) + return log_oom(); + + r = dns_packet_read_rr(p, &rr, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse RR: %m"); + + s = dns_resource_record_to_string(rr); + if (!s) + return log_oom(); + + ifname[0] = 0; + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + printf("%s%s%s\n", s, isempty(ifname) ? "" : " # interface ", ifname); + n++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "t", &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (n == 0) { + log_error("%s: no records found", name); + return -ESRCH; + } + + print_source(flags, ts); + + 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; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char ifname[IF_NAMESIZE] = ""; + size_t indent, sz; + uint64_t flags; + const char *p; + unsigned c; + usec_t ts; + int r; + + assert(bus); + assert(domain); + + if (isempty(name)) + name = NULL; + if (isempty(type)) + type = NULL; + + 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); + + if (name) + log_debug("Resolving service \"%s\" of type %s in %s (family %s, interface %s).", name, type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); + else if (type) + log_debug("Resolving service type %s of %s (family %s, interface %s).", type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); + else + log_debug("Resolving service type %s (family %s, interface %s).", domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveService"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "isssit", arg_ifindex, name, type, domain, arg_family, arg_flags); + if (r < 0) + return bus_log_create_error(r); + + ts = now(CLOCK_MONOTONIC); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) + return log_error_errno(r, "Resolve call failed: %s", bus_error_message(&error, r)); + + ts = now(CLOCK_MONOTONIC) - ts; + + r = sd_bus_message_enter_container(reply, 'a', "(qqqsa(iiay)s)"); + if (r < 0) + return bus_log_parse_error(r); + + indent = + (name ? strlen(name) + 1 : 0) + + (type ? strlen(type) + 1 : 0) + + strlen(domain) + 2; + + c = 0; + while ((r = sd_bus_message_enter_container(reply, 'r', "qqqsa(iiay)s")) > 0) { + uint16_t priority, weight, port; + const char *hostname, *canonical; + + r = sd_bus_message_read(reply, "qqqs", &priority, &weight, &port, &hostname); + if (r < 0) + return bus_log_parse_error(r); + + if (name) + printf("%*s%s", (int) strlen(name), c == 0 ? name : "", c == 0 ? "/" : " "); + if (type) + printf("%*s%s", (int) strlen(type), c == 0 ? type : "", c == 0 ? "/" : " "); + + printf("%*s%s %s:%u [priority=%u, weight=%u]\n", + (int) strlen(domain), c == 0 ? domain : "", + c == 0 ? ":" : " ", + hostname, port, + priority, weight); + + r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { + _cleanup_free_ char *pretty = NULL; + int ifindex, family; + const void *a; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(reply, "ii", &ifindex, &family); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (!IN_SET(family, AF_INET, AF_INET6)) { + log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); + continue; + } + + if (sz != FAMILY_ADDRESS_SIZE(family)) { + log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); + return -EINVAL; + } + + ifname[0] = 0; + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + r = in_addr_to_string(family, a, &pretty); + if (r < 0) + return log_error_errno(r, "Failed to print address for %s: %m", name); + + printf("%*s%s%s%s\n", (int) indent, "", pretty, isempty(ifname) ? "" : "%s", ifname); + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "s", &canonical); + if (r < 0) + return bus_log_parse_error(r); + + if (!streq(hostname, canonical)) + printf("%*s(%s)\n", (int) indent, "", canonical); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_enter_container(reply, 'a', "ay"); + if (r < 0) + return bus_log_parse_error(r); + + c = 0; + while ((r = sd_bus_message_read_array(reply, 'y', (const void**) &p, &sz)) > 0) { + _cleanup_free_ char *escaped = NULL; + + escaped = cescape_length(p, sz); + if (!escaped) + return log_oom(); + + printf("%*s%s\n", (int) indent, "", escaped); + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "ssst", &canonical_name, &canonical_type, &canonical_domain, &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (isempty(canonical_name)) + canonical_name = NULL; + if (isempty(canonical_type)) + canonical_type = NULL; + + if (!streq_ptr(name, canonical_name) || + !streq_ptr(type, canonical_type) || + !streq_ptr(domain, canonical_domain)) { + + printf("%*s(", (int) indent, ""); + + if (canonical_name) + printf("%s/", canonical_name); + if (canonical_type) + printf("%s/", canonical_type); + + printf("%s)\n", canonical_domain); + } + + print_source(flags, ts); + + 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; + + if (arg_legend) + puts("Known DNS RR types:"); + for (i = 0; i < _DNS_TYPE_MAX; i++) { + t = dns_type_to_string(i); + if (t) + puts(t); + } +} + +static void help_dns_classes(void) { + int i; + const char *t; + + if (arg_legend) + puts("Known DNS RR classes:"); + for (i = 0; i < _DNS_CLASS_MAX; i++) { + t = dns_class_to_string(i); + if (t) + puts(t); + } +} + +static void help(void) { + printf("%s [OPTIONS...] NAME...\n" + "%s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n\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); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_LEGEND, + ARG_SERVICE, + ARG_CNAME, + 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 }, + { "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 }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h46i:t:c:p:", options, NULL)) >= 0) + switch(c) { + + case 'h': + help(); + return 0; /* done */; + + case ARG_VERSION: + return version(); + + case '4': + arg_family = AF_INET; + break; + + case '6': + arg_family = AF_INET6; + break; + + case 'i': { + int ifi; + + if (parse_ifindex(optarg, &ifi) >= 0) + arg_ifindex = ifi; + else { + ifi = if_nametoindex(optarg); + if (ifi <= 0) + return log_error_errno(errno, "Unknown interface %s: %m", optarg); + + arg_ifindex = ifi; + } + + break; + } + + case 't': + if (streq(optarg, "help")) { + help_dns_types(); + return 0; + } + + r = dns_type_from_string(optarg); + if (r < 0) { + log_error("Failed to parse RR record type %s", optarg); + return r; + } + arg_type = (uint16_t) r; + assert((int) arg_type == r); + + arg_mode = MODE_RESOLVE_RECORD; + break; + + case 'c': + if (streq(optarg, "help")) { + help_dns_classes(); + return 0; + } + + 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; + + case ARG_LEGEND: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --legend= argument"); + + arg_legend = r; + break; + + case 'p': + 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; + else if (streq(optarg, "llmnr-ipv4")) + arg_flags |= SD_RESOLVED_LLMNR_IPV4; + else if (streq(optarg, "llmnr-ipv6")) + arg_flags |= SD_RESOLVED_LLMNR_IPV6; + else { + log_error("Unknown protocol specifier: %s", optarg); + return -EINVAL; + } + + break; + + case ARG_SERVICE: + arg_mode = MODE_RESOLVE_SERVICE; + break; + + case ARG_CNAME: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --cname= argument."); + if (r == 0) + arg_flags |= SD_RESOLVED_NO_CNAME; + else + arg_flags &= ~SD_RESOLVED_NO_CNAME; + break; + + case ARG_SERVICE_ADDRESS: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --service-address= argument."); + if (r == 0) + arg_flags |= SD_RESOLVED_NO_ADDRESS; + else + arg_flags &= ~SD_RESOLVED_NO_ADDRESS; + break; + + case ARG_SERVICE_TXT: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --service-txt= argument."); + if (r == 0) + arg_flags |= SD_RESOLVED_NO_TXT; + else + arg_flags &= ~SD_RESOLVED_NO_TXT; + break; + + case ARG_SEARCH: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --search argument."); + if (r == 0) + arg_flags |= SD_RESOLVED_NO_SEARCH; + else + 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; + + default: + assert_not_reached("Unhandled option"); + } + + if (arg_type == 0 && arg_class != 0) { + log_error("--class= may only be used in conjunction with --type=."); + return -EINVAL; + } + + if (arg_type != 0 && arg_mode != MODE_RESOLVE_RECORD) { + log_error("--service and --type= may not be combined."); + return -EINVAL; + } + + 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 */; +} + +int main(int argc, char **argv) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = sd_bus_open_system(&bus); + if (r < 0) { + log_error_errno(r, "sd_bus_open_system: %m"); + goto finish; + } + + 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; + goto finish; + + } else if (argc == optind + 1) + r = resolve_service(bus, NULL, NULL, argv[optind]); + else if (argc == optind + 2) + r = resolve_service(bus, NULL, argv[optind], argv[optind+1]); + else if (argc == optind + 3) + r = resolve_service(bus, argv[optind], argv[optind+1], argv[optind+2]); + else { + log_error("Too many arguments."); + r = -EINVAL; + goto finish; + } + + break; + + case MODE_STATISTICS: + if (argc > optind) { + log_error("Too many arguments."); + r = -EINVAL; + goto finish; + } + + r = show_statistics(bus); + break; + + case MODE_RESET_STATISTICS: + if (argc > optind) { + log_error("Too many arguments."); + r = -EINVAL; + goto finish; + } + + r = reset_statistics(bus); + break; + } + +finish: + return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/resolve/src/resolved-bus.c b/src/resolve/src/resolved-bus.c new file mode 100644 index 0000000000..fc5e6beca0 --- /dev/null +++ b/src/resolve/src/resolved-bus.c @@ -0,0 +1,1565 @@ +/*** + 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 . +***/ + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-util.h" +#include "dns-domain.h" +#include "resolved-bus.h" +#include "resolved-def.h" +#include "resolved-link-bus.h" + +static int reply_query_state(DnsQuery *q) { + + switch (q->state) { + + case DNS_TRANSACTION_NO_SERVERS: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_NAME_SERVERS, "No appropriate name servers or networks for name found"); + + case DNS_TRANSACTION_TIMEOUT: + return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "Query timed out"); + + case DNS_TRANSACTION_ATTEMPTS_MAX_REACHED: + return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "All attempts to contact name servers or networks failed"); + + case DNS_TRANSACTION_INVALID_REPLY: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); + + 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_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_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", dns_query_string(q)); + else { + const char *rc, *n; + char p[DECIMAL_STR_MAX(q->answer_rcode)]; + + rc = dns_rcode_to_string(q->answer_rcode); + if (!rc) { + sprintf(p, "%i", q->answer_rcode); + rc = p; + } + + n = strjoina(_BUS_ERROR_DNS, 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); + } + + case DNS_TRANSACTION_NULL: + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: + case DNS_TRANSACTION_SUCCESS: + default: + assert_not_reached("Impossible state"); + } +} + +static int append_address(sd_bus_message *reply, DnsResourceRecord *rr, int ifindex) { + int r; + + assert(reply); + assert(rr); + + r = sd_bus_message_open_container(reply, 'r', "iiay"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "i", ifindex); + if (r < 0) + return r; + + if (rr->key->type == DNS_TYPE_A) { + r = sd_bus_message_append(reply, "i", AF_INET); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &rr->a.in_addr, sizeof(struct in_addr)); + + } else if (rr->key->type == DNS_TYPE_AAAA) { + r = sd_bus_message_append(reply, "i", AF_INET6); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &rr->aaaa.in6_addr, sizeof(struct in6_addr)); + } else + return -EAFNOSUPPORT; + + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return 0; +} + +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 ifindex, r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + 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_query_string(q)); + goto finish; + } + if (r < 0) + goto finish; + 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); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "(iiay)"); + if (r < 0) + goto finish; + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + DnsQuestion *question; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = append_address(reply, rr, ifindex); + if (r < 0) + goto finish; + + 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_query_string(q)); + goto finish; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + /* Return the precise spelling and uppercasing and CNAME target reported by the server */ + assert(canonical); + r = sd_bus_message_append( + reply, "st", + DNS_RESOURCE_KEY_NAME(canonical->key), + SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send hostname reply: %m"); + sd_bus_reply_method_errno(q->request, r, NULL); + } + + dns_query_free(q); +} + +static int check_ifindex_flags(int ifindex, uint64_t *flags, uint64_t ok, sd_bus_error *error) { + assert(flags); + + if (ifindex < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); + + if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); + + if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */ + *flags |= SD_RESOLVED_PROTOCOLS_ALL; + + return 0; +} + +static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; + Manager *m = userdata; + const char *hostname; + int family, ifindex; + uint64_t flags; + DnsQuery *q; + int r; + + assert(message); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(message, "isit", &ifindex, &hostname, &family, &flags); + if (r < 0) + return r; + + if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); + + r = dns_name_is_valid(hostname); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", hostname); + + r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_SEARCH, error); + if (r < 0) + return r; + + 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_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) + goto fail; + + r = dns_query_go(q); + if (r < 0) + goto fail; + + return 1; + +fail: + dns_query_free(q); + return r; +} + +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; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + 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_query_string(q)); + goto finish; + } + if (r < 0) + goto finish; + 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); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "(is)"); + if (r < 0) + goto finish; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + 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 = sd_bus_message_append(reply, "(is)", ifindex, rr->ptr.name); + if (r < 0) + goto finish; + + added ++; + } + + if (added <= 0) { + _cleanup_free_ char *ip = NULL; + + in_addr_to_string(q->request_family, &q->request_address, &ip); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Address '%s' does not have any RR of requested type", strna(ip)); + goto finish; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send address reply: %m"); + sd_bus_reply_method_errno(q->request, r, NULL); + } + + dns_query_free(q); +} + +static int bus_method_resolve_address(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + Manager *m = userdata; + int family, ifindex; + uint64_t flags; + const void *d; + DnsQuery *q; + size_t sz; + int r; + + assert(message); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(message, "ii", &ifindex, &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_read(message, "t", &flags); + if (r < 0) + return r; + + r = check_ifindex_flags(ifindex, &flags, 0, error); + if (r < 0) + return r; + + r = dns_question_new_reverse(&question, family, d); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); + if (r < 0) + return r; + + q->request = sd_bus_message_ref(message); + q->request_family = family; + memcpy(&q->request_address, d, sz); + q->complete = bus_method_resolve_address_complete; + + r = dns_query_bus_track(q, message); + if (r < 0) + goto fail; + + r = dns_query_go(q); + if (r < 0) + goto fail; + + return 1; + +fail: + dns_query_free(q); + return r; +} + +static int bus_message_append_rr(sd_bus_message *m, DnsResourceRecord *rr, int ifindex) { + int r; + + assert(m); + assert(rr); + + r = sd_bus_message_open_container(m, 'r', "iqqay"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "iqq", + ifindex, + rr->key->class, + rr->key->type); + if (r < 0) + return r; + + r = dns_resource_record_to_wire_format(rr, false); + if (r < 0) + return r; + + r = sd_bus_message_append_array(m, 'y', rr->wire_format, rr->wire_format_size); + if (r < 0) + return r; + + return sd_bus_message_close_container(m); +} + +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); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + 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_query_string(q)); + goto finish; + } + if (r < 0) + goto finish; + 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); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "(iqqay)"); + if (r < 0) + goto finish; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + 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; + + 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_query_string(q)); + goto finish; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send record reply: %m"); + sd_bus_reply_method_errno(q->request, r, NULL); + } + + dns_query_free(q); +} + +static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + Manager *m = userdata; + uint16_t class, type; + const char *name; + int r, ifindex; + uint64_t flags; + DnsQuery *q; + + assert(message); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(message, "isqqt", &ifindex, &name, &class, &type, &flags); + if (r < 0) + return r; + + 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 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; + + question = dns_question_new(1); + if (!question) + return -ENOMEM; + + key = dns_resource_key_new(class, type, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(question, key); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); + if (r < 0) + return r; + + q->request = sd_bus_message_ref(message); + q->complete = bus_method_resolve_record_complete; + + r = dns_query_bus_track(q, message); + if (r < 0) + goto fail; + + r = dns_query_go(q); + if (r < 0) + goto fail; + + return 1; + +fail: + dns_query_free(q); + return r; +} + +static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; + DnsQuery *aux; + int r; + + assert(q); + assert(reply); + assert(rr); + assert(rr->key); + + if (rr->key->type != DNS_TYPE_SRV) + return 0; + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + /* First, let's see if we could find an appropriate A or AAAA + * 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; + + 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) + continue; + + DNS_ANSWER_FOREACH(zz, aux->answer) { + + r = dns_question_matches_rr(question, zz, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + canonical = dns_resource_record_ref(zz); + break; + } + + if (canonical) + break; + } + + /* Is there are successful A/AAAA lookup for this SRV RR? If not, don't add it */ + if (!canonical) + return 0; + } + + r = sd_bus_message_open_container(reply, 'r', "qqqsa(iiay)s"); + if (r < 0) + return r; + + r = sd_bus_message_append( + reply, + "qqqs", + rr->srv.priority, rr->srv.weight, rr->srv.port, rr->srv.name); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(iiay)"); + if (r < 0) + return r; + + 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) + continue; + if (aux->auxiliary_result != 0) + continue; + + 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) + continue; + + DNS_ANSWER_FOREACH_IFINDEX(zz, ifindex, aux->answer) { + + r = dns_question_matches_rr(question, zz, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + r = append_address(reply, zz, ifindex); + if (r < 0) + return r; + } + } + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + /* Note that above we appended the hostname as encoded in the + * SRV, and here the canonical hostname this maps to. */ + r = sd_bus_message_append(reply, "s", canonical ? DNS_RESOURCE_KEY_NAME(canonical->key) : rr->srv.name); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return 1; +} + +static int append_txt(sd_bus_message *reply, DnsResourceRecord *rr) { + DnsTxtItem *i; + int r; + + assert(reply); + assert(rr); + assert(rr->key); + + if (rr->key->type != DNS_TYPE_TXT) + return 0; + + LIST_FOREACH(items, i, rr->txt.items) { + + if (i->length <= 0) + continue; + + r = sd_bus_message_append_array(reply, 'y', i->data, i->length); + if (r < 0) + return r; + } + + return 1; +} + +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; + int r; + + assert(q); + + if (q->block_all_complete > 0) + return; + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + DnsQuery *bad = NULL; + bool have_success = false; + + LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { + + switch (aux->state) { + + case DNS_TRANSACTION_PENDING: + /* If an auxiliary query is still pending, let's wait */ + return; + + case DNS_TRANSACTION_SUCCESS: + if (aux->auxiliary_result == 0) + have_success = true; + else + bad = aux; + break; + + default: + bad = aux; + break; + } + } + + if (!have_success) { + /* We can only return one error, hence pick the last error we encountered */ + + assert(bad); + + if (bad->state == DNS_TRANSACTION_SUCCESS) { + 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_query_string(bad)); + goto finish; + } + + r = bad->auxiliary_result; + goto finish; + } + + r = reply_query_state(bad); + goto finish; + } + } + + r = sd_bus_message_new_method_return(q->request, &reply); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "(qqqsa(iiay)s)"); + if (r < 0) + goto finish; + + 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; + + 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_query_string(q)); + goto finish; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "ay"); + if (r < 0) + goto finish; + + 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 = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + assert(canonical); + r = dns_service_split(DNS_RESOURCE_KEY_NAME(canonical->key), &name, &type, &domain); + if (r < 0) + goto finish; + + r = sd_bus_message_append( + reply, + "ssst", + name, type, domain, + SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send service reply: %m"); + sd_bus_reply_method_errno(q->request, r, NULL); + } + + dns_query_free(q); +} + +static void resolve_service_hostname_complete(DnsQuery *q) { + int r; + + assert(q); + assert(q->auxiliary_for); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + resolve_service_all_complete(q->auxiliary_for); + return; + } + + r = dns_query_process_cname(q); + 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. */ + q->auxiliary_result = r; + resolve_service_all_complete(q->auxiliary_for); +} + +static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifindex) { + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + DnsQuery *aux; + int r; + + assert(q); + assert(rr); + assert(rr->key); + assert(rr->key->type == DNS_TYPE_SRV); + + /* 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, false); + if (r < 0) + return r; + + r = dns_query_new(q->manager, &aux, question, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH); + if (r < 0) + return r; + + aux->request_family = q->request_family; + aux->complete = resolve_service_hostname_complete; + + r = dns_query_make_auxiliary(aux, q); + if (r == -EAGAIN) { + /* Too many auxiliary lookups? If so, don't complain, + * let's just not add this one, we already have more + * than enough */ + + dns_query_free(aux); + return 0; + } + if (r < 0) + goto fail; + + /* Note that auxiliary queries do not track the original bus + * client, only the primary request does that. */ + + r = dns_query_go(aux); + if (r < 0) + goto fail; + + return 1; + +fail: + dns_query_free(aux); + return r; +} + +static void bus_method_resolve_service_complete(DnsQuery *q) { + bool has_root_domain = false; + DnsResourceRecord *rr; + DnsQuestion *question; + unsigned found = 0; + int ifindex, r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + 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_query_string(q)); + goto finish; + } + if (r < 0) + goto finish; + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ + return; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + 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 (dns_name_is_root(rr->srv.name)) { + has_root_domain = true; + continue; + } + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + q->block_all_complete ++; + r = resolve_service_hostname(q, rr, ifindex); + q->block_all_complete --; + + if (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_query_string(q)); + goto finish; + } + + /* Maybe we are already finished? check now... */ + resolve_service_all_complete(q); + return; + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send service reply: %m"); + sd_bus_reply_method_errno(q->request, r, NULL); + } + + dns_query_free(q); +} + +static int bus_method_resolve_service(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _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; + uint64_t flags; + DnsQuery *q; + int r; + + assert(message); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(message, "isssit", &ifindex, &name, &type, &domain, &family, &flags); + if (r < 0) + return r; + + if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); + + 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); + + if (isempty(type)) + type = NULL; + else if (!dns_srv_type_is_valid(type)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid SRV service type '%s'", type); + + r = dns_name_is_valid(domain); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid domain '%s'", domain); + + if (name && !type) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Service name cannot be specified without service type."); + + r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS, error); + if (r < 0) + return r; + + 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_idna, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), true); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags|SD_RESOLVED_NO_SEARCH); + if (r < 0) + return r; + + q->request = sd_bus_message_ref(message); + q->request_family = family; + q->complete = bus_method_resolve_service_complete; + + r = dns_query_bus_track(q, message); + if (r < 0) + goto fail; + + r = dns_query_go(q); + if (r < 0) + goto fail; + + return 1; + +fail: + dns_query_free(q); + return r; +} + +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', with_ifindex ? "iiay" : "iay"); + if (r < 0) + return r; + + 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; + + r = sd_bus_message_append_array(reply, 'y', &s->address, FAMILY_ADDRESS_SIZE(s->family)); + if (r < 0) + return r; + + return sd_bus_message_close_container(reply); +} + +static int bus_property_get_dns_servers( + 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; + unsigned c = 0; + DnsServer *s; + Iterator i; + Link *l; + int r; + + assert(reply); + assert(m); + + r = sd_bus_message_open_container(reply, 'a', "(iiay)"); + if (r < 0) + return r; + + LIST_FOREACH(servers, s, m->dns_servers) { + r = bus_dns_server_append(reply, s, true); + if (r < 0) + return r; + + c++; + } + + HASHMAP_FOREACH(l, m->links, i) { + LIST_FOREACH(servers, s, l->dns_servers) { + r = bus_dns_server_append(reply, s, true); + if (r < 0) + return r; + c++; + } + } + + if (c == 0) { + LIST_FOREACH(servers, s, m->fallback_dns_servers) { + r = bus_dns_server_append(reply, s, true); + if (r < 0) + return r; + } + } + + return sd_bus_message_close_container(reply); +} + +static int bus_property_get_search_domains( + 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; + DnsSearchDomain *d; + Iterator i; + Link *l; + int r; + + assert(reply); + assert(m); + + 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, "(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, "(isb)", l->ifindex, d->name, d->route_only); + if (r < 0) + return r; + } + } + + 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("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, +}; + +static int on_bus_retry(sd_event_source *s, usec_t usec, void *userdata) { + Manager *m = userdata; + + assert(s); + assert(m); + + m->bus_retry_event_source = sd_event_source_unref(m->bus_retry_event_source); + + manager_connect_bus(m); + return 0; +} + +static int match_prepare_for_sleep(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) { + Manager *m = userdata; + int b, r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) { + log_debug_errno(r, "Failed to parse PrepareForSleep signal: %m"); + return 0; + } + + if (b) + return 0; + + log_debug("Coming back from suspend, verifying all RRs..."); + + manager_verify_all(m); + return 0; +} + +int manager_connect_bus(Manager *m) { + int r; + + assert(m); + + if (m->bus) + return 0; + + r = sd_bus_default_system(&m->bus); + if (r < 0) { + /* We failed to connect? Yuck, we must be in early + * boot. Let's try in 5s again. As soon as we have + * kdbus we can stop doing this... */ + + log_debug_errno(r, "Failed to connect to bus, trying again in 5s: %m"); + + r = sd_event_add_time(m->event, &m->bus_retry_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 5*USEC_PER_SEC, 0, on_bus_retry, 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; + } + + r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/resolve1", "org.freedesktop.resolve1.Manager", resolve_vtable, 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"); + + r = sd_bus_attach_event(m->bus, m->event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + r = sd_bus_add_match(m->bus, &m->prepare_for_sleep_slot, + "type='signal'," + "sender='org.freedesktop.login1'," + "interface='org.freedesktop.login1.Manager'," + "member='PrepareForSleep'," + "path='/org/freedesktop/login1'", + match_prepare_for_sleep, + m); + if (r < 0) + log_error_errno(r, "Failed to add match for PrepareForSleep: %m"); + + return 0; +} diff --git a/src/resolve/src/resolved-bus.h b/src/resolve/src/resolved-bus.h new file mode 100644 index 0000000000..f49e1337d2 --- /dev/null +++ b/src/resolve/src/resolved-bus.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + 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 . +***/ + +#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/src/resolved-conf.c b/src/resolve/src/resolved-conf.c new file mode 100644 index 0000000000..bb93fbfda2 --- /dev/null +++ b/src/resolve/src/resolved-conf.c @@ -0,0 +1,236 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . + ***/ + +#include "alloc-util.h" +#include "conf-parser.h" +#include "def.h" +#include "extract-word.h" +#include "parse-util.h" +#include "resolved-conf.h" +#include "string-util.h" + +int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word) { + union in_addr_union address; + int family, r; + DnsServer *s; + + assert(m); + assert(word); + + r = in_addr_from_string_auto(word, &family, &address); + if (r < 0) + return r; + + /* Filter out duplicates */ + s = dns_server_find(manager_get_first_dns_server(m, type), family, &address); + if (s) { + /* + * Drop the marker. This is used to find the servers + * that ceased to exist, see + * manager_mark_dns_servers() and + * manager_flush_marked_dns_servers(). + */ + dns_server_move_back_and_unmark(s); + return 0; + } + + return dns_server_new(m, NULL, type, NULL, family, &address); +} + +int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string) { + int r; + + assert(m); + assert(string); + + for(;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + r = manager_add_dns_server_by_string(m, type, word); + if (r < 0) + log_warning_errno(r, "Failed to add DNS server address '%s', ignoring.", word); + } + + return 0; +} + +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) + dns_search_domain_move_back_and_unmark(d); + else { + r = dns_search_domain_new(m, &d, DNS_SEARCH_DOMAIN_SYSTEM, NULL, domain); + if (r < 0) + return r; + } + + d->route_only = route_only; + return 0; +} + +int manager_parse_search_domains_and_warn(Manager *m, const char *string) { + int r; + + assert(m); + assert(string); + + for(;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&string, &word, NULL, EXTRACT_QUOTES); + if (r < 0) + return r; + if (r == 0) + break; + + r = manager_add_search_domain_by_string(m, word); + if (r < 0) + log_warning_errno(r, "Failed to add search domain '%s', ignoring.", word); + } + + return 0; +} + +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) { + + Manager *m = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(m); + + if (isempty(rvalue)) + /* Empty assignment means clear the list */ + dns_server_unlink_all(manager_get_first_dns_server(m, ltype)); + else { + /* Otherwise, add to the list */ + r = manager_parse_dns_server_string_and_warn(m, ltype, rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse DNS server string '%s'. Ignoring.", rvalue); + return 0; + } + } + + /* If we have a manual setting, then we stop reading + * /etc/resolv.conf */ + if (ltype == DNS_SERVER_SYSTEM) + m->read_resolv_conf = false; + if (ltype == DNS_SERVER_FALLBACK) + m->need_builtin_fallbacks = false; + + return 0; +} + +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) { + + Manager *m = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(m); + + if (isempty(rvalue)) + /* Empty assignment means clear the list */ + dns_search_domain_unlink_all(m->search_domains); + else { + /* Otherwise, add to the list */ + r = manager_parse_search_domains_and_warn(m, rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse search domains string '%s'. Ignoring.", rvalue); + return 0; + } + } + + /* If we have a manual setting, then we stop reading + * /etc/resolv.conf */ + m->read_resolv_conf = false; + + return 0; +} + +int manager_parse_config_file(Manager *m) { + int r; + + assert(m); + + r = config_parse_many(PKGSYSCONFDIR "/resolved.conf", + CONF_PATHS_NULSTR("systemd/resolved.conf.d"), + "Resolve\0", + config_item_perf_lookup, resolved_gperf_lookup, + false, m); + if (r < 0) + return r; + + if (m->need_builtin_fallbacks) { + r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_FALLBACK, DNS_SERVERS); + if (r < 0) + return r; + } + + return 0; + +} diff --git a/src/resolve/src/resolved-conf.h b/src/resolve/src/resolved-conf.h new file mode 100644 index 0000000000..e1fd2cceec --- /dev/null +++ b/src/resolve/src/resolved-conf.h @@ -0,0 +1,36 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include "resolved-manager.h" + +int manager_parse_config_file(Manager *m); + +int manager_add_search_domain_by_string(Manager *m, const char *domain); +int manager_parse_search_domains_and_warn(Manager *m, const char *string); + +int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word); +int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string); + +const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned length); + +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_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/src/resolved-def.h b/src/resolve/src/resolved-def.h new file mode 100644 index 0000000000..c4c1915b18 --- /dev/null +++ b/src/resolve/src/resolved-def.h @@ -0,0 +1,38 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#define SD_RESOLVED_DNS (UINT64_C(1) << 0) +#define SD_RESOLVED_LLMNR_IPV4 (UINT64_C(1) << 1) +#define SD_RESOLVED_LLMNR_IPV6 (UINT64_C(1) << 2) +#define SD_RESOLVED_MDNS_IPV4 (UINT64_C(1) << 3) +#define SD_RESOLVED_MDNS_IPV6 (UINT64_C(1) << 4) +#define SD_RESOLVED_NO_CNAME (UINT64_C(1) << 5) +#define SD_RESOLVED_NO_TXT (UINT64_C(1) << 6) +#define SD_RESOLVED_NO_ADDRESS (UINT64_C(1) << 7) +#define SD_RESOLVED_NO_SEARCH (UINT64_C(1) << 8) +#define SD_RESOLVED_AUTHENTICATED (UINT64_C(1) << 9) + +#define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6) +#define SD_RESOLVED_MDNS (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6) + +#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS) diff --git a/src/resolve/src/resolved-dns-answer.c b/src/resolve/src/resolved-dns-answer.c new file mode 100644 index 0000000000..7eb303ab95 --- /dev/null +++ b/src/resolve/src/resolved-dns-answer.c @@ -0,0 +1,858 @@ +/*** + 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 . +***/ + +#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) { + DnsAnswer *a; + + a = malloc0(offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * n); + if (!a) + return NULL; + + a->n_ref = 1; + a->n_allocated = n; + + return a; +} + +DnsAnswer *dns_answer_ref(DnsAnswer *a) { + if (!a) + return NULL; + + assert(a->n_ref > 0); + a->n_ref++; + return a; +} + +static void dns_answer_flush(DnsAnswer *a) { + DnsResourceRecord *rr; + + if (!a) + return; + + DNS_ANSWER_FOREACH(rr, a) + dns_resource_record_unref(rr); + + a->n_rrs = 0; +} + +DnsAnswer *dns_answer_unref(DnsAnswer *a) { + if (!a) + return NULL; + + assert(a->n_ref > 0); + + if (a->n_ref == 1) { + dns_answer_flush(a); + free(a); + } else + a->n_ref--; + + return NULL; +} + +static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { + assert(rr); + + if (!a) + return -ENOSPC; + + if (a->n_rrs >= a->n_allocated) + return -ENOSPC; + + 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_FULL(rr, ifindex, flags, source) { + r = dns_answer_add_raw(a, rr, ifindex, flags); + if (r < 0) + return r; + } + + return 0; +} + +int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { + unsigned i; + int r; + + assert(rr); + + if (!a) + return -ENOSPC; + if (a->n_ref > 1) + return -EBUSY; + + for (i = 0; i < a->n_rrs; i++) { + if (a->items[i].ifindex != ifindex) + continue; + + r = dns_resource_record_equal(a->items[i].rr, rr); + if (r < 0) + return r; + if (r > 0) { + /* Don't mix contradicting TTLs (see below) */ + if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0)) + return -EINVAL; + + /* 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, flags); +} + +static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { + DnsResourceRecord *rr; + DnsAnswerFlags flags; + int ifindex, r; + + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, b) { + r = dns_answer_add(a, rr, ifindex, flags); + if (r < 0) + return r; + } + + return 0; +} + +int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { + int r; + + assert(a); + assert(rr); + + r = dns_answer_reserve_or_clone(a, 1); + if (r < 0) + return r; + + return dns_answer_add(*a, rr, ifindex, flags); +} + +int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *soa = NULL; + + soa = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_SOA, name); + if (!soa) + return -ENOMEM; + + soa->ttl = ttl; + + soa->soa.mname = strdup(name); + if (!soa->soa.mname) + return -ENOMEM; + + soa->soa.rname = strappend("root.", name); + if (!soa->soa.rname) + return -ENOMEM; + + soa->soa.serial = 1; + soa->soa.refresh = 1; + soa->soa.retry = 1; + soa->soa.expire = 1; + soa->soa.minimum = ttl; + + return dns_answer_add(a, soa, 0, DNS_ANSWER_AUTHENTICATED); +} + +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); + + 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) + continue; + + if (!ret_flags) + return 1; + + if (found) + flags &= i_flags; + else { + flags = i_flags; + found = true; + } + } + + if (ret_flags) + *ret_flags = flags; + + return found; +} + +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_FLAGS(i, i_flags, a) { + r = dns_resource_record_equal(i, rr); + if (r < 0) + return r; + if (r == 0) + continue; + + if (!ret_flags) + return 1; + + if (found) + flags &= i_flags; + else { + flags = i_flags; + found = true; + } + } + + if (ret_flags) + *ret_flags = flags; + + return found; +} + +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); + + 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_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; + } + } + + 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, DnsAnswerFlags *flags) { + DnsResourceRecord *rr; + DnsAnswerFlags rr_flags; + int r; + + assert(key); + + /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */ + if (!dns_type_may_redirect(key->type)) + return 0; + + 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; + } + } + + return 0; +} + +int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret) { + _cleanup_(dns_answer_unrefp) DnsAnswer *k = NULL; + int r; + + assert(ret); + + if (dns_answer_size(a) <= 0) { + *ret = dns_answer_ref(b); + return 0; + } + + if (dns_answer_size(b) <= 0) { + *ret = dns_answer_ref(a); + return 0; + } + + k = dns_answer_new(a->n_rrs + b->n_rrs); + if (!k) + return -ENOMEM; + + r = dns_answer_add_raw_all(k, a); + if (r < 0) + return r; + + r = dns_answer_add_all(k, b); + if (r < 0) + return r; + + *ret = k; + k = NULL; + + return 0; +} + +int dns_answer_extend(DnsAnswer **a, DnsAnswer *b) { + DnsAnswer *merged; + int r; + + assert(a); + + r = dns_answer_merge(*a, b, &merged); + if (r < 0) + return r; + + dns_answer_unref(*a); + *a = merged; + + return 0; +} + +int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) { + bool found = false, other = false; + DnsResourceRecord *rr; + unsigned i; + int r; + + assert(a); + assert(key); + + /* Remove all entries matching the specified key from *a */ + + DNS_ANSWER_FOREACH(rr, *a) { + r = dns_resource_key_equal(rr->key, key); + 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_key_equal(rr->key, key); + 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_key_equal((*a)->items[i].rr->key, key); + 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_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_FULL(rr_source, ifindex_source, flags_source, source) { + + r = dns_resource_key_equal(rr_source->key, key); + if (r < 0) + return r; + if (r == 0) + continue; + + /* Make space for at least one entry */ + r = dns_answer_reserve_or_clone(a, 1); + if (r < 0) + return r; + + r = dns_answer_add(*a, rr_source, ifindex_source, flags_source|or_flags); + if (r < 0) + return r; + } + + 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; + + if (!a) + return; + + if (a->n_rrs <= 1) + return; + + start = 0; + end = a->n_rrs-1; + + /* RFC 4795, Section 2.6 suggests we should order entries + * depending on whether the sender is a link-local address. */ + + items = newa(DnsAnswerItem, a->n_rrs); + for (i = 0; i < a->n_rrs; i++) { + + if (a->items[i].rr->key->class == DNS_CLASS_IN && + ((a->items[i].rr->key->type == DNS_TYPE_A && in_addr_is_link_local(AF_INET, (union in_addr_union*) &a->items[i].rr->a.in_addr) != prefer_link_local) || + (a->items[i].rr->key->type == DNS_TYPE_AAAA && in_addr_is_link_local(AF_INET6, (union in_addr_union*) &a->items[i].rr->aaaa.in6_addr) != prefer_link_local))) + /* Order address records that are are not preferred to the end of the array */ + items[end--] = a->items[i]; + else + /* Order all other records to the beginning of the array */ + items[start++] = a->items[i]; + } + + assert(start == end+1); + memcpy(a->items, items, sizeof(DnsAnswerItem) * a->n_rrs); +} + +int dns_answer_reserve(DnsAnswer **a, unsigned n_free) { + DnsAnswer *n; + + assert(a); + + if (n_free <= 0) + return 0; + + if (*a) { + unsigned ns; + + if ((*a)->n_ref > 1) + return -EBUSY; + + ns = (*a)->n_rrs + n_free; + + if ((*a)->n_allocated >= ns) + return 0; + + /* Allocate more than we need */ + ns *= 2; + + n = realloc(*a, offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * ns); + if (!n) + return -ENOMEM; + + n->n_allocated = ns; + } else { + n = dns_answer_new(n_free); + if (!n) + return -ENOMEM; + } + + *a = n; + return 0; +} + +int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free) { + _cleanup_(dns_answer_unrefp) DnsAnswer *n = NULL; + int r; + + assert(a); + + /* Tries to extend the DnsAnswer object. And if that's not + * possibly, since we are not the sole owner, then allocate a + * new, appropriately sized one. Either way, after this call + * the object will only have a single reference, and has room + * for at least the specified number of RRs. */ + + r = dns_answer_reserve(a, n_free); + if (r != -EBUSY) + return r; + + assert(*a); + + n = dns_answer_new(((*a)->n_rrs + n_free) * 2); + if (!n) + return -ENOMEM; + + r = dns_answer_add_raw_all(n, *a); + if (r < 0) + return r; + + dns_answer_unref(*a); + *a = n; + n = NULL; + + 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/src/resolved-dns-answer.h b/src/resolve/src/resolved-dns-answer.h new file mode 100644 index 0000000000..8f9c15eab4 --- /dev/null +++ b/src/resolve/src/resolved-dns-answer.h @@ -0,0 +1,143 @@ +#pragma once + +/*** + 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 . +***/ + +typedef struct DnsAnswer DnsAnswer; +typedef struct DnsAnswerItem DnsAnswerItem; + +#include "macro.h" +#include "resolved-dns-rr.h" + +/* A simple array of resource records. We keep track of the + * originating ifindex for each RR where that makes sense, so that we + * can qualify A and AAAA RRs referring to a local link with the + * right ifindex. + * + * 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 { + unsigned n_ref; + unsigned n_rrs, n_allocated; + DnsAnswerItem items[0]; +}; + +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, 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, 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, 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); + +void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local); + +int dns_answer_reserve(DnsAnswer **a, unsigned n_free); +int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free); + +int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key); +int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rr); + +int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags); +int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags); + +bool dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname); + +static inline unsigned dns_answer_size(DnsAnswer *a) { + return a ? a->n_rrs : 0; +} + +void dns_answer_dump(DnsAnswer *answer, FILE *f); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); + +#define _DNS_ANSWER_FOREACH(q, kk, a) \ + for (unsigned UNIQ_T(i, q) = ({ \ + (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ + 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)) + +#define DNS_ANSWER_FOREACH(kk, a) _DNS_ANSWER_FOREACH(UNIQ, kk, a) + +#define _DNS_ANSWER_FOREACH_IFINDEX(q, kk, ifi, 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; \ + 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)) + +#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/src/resolved-dns-cache.c b/src/resolve/src/resolved-dns-cache.c new file mode 100644 index 0000000000..9bcc71724e --- /dev/null +++ b/src/resolve/src/resolved-dns-cache.c @@ -0,0 +1,1088 @@ +/*** + 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 . +***/ + +#include "alloc-util.h" +#include "dns-domain.h" +#include "resolved-dns-answer.h" +#include "resolved-dns-cache.h" +#include "resolved-dns-packet.h" +#include "string-util.h" + +/* 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 2h in our cache */ +#define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR) + +typedef enum DnsCacheItemType DnsCacheItemType; +typedef struct DnsCacheItem DnsCacheItem; + +enum DnsCacheItemType { + DNS_CACHE_POSITIVE, + DNS_CACHE_NODATA, + DNS_CACHE_NXDOMAIN, +}; + +struct DnsCacheItem { + DnsCacheItemType type; + DnsResourceKey *key; + DnsResourceRecord *rr; + + usec_t until; + 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); +}; + +static void dns_cache_item_free(DnsCacheItem *i) { + if (!i) + return; + + dns_resource_record_unref(i->rr); + dns_resource_key_unref(i->key); + free(i); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free); + +static void dns_cache_item_unlink_and_free(DnsCache *c, DnsCacheItem *i) { + DnsCacheItem *first; + + assert(c); + + if (!i) + return; + + first = hashmap_get(c->by_key, i->key); + LIST_REMOVE(by_key, first, i); + + if (first) + assert_se(hashmap_replace(c->by_key, first->key, first) >= 0); + else + hashmap_remove(c->by_key, i->key); + + prioq_remove(c->by_expiry, i, &i->prioq_idx); + + dns_cache_item_free(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); + + first = hashmap_remove(c->by_key, key); + if (!first) + return false; + + LIST_FOREACH_SAFE(by_key, i, n, first) { + prioq_remove(c->by_expiry, i, &i->prioq_idx); + dns_cache_item_free(i); + } + + return true; +} + +void dns_cache_flush(DnsCache *c) { + DnsResourceKey *key; + + assert(c); + + while ((key = hashmap_first_key(c->by_key))) + dns_cache_remove_by_key(c, key); + + 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) { + assert(c); + + if (add <= 0) + return; + + /* Makes space for n new entries. Note that we actually allow + * the cache to grow beyond CACHE_MAX, but only when we shall + * add more RRs to the cache than CACHE_MAX at once. In that + * case the cache will be emptied completely otherwise. */ + + for (;;) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + DnsCacheItem *i; + + if (prioq_size(c->by_expiry) <= 0) + break; + + if (prioq_size(c->by_expiry) + add < CACHE_MAX) + break; + + i = prioq_peek(c->by_expiry); + assert(i); + + /* 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); + } +} + +void dns_cache_prune(DnsCache *c) { + usec_t t = 0; + + assert(c); + + /* Remove all entries that are past their TTL */ + + for (;;) { + DnsCacheItem *i; + + i = prioq_peek(c->by_expiry); + if (!i) + break; + + if (t <= 0) + t = now(clock_boottime_or_monotonic()); + + if (i->until > t) + break; + + /* 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); + } + } +} + +static int dns_cache_item_prioq_compare_func(const void *a, const void *b) { + const DnsCacheItem *x = a, *y = b; + + if (x->until < y->until) + return -1; + if (x->until > y->until) + return 1; + return 0; +} + +static int dns_cache_init(DnsCache *c) { + int r; + + assert(c); + + r = prioq_ensure_allocated(&c->by_expiry, dns_cache_item_prioq_compare_func); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&c->by_key, &dns_resource_key_hash_ops); + if (r < 0) + return r; + + return r; +} + +static int dns_cache_link_item(DnsCache *c, DnsCacheItem *i) { + DnsCacheItem *first; + int r; + + assert(c); + assert(i); + + r = prioq_put(c->by_expiry, i, &i->prioq_idx); + if (r < 0) + return r; + + 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 { + r = hashmap_put(c->by_key, i->key, i); + if (r < 0) { + prioq_remove(c->by_expiry, i, &i->prioq_idx); + return r; + } + } + + return 0; +} + +static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) { + DnsCacheItem *i; + + assert(c); + assert(rr); + + LIST_FOREACH(by_key, i, hashmap_get(c->by_key, rr->key)) + if (i->rr && dns_resource_record_equal(i->rr, rr) > 0) + return i; + + return NULL; +} + +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; + + if (!i->by_key_prev) + /* We are the first item in the list, we need to + * update the key used in the hashmap */ + + assert_se(hashmap_replace(c->by_key, rr->key, i) >= 0); + + dns_resource_record_ref(rr); + dns_resource_record_unref(i->rr); + i->rr = rr; + + 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->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); +} + +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) { + + _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; + _cleanup_free_ char *key_str = NULL; + DnsCacheItem *existing; + int r, k; + + assert(c); + assert(rr); + assert(owner_address); + + /* 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_by_rr(c, rr); + + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(rr->key, &key_str); + if (r < 0) + return r; + + if (k > 0) + log_debug("Removed zero TTL entry from cache: %s", key_str); + else + log_debug("Not caching zero TTL cache entry: %s", key_str); + } + + return 0; + } + + /* 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, + shared_owner, + timestamp, + ifindex, + owner_family, + owner_address); + return 0; + } + + /* Otherwise, add the new RR */ + r = dns_cache_init(c); + if (r < 0) + return r; + + dns_cache_make_space(c, 1); + + i = new0(DnsCacheItem, 1); + if (!i) + return -ENOMEM; + + i->type = DNS_CACHE_POSITIVE; + i->key = dns_resource_key_ref(rr->key); + i->rr = dns_resource_record_ref(rr); + i->until = calculate_until(rr, (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->prioq_idx = PRIOQ_IDX_NULL; + + r = dns_cache_link_item(c, i); + if (r < 0) + return r; + + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(i->key, &key_str); + if (r < 0) + return r; + + log_debug("Added positive cache entry for %s", key_str); + } + + i = NULL; + return 0; +} + +static int dns_cache_put_negative( + DnsCache *c, + DnsResourceKey *key, + int rcode, + bool authenticated, + uint32_t nsec_ttl, + usec_t timestamp, + DnsResourceRecord *soa, + int owner_family, + const union in_addr_union *owner_address) { + + _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; + _cleanup_free_ char *key_str = NULL; + int r; + + assert(c); + assert(key); + assert(soa); + assert(owner_address); + + /* 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 (dns_type_is_pseudo(key->type)) + return 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/NSEC/NSEC3 TTL: %s", key_str); + } + + return 0; + } + + if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) + return 0; + + r = dns_cache_init(c); + if (r < 0) + return r; + + dns_cache_make_space(c, 1); + + i = new0(DnsCacheItem, 1); + if (!i) + return -ENOMEM; + + i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN; + i->until = calculate_until(soa, nsec_ttl, timestamp, true); + i->authenticated = authenticated; + i->owner_family = owner_family; + i->owner_address = *owner_address; + 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 + * a pseudo type for this purpose here. */ + 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); + + r = dns_cache_link_item(c, i); + if (r < 0) + return r; + + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(i->key, &key_str); + if (r < 0) + return r; + + log_debug("Added %s cache entry for %s", i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", key_str); + } + + i = NULL; + 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, + bool authenticated, + uint32_t nsec_ttl, + usec_t timestamp, + int owner_family, + const union in_addr_union *owner_address) { + + DnsResourceRecord *soa = NULL, *rr; + DnsAnswerFlags flags; + unsigned cache_keys; + int r, ifindex; + + assert(c); + assert(owner_address); + + dns_cache_remove_previous(c, key, answer); + + if (dns_answer_size(answer) <= 0) { + if (log_get_max_level() >= LOG_DEBUG) { + _cleanup_free_ char *key_str = NULL; + + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; + + log_debug("Not caching negative entry without a SOA record: %s", key_str); + } + + return 0; + } + + /* 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 = dns_answer_size(answer); + if (key) + cache_keys ++; + + /* Make some space for our new entries */ + dns_cache_make_space(c, cache_keys); + + if (timestamp <= 0) + timestamp = now(clock_boottime_or_monotonic()); + + /* Second, add in positive entries for all contained RRs */ + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { + if ((flags & DNS_ANSWER_CACHEABLE) == 0) + continue; + + 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) /* 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, NULL); + if (r < 0) + goto fail; + if (r > 0) + return 0; + + /* 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, NULL); + if (r < 0) + goto fail; + if (r > 0) + return 0; + + /* 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, &flags); + if (r < 0) + goto fail; + if (r == 0) + return 0; + + /* 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; + + return 0; + +fail: + /* Adding all RRs failed. Let's clean up what we already + * added, just in case */ + + if (key) + dns_cache_remove_by_key(c, key); + + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + if ((flags & DNS_ANSWER_CACHEABLE) == 0) + continue; + + dns_cache_remove_by_key(c, rr->key); + } + + return r; +} + +static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, DnsResourceKey *k) { + DnsCacheItem *i; + const char *n; + int r; + + assert(c); + assert(k); + + /* If we hit some OOM error, or suchlike, we don't care too + * much, after all this is just a cache */ + + i = hashmap_get(c->by_key, k); + if (i) + return i; + + n = DNS_RESOURCE_KEY_NAME(k); + + /* Check if we have an NXDOMAIN cache item for the name, notice that we use + * the pseudo-type ANY for NXDOMAIN cache items. */ + i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_ANY, n)); + if (i && i->type == DNS_CACHE_NXDOMAIN) + return i; + + 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) + return i; + + /* OK, let's look for cached DNAME records. */ + for (;;) { + if (isempty(n)) + return NULL; + + i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n)); + if (i) + return i; + + /* Jump one label ahead */ + r = dns_name_parent(&n); + if (r <= 0) + return NULL; + } + } + + 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) + return i; + } + + return NULL; +} + +int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret, bool *authenticated) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + unsigned n = 0; + int r; + bool nxdomain = false; + _cleanup_free_ char *key_str = NULL; + DnsCacheItem *j, *first, *nsec = NULL; + bool have_authenticated = false, have_non_authenticated = false; + + assert(c); + assert(key); + assert(rcode); + assert(ret); + assert(authenticated); + + if (key->type == DNS_TYPE_ANY || + key->class == DNS_CLASS_ANY) { + + /* If we have ANY lookups we don't use the cache, so + * that the caller refreshes via the network. */ + + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; + + log_debug("Ignoring cache for ANY lookup: %s", key_str); + } + + c->n_miss++; + + *ret = NULL; + *rcode = DNS_RCODE_SUCCESS; + return 0; + } + + first = dns_cache_get_by_key_follow_cname_dname_nsec(c, key); + if (!first) { + /* If one question cannot be answered we need to refresh */ + + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; + + log_debug("Cache miss for %s", key_str); + } + + c->n_miss++; + + *ret = NULL; + *rcode = DNS_RCODE_SUCCESS; + return 0; + } + + LIST_FOREACH(by_key, j, first) { + if (j->rr) { + if (j->rr->key->type == DNS_TYPE_NSEC) + nsec = j; + + n++; + } else if (j->type == DNS_CACHE_NXDOMAIN) + nxdomain = true; + + if (j->authenticated) + have_authenticated = true; + else + have_non_authenticated = true; + } + + 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) + return r; + + log_debug("NSEC NODATA cache hit for %s", key_str); + } + + /* We only found an NSEC record that matches our name. + * If it says the type doesn't exist report + * NODATA. Otherwise report a cache miss. */ + + *ret = NULL; + *rcode = DNS_RCODE_SUCCESS; + *authenticated = nsec->authenticated; + + 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) { + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; + + log_debug("%s cache hit for %s", + n > 0 ? "Positive" : + nxdomain ? "NXDOMAIN" : "NODATA", + key_str); + } + + if (n <= 0) { + c->n_hit++; + + *ret = NULL; + *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS; + *authenticated = have_authenticated && !have_non_authenticated; + return 1; + } + + answer = dns_answer_new(n); + if (!answer) + return -ENOMEM; + + LIST_FOREACH(by_key, j, first) { + if (!j->rr) + continue; + + 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; + answer = NULL; + + return n; +} + +int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) { + DnsCacheItem *i, *first; + bool same_owner = true; + + assert(cache); + assert(rr); + + dns_cache_prune(cache); + + /* See if there's a cache entry for the same key. If there + * isn't there's no conflict */ + first = hashmap_get(cache->by_key, rr->key); + if (!first) + return 0; + + /* See if the RR key is owned by the same owner, if so, there + * isn't a conflict either */ + LIST_FOREACH(by_key, i, first) { + if (i->owner_family != owner_family || + !in_addr_equal(owner_family, &i->owner_address, owner_address)) { + same_owner = false; + break; + } + } + if (same_owner) + return 0; + + /* See if there's the exact same RR in the cache. If yes, then + * there's no conflict. */ + if (dns_cache_get(cache, rr)) + return 0; + + /* There's a conflict */ + return 1; +} + +int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p) { + unsigned ancount = 0; + Iterator iterator; + DnsCacheItem *i; + int r; + + assert(cache); + assert(p); + + HASHMAP_FOREACH(i, cache->by_key, iterator) { + DnsCacheItem *j; + + LIST_FOREACH(by_key, j, i) { + if (!j->rr) + continue; + + if (!j->shared_owner) + continue; + + r = dns_packet_append_rr(p, j->rr, NULL, NULL); + if (r == -EMSGSIZE && p->protocol == DNS_PROTOCOL_MDNS) { + /* For mDNS, if we're unable to stuff all known answers into the given packet, + * allocate a new one, push the RR into that one and link it to the current one. + */ + + DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); + ancount = 0; + + r = dns_packet_new_query(&p->more, p->protocol, 0, true); + if (r < 0) + return r; + + /* continue with new packet */ + p = p->more; + r = dns_packet_append_rr(p, j->rr, NULL, NULL); + } + + if (r < 0) + return r; + + ancount ++; + } + } + + DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); + + return 0; +} + +void dns_cache_dump(DnsCache *cache, FILE *f) { + Iterator iterator; + DnsCacheItem *i; + int r; + + if (!cache) + return; + + if (!f) + f = stdout; + + HASHMAP_FOREACH(i, cache->by_key, iterator) { + DnsCacheItem *j; + + LIST_FOREACH(by_key, j, i) { + + fputc('\t', f); + + if (j->rr) { + const char *t; + t = dns_resource_record_to_string(j->rr); + if (!t) { + log_oom(); + continue; + } + + fputs(t, f); + fputc('\n', f); + } else { + _cleanup_free_ char *z = NULL; + r = dns_resource_key_to_string(j->key, &z); + if (r < 0) { + log_oom(); + continue; + } + + fputs(z, f); + fputs(" -- ", f); + fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f); + fputc('\n', f); + } + } + } +} + +bool dns_cache_is_empty(DnsCache *cache) { + if (!cache) + return true; + + 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/src/resolved-dns-cache.h b/src/resolve/src/resolved-dns-cache.h new file mode 100644 index 0000000000..2293718e86 --- /dev/null +++ b/src/resolve/src/resolved-dns-cache.h @@ -0,0 +1,52 @@ +#pragma once + +/*** + 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 . +***/ + +#include "hashmap.h" +#include "list.h" +#include "prioq.h" +#include "time-util.h" + +typedef struct DnsCache { + Hashmap *by_key; + Prioq *by_expiry; + unsigned n_hit; + unsigned n_miss; +} DnsCache; + +#include "resolved-dns-answer.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-question.h" +#include "resolved-dns-rr.h" + +void dns_cache_flush(DnsCache *c); +void dns_cache_prune(DnsCache *c); + +int dns_cache_put(DnsCache *c, DnsResourceKey *key, int rcode, DnsAnswer *answer, bool authenticated, 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); + +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/src/resolved-dns-dnssec.c b/src/resolve/src/resolved-dns-dnssec.c new file mode 100644 index 0000000000..7123d2d3a8 --- /dev/null +++ b/src/resolve/src/resolved-dns-dnssec.c @@ -0,0 +1,2211 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#ifdef HAVE_GCRYPT +#include +#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" + +#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: + * + * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone + * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree + * DS RRs are protected like normal RRs + * + * Example chain: + * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS + */ + +uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) { + const uint8_t *p; + uint32_t sum, f; + size_t i; + + /* The algorithm from RFC 4034, Appendix B. */ + + assert(dnskey); + assert(dnskey->key->type == DNS_TYPE_DNSKEY); + + 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; + + for (i = 0; i < dnskey->dnskey.key_size; i++) + sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i]; + + sum += (sum >> 16) & UINT32_C(0xFFFF); + + 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; + int r; + + /* Let's order the RRs according to RFC 4034, Section 6.3 */ + + assert(x); + assert(*x); + assert((*x)->wire_format); + assert(y); + assert(*y); + assert((*y)->wire_format); + + m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y)); + + r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m); + if (r != 0) + return r; + + if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) + return -1; + else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) + return 1; + + return 0; +} + +static int dnssec_rsa_verify_raw( + const char *hash_algorithm, + const void *signature, size_t signature_size, + const void *data, size_t data_size, + const void *exponent, size_t exponent_size, + const void *modulus, size_t modulus_size) { + + gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; + gcry_mpi_t n = NULL, e = NULL, s = NULL; + gcry_error_t ge; + int r; + + assert(hash_algorithm); + + ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&signature_sexp, + NULL, + "(sig-val (rsa (s %m)))", + s); + + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&data_sexp, + NULL, + "(data (flags pkcs1) (hash %s %b))", + hash_algorithm, + (int) data_size, + data); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&public_key_sexp, + NULL, + "(public-key (rsa (n %m) (e %m)))", + n, + e); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp); + if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE) + r = 0; + else if (ge != 0) { + log_debug("RSA signature check failed: %s", gpg_strerror(ge)); + r = -EIO; + } else + r = 1; + +finish: + if (e) + gcry_mpi_release(e); + if (n) + gcry_mpi_release(n); + if (s) + gcry_mpi_release(s); + + 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 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)); +} + +static void md_add_uint16(gcry_md_hd_t md, uint16_t v) { + v = htobe16(v); + gcry_md_write(md, &v, sizeof(v)); +} + +static void md_add_uint32(gcry_md_hd_t md, uint32_t v) { + v = htobe32(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; + + assert(rrsig); + assert(rrsig->key->type == DNS_TYPE_RRSIG); + + if (realtime == USEC_INFINITY) + realtime = now(CLOCK_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 true; + + /* Permit a certain amount of clock skew of 10% of the valid + * time range. This takes inspiration from unbound's + * resolver. */ + skew = (expiration - inception) / 10; + if (skew > SKEW_MAX) + skew = SKEW_MAX; + + if (inception < skew) + inception = 0; + else + inception -= skew; + + if (expiration + skew < expiration) + expiration = USEC_INFINITY; + else + expiration += skew; + + 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, + const DnsResourceKey *key, + DnsResourceRecord *rrsig, + DnsResourceRecord *dnskey, + usec_t realtime, + DnssecResult *result) { + + uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX]; + DnsResourceRecord **list, *rr; + const char *source, *name; + gcry_md_hd_t md = NULL; + int r, md_algorithm; + size_t k, n = 0; + size_t hash_size; + void *hash; + bool wildcard; + + assert(key); + assert(rrsig); + assert(dnskey); + assert(result); + assert(rrsig->key->type == DNS_TYPE_RRSIG); + assert(dnskey->key->type == DNS_TYPE_DNSKEY); + + /* Verifies the the RRSet matching the specified "key" in "a", + * using the signature "rrsig" and the key "dnskey". It's + * assumed the RRSIG and DNSKEY match. */ + + 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; + + r = dnssec_rrsig_prepare(rrsig); + if (r == -EINVAL) { + *result = DNSSEC_INVALID; + return r; + } + if (r < 0) + return r; + + r = dnssec_rrsig_expired(rrsig, realtime); + if (r < 0) + return r; + if (r > 0) { + *result = DNSSEC_SIGNATURE_EXPIRED; + 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 *, dns_answer_size(a)); + + DNS_ANSWER_FOREACH(rr, a) { + r = dns_resource_key_equal(key, rr->key); + if (r < 0) + return r; + if (r == 0) + continue; + + /* We need the wire format for ordering, and digest calculation */ + r = dns_resource_record_to_wire_format(rr, true); + if (r < 0) + return r; + + list[n++] = rr; + + if (n > VERIFY_RRS_MAX) + return -E2BIG; + } + + if (n <= 0) + return -ENODATA; + + /* Bring the RRs into canonical order */ + qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare); + + /* OK, the RRs are now in canonical order. Let's calculate the digest */ + initialize_libgcrypt(); + + hash_size = gcry_md_get_algo_dlen(md_algorithm); + assert(hash_size > 0); + + gcry_md_open(&md, md_algorithm, 0); + if (!md) + return -EIO; + + md_add_uint16(md, rrsig->rrsig.type_covered); + md_add_uint8(md, rrsig->rrsig.algorithm); + md_add_uint8(md, rrsig->rrsig.labels); + md_add_uint32(md, rrsig->rrsig.original_ttl); + md_add_uint32(md, rrsig->rrsig.expiration); + md_add_uint32(md, rrsig->rrsig.inception); + md_add_uint16(md, rrsig->rrsig.key_tag); + + r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true); + if (r < 0) + 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]; + + /* 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); + + l = DNS_RESOURCE_RECORD_RDATA_SIZE(rr); + assert(l <= 0xFFFF); + + md_add_uint16(md, (uint16_t) l); + gcry_md_write(md, DNS_RESOURCE_RECORD_RDATA(rr), l); + } + + hash = gcry_md_read(md, 0); + if (!hash) { + r = -EIO; + goto finish; + } + + switch (rrsig->rrsig.algorithm) { + + 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; + + 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; + } + + if (r < 0) + goto finish; + + /* 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: + gcry_md_close(md); + return r; +} + +int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) { + + assert(rrsig); + assert(dnskey); + + /* Checks if the specified DNSKEY RR matches the key used for + * the signature in the specified RRSIG RR */ + + if (rrsig->key->type != DNS_TYPE_RRSIG) + return -EINVAL; + + if (dnskey->key->type != DNS_TYPE_DNSKEY) + return 0; + if (dnskey->key->class != rrsig->key->class) + 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, false) != rrsig->rrsig.key_tag) + return 0; + + return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer); +} + +int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) { + assert(key); + assert(rrsig); + + /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */ + + if (rrsig->key->type != DNS_TYPE_RRSIG) + return 0; + if (rrsig->key->class != key->class) + return 0; + if (rrsig->rrsig.type_covered != key->type) + return 0; + + return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key)); +} + +int dnssec_verify_rrset_search( + DnsAnswer *a, + const DnsResourceKey *key, + DnsAnswer *validated_dnskeys, + usec_t realtime, + DnssecResult *result, + DnsResourceRecord **ret_rrsig) { + + 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 DNSKEYs in "validated_dnskeys" */ + + if (!a || a->n_rrs <= 0) + return -ENODATA; + + /* 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; + if (r == 0) + continue; + + found_rrsig = true; + + /* Look for a matching key */ + DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) { + DnssecResult one_result; + + 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; + + /* Take the time here, if it isn't set yet, so + * that we do all validations with the same + * time. */ + if (realtime == USEC_INFINITY) + realtime = now(CLOCK_REALTIME); + + /* Yay, we found a matching RRSIG with a matching + * DNSKEY, awesome. Now let's verify all entries of + * the RRSet against the RRSIG and DNSKEY + * combination. */ + + r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result); + if (r < 0) + return r; + + 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; + + 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_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_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) { + DnsResourceRecord *rr; + int r; + + /* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */ + + DNS_ANSWER_FOREACH(rr, a) { + r = dnssec_key_match_rrsig(key, rr); + if (r < 0) + return r; + if (r > 0) + return 1; + } + + return 0; +} + +static int digest_to_gcrypt_md(uint8_t algorithm) { + + /* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */ + + switch (algorithm) { + + case DNSSEC_DIGEST_SHA1: + return GCRY_MD_SHA1; + + case DNSSEC_DIGEST_SHA256: + return GCRY_MD_SHA256; + + case DNSSEC_DIGEST_SHA384: + return GCRY_MD_SHA384; + + default: + return -EOPNOTSUPP; + } +} + +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; + + assert(dnskey); + assert(ds); + + /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */ + + if (dnskey->key->type != DNS_TYPE_DNSKEY) + return -EINVAL; + if (ds->key->type != DNS_TYPE_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, mask_revoke) != ds->ds.key_tag) + return 0; + + initialize_libgcrypt(); + + md_algorithm = digest_to_gcrypt_md(ds->ds.digest_type); + if (md_algorithm < 0) + return md_algorithm; + + hash_size = gcry_md_get_algo_dlen(md_algorithm); + assert(hash_size > 0); + + if (ds->ds.digest_size != hash_size) + return 0; + + 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; + + gcry_md_write(md, owner_name, r); + 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); + + result = gcry_md_read(md, 0); + if (!result) { + r = -EIO; + goto finish; + } + + r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0; + +finish: + gcry_md_close(md); + return r; +} + +int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { + DnsResourceRecord *ds; + DnsAnswerFlags flags; + int r; + + assert(dnskey); + + if (dnskey->key->type != DNS_TYPE_DNSKEY) + return 0; + + 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_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) + return 1; + } + + return 0; +} + +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_SIGNATURE_EXPIRED] = "signature-expired", + [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm", + [DNSSEC_NO_SIGNATURE] = "no-signature", + [DNSSEC_MISSING_KEY] = "missing-key", + [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/src/resolved-dns-dnssec.h b/src/resolve/src/resolved-dns-dnssec.h new file mode 100644 index 0000000000..77bd4d71bf --- /dev/null +++ b/src/resolve/src/resolved-dns-dnssec.h @@ -0,0 +1,102 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +typedef enum DnssecResult DnssecResult; +typedef enum DnssecVerdict DnssecVerdict; + +#include "dns-domain.h" +#include "resolved-dns-answer.h" +#include "resolved-dns-rr.h" + +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_SIGNATURE_EXPIRED, + DNSSEC_UNSUPPORTED_ALGORITHM, + + /* These two are added by dnssec_verify_rrset_search() */ + DNSSEC_NO_SIGNATURE, + DNSSEC_MISSING_KEY, + + /* 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) + +/* 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_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke); +int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds); + +int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key); + +uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke); + +int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max); + +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/src/resolved-dns-packet.c b/src/resolve/src/resolved-dns-packet.c new file mode 100644 index 0000000000..c940dd8929 --- /dev/null +++ b/src/resolve/src/resolved-dns-packet.c @@ -0,0 +1,2295 @@ +/*** + 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 . + ***/ + +#include "alloc-util.h" +#include "dns-domain.h" +#include "resolved-dns-packet.h" +#include "string-table.h" +#include "strv.h" +#include "unaligned.h" +#include "utf8.h" +#include "util.h" + +#define EDNS0_OPT_DO (1<<15) + +int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { + DnsPacket *p; + size_t a; + + assert(ret); + + if (mtu <= UDP_PACKET_HEADER_SIZE) + a = DNS_PACKET_SIZE_START; + else + a = mtu - UDP_PACKET_HEADER_SIZE; + + if (a < DNS_PACKET_HEADER_SIZE) + a = DNS_PACKET_HEADER_SIZE; + + /* round up to next page size */ + a = PAGE_ALIGN(ALIGN(sizeof(DnsPacket)) + a) - ALIGN(sizeof(DnsPacket)); + + /* make sure we never allocate more than useful */ + if (a > DNS_PACKET_SIZE_MAX) + a = DNS_PACKET_SIZE_MAX; + + p = malloc0(ALIGN(sizeof(DnsPacket)) + a); + if (!p) + return -ENOMEM; + + 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; + + return 0; +} + +void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated) { + + DnsPacketHeader *h; + + assert(p); + + h = DNS_PACKET_HEADER(p); + + switch(p->protocol) { + case DNS_PROTOCOL_LLMNR: + assert(!truncated); + + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, + 0 /* opcode */, + 0 /* c */, + 0 /* tc */, + 0 /* t */, + 0 /* ra */, + 0 /* ad */, + 0 /* cd */, + 0 /* rcode */)); + break; + + case DNS_PROTOCOL_MDNS: + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, + 0 /* opcode */, + 0 /* aa */, + truncated /* tc */, + 0 /* rd (ask for recursion) */, + 0 /* ra */, + 0 /* ad */, + 0 /* cd */, + 0 /* rcode */)); + break; + + default: + assert(!truncated); + + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, + 0 /* opcode */, + 0 /* aa */, + 0 /* tc */, + 1 /* rd (ask for recursion) */, + 0 /* ra */, + 0 /* ad */, + dnssec_checking_disabled /* cd */, + 0 /* rcode */)); + } +} + +int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled) { + DnsPacket *p; + int r; + + assert(ret); + + r = dns_packet_new(&p, protocol, mtu); + if (r < 0) + return r; + + /* Always set the TC bit to 0 initially. + * If there are multiple packets later, we'll update the bit shortly before sending. + */ + dns_packet_set_flags(p, dnssec_checking_disabled, false); + + *ret = p; + return 0; +} + +DnsPacket *dns_packet_ref(DnsPacket *p) { + + if (!p) + return NULL; + + assert(!p->on_stack); + + assert(p->n_ref > 0); + p->n_ref++; + return p; +} + +static void dns_packet_free(DnsPacket *p) { + char *s; + + assert(p); + + dns_question_unref(p->question); + dns_answer_unref(p->answer); + dns_resource_record_unref(p->opt); + + while ((s = hashmap_steal_first_key(p->names))) + free(s); + hashmap_free(p->names); + + free(p->_data); + + if (!p->on_stack) + free(p); +} + +DnsPacket *dns_packet_unref(DnsPacket *p) { + if (!p) + return NULL; + + assert(p->n_ref > 0); + + dns_packet_unref(p->more); + + if (p->n_ref == 1) + dns_packet_free(p); + else + p->n_ref--; + + return NULL; +} + +int dns_packet_validate(DnsPacket *p) { + assert(p); + + if (p->size < DNS_PACKET_HEADER_SIZE) + return -EBADMSG; + + if (p->size > DNS_PACKET_SIZE_MAX) + return -EBADMSG; + + return 1; +} + +int dns_packet_validate_reply(DnsPacket *p) { + int r; + + assert(p); + + r = dns_packet_validate(p); + if (r < 0) + return r; + + if (DNS_PACKET_QR(p) != 1) + return 0; + + if (DNS_PACKET_OPCODE(p) != 0) + return -EBADMSG; + + switch (p->protocol) { + + case DNS_PROTOCOL_LLMNR: + /* RFC 4795, Section 2.1.1. says to discard all replies with QDCOUNT != 1 */ + if (DNS_PACKET_QDCOUNT(p) != 1) + return -EBADMSG; + + break; + + case DNS_PROTOCOL_MDNS: + /* RFC 6762, Section 18 */ + if (DNS_PACKET_RCODE(p) != 0) + return -EBADMSG; + + break; + + default: + break; + } + + return 1; +} + +int dns_packet_validate_query(DnsPacket *p) { + int r; + + assert(p); + + r = dns_packet_validate(p); + if (r < 0) + return r; + + if (DNS_PACKET_QR(p) != 0) + return 0; + + if (DNS_PACKET_OPCODE(p) != 0) + return -EBADMSG; + + if (DNS_PACKET_TC(p)) + return -EBADMSG; + + switch (p->protocol) { + + case DNS_PROTOCOL_LLMNR: + /* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */ + if (DNS_PACKET_QDCOUNT(p) != 1) + return -EBADMSG; + + /* RFC 4795, Section 2.1.1. says to discard all queries with ANCOUNT != 0 */ + if (DNS_PACKET_ANCOUNT(p) > 0) + return -EBADMSG; + + /* RFC 4795, Section 2.1.1. says to discard all queries with NSCOUNT != 0 */ + if (DNS_PACKET_NSCOUNT(p) > 0) + return -EBADMSG; + + break; + + case DNS_PROTOCOL_MDNS: + /* RFC 6762, Section 18 */ + if (DNS_PACKET_AA(p) != 0 || + DNS_PACKET_RD(p) != 0 || + DNS_PACKET_RA(p) != 0 || + DNS_PACKET_AD(p) != 0 || + DNS_PACKET_CD(p) != 0 || + DNS_PACKET_RCODE(p) != 0) + return -EBADMSG; + + break; + + default: + break; + } + + return 1; +} + +static int dns_packet_extend(DnsPacket *p, size_t add, void **ret, size_t *start) { + assert(p); + + if (p->size + add > p->allocated) { + size_t a; + + a = PAGE_ALIGN((p->size + add) * 2); + if (a > DNS_PACKET_SIZE_MAX) + a = DNS_PACKET_SIZE_MAX; + + if (p->size + add > a) + return -EMSGSIZE; + + if (p->_data) { + void *d; + + d = realloc(p->_data, a); + if (!d) + return -ENOMEM; + + p->_data = d; + } else { + p->_data = malloc(a); + if (!p->_data) + return -ENOMEM; + + memcpy(p->_data, (uint8_t*) p + ALIGN(sizeof(DnsPacket)), p->size); + memzero((uint8_t*) p->_data + p->size, a - p->size); + } + + p->allocated = a; + } + + if (start) + *start = p->size; + + if (ret) + *ret = (uint8_t*) DNS_PACKET_DATA(p) + p->size; + + p->size += add; + return 0; +} + +void dns_packet_truncate(DnsPacket *p, size_t sz) { + Iterator i; + char *s; + void *n; + + assert(p); + + if (p->size <= sz) + return; + + HASHMAP_FOREACH_KEY(n, s, p->names, i) { + + if (PTR_TO_SIZE(n) < sz) + continue; + + hashmap_remove(p->names, s); + free(s); + } + + p->size = sz; +} + +int dns_packet_append_blob(DnsPacket *p, const void *d, size_t l, size_t *start) { + void *q; + int r; + + assert(p); + + r = dns_packet_extend(p, l, &q, start); + if (r < 0) + return r; + + memcpy(q, d, l); + return 0; +} + +int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start) { + void *d; + int r; + + assert(p); + + r = dns_packet_extend(p, sizeof(uint8_t), &d, start); + if (r < 0) + return r; + + ((uint8_t*) d)[0] = v; + + return 0; +} + +int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start) { + void *d; + int r; + + assert(p); + + r = dns_packet_extend(p, sizeof(uint16_t), &d, start); + if (r < 0) + return r; + + unaligned_write_be16(d, v); + + return 0; +} + +int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start) { + void *d; + int r; + + assert(p); + + r = dns_packet_extend(p, sizeof(uint32_t), &d, start); + if (r < 0) + return r; + + unaligned_write_be32(d, v); + + return 0; +} + +int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start) { + assert(p); + assert(s); + + return dns_packet_append_raw_string(p, s, strlen(s), start); +} + +int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start) { + void *d; + int r; + + assert(p); + assert(s || size == 0); + + if (size > 255) + return -E2BIG; + + r = dns_packet_extend(p, 1 + size, &d, start); + if (r < 0) + return r; + + ((uint8_t*) d)[0] = (uint8_t) size; + + if (size > 0) + memcpy(((uint8_t*) d) + 1, s, size); + + return 0; +} + +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); + + if (l > DNS_LABEL_MAX) + return -E2BIG; + + r = dns_packet_extend(p, 1 + l, (void**) &w, start); + if (r < 0) + return r; + + *(w++) = (uint8_t) l; + + 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++) + 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 + * matters and needs to be retained. */ + memcpy(w, d, l); + + return 0; +} + +int dns_packet_append_name( + DnsPacket *p, + const char *name, + bool allow_compression, + bool canonical_candidate, + size_t *start) { + + size_t saved_size; + int r; + + assert(p); + assert(name); + + if (p->refuse_compression) + allow_compression = false; + + saved_size = p->size; + + while (!dns_name_is_root(name)) { + const char *z = name; + char label[DNS_LABEL_MAX]; + size_t n = 0; + + if (allow_compression) + n = PTR_TO_SIZE(hashmap_get(p->names, name)); + if (n > 0) { + assert(n < p->size); + + if (n < 0x4000) { + r = dns_packet_append_uint16(p, 0xC000 | n, NULL); + if (r < 0) + goto fail; + + goto done; + } + } + + r = dns_label_unescape(&name, label, sizeof(label)); + if (r < 0) + goto fail; + + 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; + + r = hashmap_put(p->names, s, SIZE_TO_PTR(n)); + if (r < 0) + goto fail; + + s = NULL; + } + } + + r = dns_packet_append_uint8(p, 0, NULL); + if (r < 0) + return r; + +done: + if (start) + *start = saved_size; + + return 0; + +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) { + size_t saved_size; + int r; + + assert(p); + assert(k); + + saved_size = p->size; + + r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, true, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, k->type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, k->class, NULL); + if (r < 0) + goto fail; + + if (start) + *start = saved_size; + + return 0; + +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +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; + + assert(p); + assert(types); + assert(length > 0); + + saved_size = p->size; + + r = dns_packet_append_uint8(p, window, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, length, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, types, length, NULL); + if (r < 0) + goto fail; + + if (start) + *start = saved_size; + + return 0; +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) { + Iterator i; + uint8_t window = 0; + uint8_t entry = 0; + uint8_t bitmaps[32] = {}; + unsigned n; + size_t saved_size; + int r; + + assert(p); + + saved_size = p->size; + + BITMAP_FOREACH(n, types, i) { + assert(n <= 0xffff); + + if ((n >> 8) != window && bitmaps[entry / 8] != 0) { + r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); + if (r < 0) + goto fail; + + zero(bitmaps); + } + + window = n >> 8; + entry = n & 255; + + bitmaps[entry / 8] |= 1 << (7 - (entry % 8)); + } + + 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; + + return 0; +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +/* Append the OPT pseudo-RR described in RFC6891 */ +int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start) { + size_t saved_size; + int r; + + assert(p); + /* 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 */ + r = dns_packet_append_uint8(p, 0, NULL); + if (r < 0) + return r; + + /* type */ + r = dns_packet_append_uint16(p, DNS_TYPE_OPT, NULL); + if (r < 0) + goto fail; + + /* maximum udp packet that can be received */ + r = dns_packet_append_uint16(p, max_udp_size, NULL); + if (r < 0) + goto fail; + + /* extended RCODE and VERSION */ + r = dns_packet_append_uint16(p, 0, NULL); + if (r < 0) + goto fail; + + /* flags: DNSSEC OK (DO), see RFC3225 */ + r = dns_packet_append_uint16(p, edns0_do ? EDNS0_OPT_DO : 0, NULL); + if (r < 0) + goto fail; + + /* RDLENGTH */ + + 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; + + return 0; + +fail: + dns_packet_truncate(p, saved_size); + 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; + + assert(p); + assert(rr); + + saved_size = p->size; + + r = dns_packet_append_key(p, rr->key, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->ttl, NULL); + if (r < 0) + goto fail; + + /* Initially we write 0 here */ + r = dns_packet_append_uint16(p, 0, &rdlength_offset); + if (r < 0) + goto fail; + + rds = p->size - saved_size; + + switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { + + case DNS_TYPE_SRV: + r = dns_packet_append_uint16(p, rr->srv.priority, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->srv.weight, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->srv.port, NULL); + if (r < 0) + goto fail; + + 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, false, NULL); + break; + + case DNS_TYPE_HINFO: + r = dns_packet_append_string(p, rr->hinfo.cpu, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_string(p, rr->hinfo.os, NULL); + break; + + case DNS_TYPE_SPF: /* exactly the same as TXT */ + case DNS_TYPE_TXT: + + if (!rr->txt.items) { + /* RFC 6763, section 6.1 suggests to generate + * single empty string for an empty array. */ + + r = dns_packet_append_raw_string(p, NULL, 0, NULL); + if (r < 0) + goto fail; + } else { + DnsTxtItem *i; + + LIST_FOREACH(items, i, rr->txt.items) { + r = dns_packet_append_raw_string(p, i->data, i->length, NULL); + if (r < 0) + goto fail; + } + } + + r = 0; + break; + + case DNS_TYPE_A: + r = dns_packet_append_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); + break; + + case DNS_TYPE_AAAA: + r = dns_packet_append_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL); + break; + + case DNS_TYPE_SOA: + 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, false, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.serial, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.refresh, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.retry, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.expire, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.minimum, NULL); + break; + + case DNS_TYPE_MX: + r = dns_packet_append_uint16(p, rr->mx.priority, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->mx.exchange, true, false, NULL); + break; + + case DNS_TYPE_LOC: + r = dns_packet_append_uint8(p, rr->loc.version, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->loc.size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->loc.horiz_pre, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->loc.vert_pre, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->loc.latitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->loc.longitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->loc.altitude, NULL); + break; + + case DNS_TYPE_DS: + r = dns_packet_append_uint16(p, rr->ds.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->ds.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->ds.digest_type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->ds.digest, rr->ds.digest_size, NULL); + break; + + case DNS_TYPE_SSHFP: + r = dns_packet_append_uint8(p, rr->sshfp.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->sshfp.fptype, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, NULL); + break; + + case DNS_TYPE_DNSKEY: + r = dns_packet_append_uint16(p, rr->dnskey.flags, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->dnskey.protocol, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->dnskey.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->dnskey.key, rr->dnskey.key_size, NULL); + break; + + case DNS_TYPE_RRSIG: + r = dns_packet_append_uint16(p, rr->rrsig.type_covered, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->rrsig.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->rrsig.labels, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->rrsig.original_ttl, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->rrsig.expiration, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->rrsig.inception, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->rrsig.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->rrsig.signer, false, true, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->rrsig.signature, rr->rrsig.signature_size, NULL); + break; + + case DNS_TYPE_NSEC: + r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, false, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_types(p, rr->nsec.types, NULL); + if (r < 0) + goto fail; + + break; + + case DNS_TYPE_NSEC3: + r = dns_packet_append_uint8(p, rr->nsec3.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->nsec3.flags, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->nsec3.iterations, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->nsec3.salt_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->nsec3.salt, rr->nsec3.salt_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->nsec3.next_hashed_name_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_types(p, rr->nsec3.types, NULL); + if (r < 0) + goto fail; + + 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.data_size, NULL); + break; + } + if (r < 0) + goto fail; + + /* Let's calculate the actual data size and update the field */ + rdlength = p->size - rdlength_offset - sizeof(uint16_t); + if (rdlength > 0xFFFF) { + r = -ENOSPC; + goto fail; + } + + end = p->size; + p->size = rdlength_offset; + r = dns_packet_append_uint16(p, rdlength, NULL); + if (r < 0) + goto fail; + p->size = end; + + if (start) + *start = saved_size; + + if (rdata_start) + *rdata_start = rds; + + return 0; + +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) { + assert(p); + + if (p->rindex + sz > p->size) + return -EMSGSIZE; + + if (ret) + *ret = (uint8_t*) DNS_PACKET_DATA(p) + p->rindex; + + if (start) + *start = p->rindex; + + p->rindex += sz; + return 0; +} + +void dns_packet_rewind(DnsPacket *p, size_t idx) { + assert(p); + assert(idx <= p->size); + assert(idx >= DNS_PACKET_HEADER_SIZE); + + p->rindex = idx; +} + +int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start) { + const void *q; + int r; + + assert(p); + assert(d); + + r = dns_packet_read(p, sz, &q, start); + if (r < 0) + return r; + + memcpy(d, q, sz); + return 0; +} + +static int dns_packet_read_memdup( + DnsPacket *p, size_t size, + void **ret, size_t *ret_size, + size_t *ret_start) { + + const void *src; + size_t start; + int r; + + assert(p); + assert(ret); + + r = dns_packet_read(p, size, &src, &start); + if (r < 0) + return r; + + if (size <= 0) + *ret = NULL; + else { + void *copy; + + copy = memdup(src, size); + if (!copy) + return -ENOMEM; + + *ret = copy; + } + + if (ret_size) + *ret_size = size; + if (ret_start) + *ret_start = start; + + return 0; +} + +int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start) { + const void *d; + int r; + + assert(p); + + r = dns_packet_read(p, sizeof(uint8_t), &d, start); + if (r < 0) + return r; + + *ret = ((uint8_t*) d)[0]; + return 0; +} + +int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start) { + const void *d; + int r; + + assert(p); + + r = dns_packet_read(p, sizeof(uint16_t), &d, start); + if (r < 0) + return r; + + *ret = unaligned_read_be16(d); + + return 0; +} + +int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start) { + const void *d; + int r; + + assert(p); + + r = dns_packet_read(p, sizeof(uint32_t), &d, start); + if (r < 0) + return r; + + *ret = unaligned_read_be32(d); + + return 0; +} + +int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start) { + size_t saved_rindex; + const void *d; + char *t; + uint8_t c; + int r; + + assert(p); + + saved_rindex = p->rindex; + + r = dns_packet_read_uint8(p, &c, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read(p, c, &d, NULL); + if (r < 0) + goto fail; + + if (memchr(d, 0, c)) { + r = -EBADMSG; + goto fail; + } + + t = strndup(d, c); + if (!t) { + r = -ENOMEM; + goto fail; + } + + if (!utf8_is_valid(t)) { + free(t); + r = -EBADMSG; + goto fail; + } + + *ret = t; + + if (start) + *start = saved_rindex; + + return 0; + +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start) { + size_t saved_rindex; + uint8_t c; + int r; + + assert(p); + + saved_rindex = p->rindex; + + r = dns_packet_read_uint8(p, &c, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read(p, c, ret, NULL); + if (r < 0) + goto fail; + + if (size) + *size = c; + if (start) + *start = saved_rindex; + + return 0; + +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +int dns_packet_read_name( + DnsPacket *p, + char **_ret, + bool allow_compression, + size_t *start) { + + size_t saved_rindex, after_rindex = 0, jump_barrier; + _cleanup_free_ char *ret = NULL; + size_t n = 0, allocated = 0; + bool first = true; + int r; + + assert(p); + assert(_ret); + + if (p->refuse_compression) + allow_compression = false; + + saved_rindex = p->rindex; + jump_barrier = p->rindex; + + for (;;) { + uint8_t c, d; + + r = dns_packet_read_uint8(p, &c, NULL); + if (r < 0) + goto fail; + + if (c == 0) + /* End of name */ + break; + else if (c <= 63) { + const char *label; + + /* Literal label */ + r = dns_packet_read(p, c, (const void**) &label, NULL); + if (r < 0) + goto fail; + + if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) { + r = -ENOMEM; + goto fail; + } + + if (first) + first = false; + else + ret[n++] = '.'; + + r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + goto fail; + + n += r; + continue; + } else if (allow_compression && (c & 0xc0) == 0xc0) { + uint16_t ptr; + + /* Pointer */ + r = dns_packet_read_uint8(p, &d, NULL); + if (r < 0) + goto fail; + + ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; + if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= jump_barrier) { + r = -EBADMSG; + goto fail; + } + + if (after_rindex == 0) + after_rindex = p->rindex; + + /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */ + jump_barrier = ptr; + p->rindex = ptr; + } else { + r = -EBADMSG; + goto fail; + } + } + + if (!GREEDY_REALLOC(ret, allocated, n + 1)) { + r = -ENOMEM; + goto fail; + } + + ret[n] = 0; + + if (after_rindex != 0) + p->rindex= after_rindex; + + *_ret = ret; + ret = NULL; + + if (start) + *start = saved_rindex; + + return 0; + +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *start) { + uint8_t window; + uint8_t length; + const uint8_t *bitmap; + uint8_t bit = 0; + unsigned i; + bool found = false; + size_t saved_rindex; + int r; + + assert(p); + assert(types); + + saved_rindex = p->rindex; + + r = bitmap_ensure_allocated(types); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &window, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &length, NULL); + if (r < 0) + goto fail; + + if (length == 0 || length > 32) + return -EBADMSG; + + r = dns_packet_read(p, length, (const void **)&bitmap, NULL); + if (r < 0) + goto fail; + + for (i = 0; i < length; i++) { + uint8_t bitmask = 1 << 7; + + if (!bitmap[i]) { + found = false; + bit += 8; + continue; + } + + found = true; + + while (bitmask) { + if (bitmap[i] & bitmask) { + uint16_t n; + + n = (uint16_t) window << 8 | (uint16_t) bit; + + /* Ignore pseudo-types. see RFC4034 section 4.1.2 */ + if (dns_type_is_pseudo(n)) + continue; + + r = bitmap_set(*types, n); + if (r < 0) + goto fail; + } + + bit ++; + bitmask >>= 1; + } + } + + if (!found) + return -EBADMSG; + + if (start) + *start = saved_rindex; + + return 0; +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +static int dns_packet_read_type_windows(DnsPacket *p, Bitmap **types, size_t size, size_t *start) { + size_t saved_rindex; + int r; + + saved_rindex = p->rindex; + + while (p->rindex < saved_rindex + size) { + r = dns_packet_read_type_window(p, types, NULL); + if (r < 0) + goto fail; + + /* don't read past end of current RR */ + if (p->rindex > saved_rindex + size) { + r = -EBADMSG; + goto fail; + } + } + + if (p->rindex != saved_rindex + size) { + r = -EBADMSG; + goto fail; + } + + if (start) + *start = saved_rindex; + + return 0; +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start) { + _cleanup_free_ char *name = NULL; + bool cache_flush = false; + uint16_t class, type; + DnsResourceKey *key; + size_t saved_rindex; + int r; + + assert(p); + assert(ret); + + saved_rindex = p->rindex; + + r = dns_packet_read_name(p, &name, true, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &class, NULL); + if (r < 0) + goto fail; + + if (p->protocol == DNS_PROTOCOL_MDNS) { + /* See RFC6762, Section 10.2 */ + + if (type != DNS_TYPE_OPT && (class & MDNS_RR_CACHE_FLUSH)) { + class &= ~MDNS_RR_CACHE_FLUSH; + cache_flush = true; + } + } + + key = dns_resource_key_new_consume(class, type, name); + if (!key) { + r = -ENOMEM; + goto fail; + } + + name = NULL; + *ret = key; + + if (ret_cache_flush) + *ret_cache_flush = cache_flush; + if (start) + *start = saved_rindex; + + return 0; +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +static bool loc_size_ok(uint8_t size) { + uint8_t m = size >> 4, e = size & 0xF; + + return m <= 9 && e <= 9 && (m > 0 || e == 0); +} + +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); + assert(ret); + + saved_rindex = p->rindex; + + r = dns_packet_read_key(p, &key, &cache_flush, NULL); + if (r < 0) + goto fail; + + if (!dns_class_is_valid_rr(key->class)|| + !dns_type_is_valid_rr(key->type)) { + r = -EBADMSG; + goto fail; + } + + rr = dns_resource_record_new(key); + if (!rr) { + r = -ENOMEM; + goto fail; + } + + r = dns_packet_read_uint32(p, &rr->ttl, NULL); + 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; + + if (p->rindex + rdlength > p->size) { + r = -EBADMSG; + goto fail; + } + + offset = p->rindex; + + switch (rr->key->type) { + + case DNS_TYPE_SRV: + r = dns_packet_read_uint16(p, &rr->srv.priority, NULL); + if (r < 0) + goto fail; + r = dns_packet_read_uint16(p, &rr->srv.weight, NULL); + if (r < 0) + goto fail; + r = dns_packet_read_uint16(p, &rr->srv.port, NULL); + if (r < 0) + goto fail; + r = dns_packet_read_name(p, &rr->srv.name, true, NULL); + break; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + r = dns_packet_read_name(p, &rr->ptr.name, true, NULL); + break; + + case DNS_TYPE_HINFO: + r = dns_packet_read_string(p, &rr->hinfo.cpu, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_string(p, &rr->hinfo.os, NULL); + break; + + case DNS_TYPE_SPF: /* exactly the same as TXT */ + case DNS_TYPE_TXT: + if (rdlength <= 0) { + DnsTxtItem *i; + /* RFC 6763, section 6.1 suggests to treat + * empty TXT RRs as equivalent to a TXT record + * with a single empty string. */ + + i = malloc0(offsetof(DnsTxtItem, data) + 1); /* for safety reasons we add an extra NUL byte */ + if (!i) + return -ENOMEM; + + rr->txt.items = i; + } else { + DnsTxtItem *last = NULL; + + while (p->rindex < offset + rdlength) { + DnsTxtItem *i; + const void *data; + size_t sz; + + r = dns_packet_read_raw_string(p, &data, &sz, NULL); + if (r < 0) + return r; + + i = malloc0(offsetof(DnsTxtItem, data) + sz + 1); /* extra NUL byte at the end */ + if (!i) + return -ENOMEM; + + memcpy(i->data, data, sz); + i->length = sz; + + LIST_INSERT_AFTER(items, rr->txt.items, last, i); + last = i; + } + } + + r = 0; + break; + + case DNS_TYPE_A: + r = dns_packet_read_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); + break; + + case DNS_TYPE_AAAA: + r = dns_packet_read_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL); + break; + + case DNS_TYPE_SOA: + r = dns_packet_read_name(p, &rr->soa.mname, true, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_name(p, &rr->soa.rname, true, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.serial, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.refresh, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.retry, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.expire, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.minimum, NULL); + break; + + case DNS_TYPE_MX: + r = dns_packet_read_uint16(p, &rr->mx.priority, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_name(p, &rr->mx.exchange, true, NULL); + break; + + case DNS_TYPE_LOC: { + uint8_t t; + size_t pos; + + r = dns_packet_read_uint8(p, &t, &pos); + if (r < 0) + goto fail; + + if (t == 0) { + rr->loc.version = t; + + r = dns_packet_read_uint8(p, &rr->loc.size, NULL); + if (r < 0) + goto fail; + + if (!loc_size_ok(rr->loc.size)) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_uint8(p, &rr->loc.horiz_pre, NULL); + if (r < 0) + goto fail; + + if (!loc_size_ok(rr->loc.horiz_pre)) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_uint8(p, &rr->loc.vert_pre, NULL); + if (r < 0) + goto fail; + + if (!loc_size_ok(rr->loc.vert_pre)) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_uint32(p, &rr->loc.latitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->loc.longitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->loc.altitude, NULL); + if (r < 0) + goto fail; + + break; + } else { + dns_packet_rewind(p, pos); + rr->unparseable = true; + goto unparseable; + } + } + + case DNS_TYPE_DS: + r = dns_packet_read_uint16(p, &rr->ds.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->ds.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->ds.digest_type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_memdup(p, rdlength - 4, + &rr->ds.digest, &rr->ds.digest_size, + NULL); + if (r < 0) + goto fail; + + if (rr->ds.digest_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_SSHFP: + r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->sshfp.fptype, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_memdup(p, rdlength - 2, + &rr->sshfp.fingerprint, &rr->sshfp.fingerprint_size, + NULL); + + if (rr->sshfp.fingerprint_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_DNSKEY: + r = dns_packet_read_uint16(p, &rr->dnskey.flags, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->dnskey.protocol, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_memdup(p, rdlength - 4, + &rr->dnskey.key, &rr->dnskey.key_size, + NULL); + + if (rr->dnskey.key_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_RRSIG: + r = dns_packet_read_uint16(p, &rr->rrsig.type_covered, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->rrsig.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->rrsig.labels, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->rrsig.original_ttl, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->rrsig.expiration, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->rrsig.inception, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &rr->rrsig.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_name(p, &rr->rrsig.signer, false, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_memdup(p, offset + rdlength - p->rindex, + &rr->rrsig.signature, &rr->rrsig.signature_size, + NULL); + + if (rr->rrsig.signature_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_NSEC: { + + /* + * RFC6762, section 18.14 explictly states mDNS should use name compression. + * This contradicts RFC3845, section 2.1.1 + */ + + bool allow_compressed = p->protocol == DNS_PROTOCOL_MDNS; + + r = dns_packet_read_name(p, &rr->nsec.next_domain_name, allow_compressed, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_type_windows(p, &rr->nsec.types, offset + rdlength - p->rindex, NULL); + if (r < 0) + goto fail; + + /* We accept empty NSEC bitmaps. The bit indicating the presence of the NSEC record itself + * is redundant and in e.g., RFC4956 this fact is used to define a use for NSEC records + * without the NSEC bit set. */ + + break; + } + case DNS_TYPE_NSEC3: { + uint8_t size; + + r = dns_packet_read_uint8(p, &rr->nsec3.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->nsec3.flags, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &rr->nsec3.iterations, NULL); + if (r < 0) + goto fail; + + /* this may be zero */ + r = dns_packet_read_uint8(p, &size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_memdup(p, size, &rr->nsec3.salt, &rr->nsec3.salt_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &size, NULL); + if (r < 0) + goto fail; + + if (size <= 0) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_memdup(p, size, &rr->nsec3.next_hashed_name, &rr->nsec3.next_hashed_name_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_type_windows(p, &rr->nsec3.types, offset + rdlength - p->rindex, NULL); + if (r < 0) + goto fail; + + /* empty non-terminals can have NSEC3 records, so empty bitmaps are allowed */ + + 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.data_size, NULL); + if (r < 0) + goto fail; + break; + } + if (r < 0) + goto fail; + if (p->rindex != offset + rdlength) { + r = -EBADMSG; + goto fail; + } + + *ret = rr; + rr = NULL; + + if (ret_cache_flush) + *ret_cache_flush = cache_flush; + if (start) + *start = saved_rindex; + + return 0; +fail: + dns_packet_rewind(p, saved_rindex); + 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; + size_t saved_rindex; + unsigned n, i; + int r; + + if (p->extracted) + return 0; + + saved_rindex = p->rindex; + dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE); + + n = DNS_PACKET_QDCOUNT(p); + if (n > 0) { + question = dns_question_new(n); + if (!question) { + r = -ENOMEM; + goto finish; + } + + for (i = 0; i < n; i++) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + bool cache_flush; + + 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; + } + } + + 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; + goto finish; + } + + for (i = 0; i < n; i++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + bool cache_flush; + + 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) { + 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 is 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 { + + /* 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; + question = NULL; + + p->answer = answer; + answer = NULL; + + p->extracted = true; + + r = 0; + +finish: + p->rindex = saved_rindex; + return r; +} + +int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key) { + int r; + + assert(p); + assert(key); + + /* Checks if the specified packet is a reply for the specified + * key and the specified key is the only one in the question + * section. */ + + if (DNS_PACKET_QR(p) != 1) + return 0; + + /* Let's unpack the packet, if that hasn't happened yet. */ + r = dns_packet_extract(p); + if (r < 0) + return r; + + if (p->question->n_keys != 1) + return 0; + + return dns_resource_key_equal(p->question->keys[0], key); +} + +static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = { + [DNS_RCODE_SUCCESS] = "SUCCESS", + [DNS_RCODE_FORMERR] = "FORMERR", + [DNS_RCODE_SERVFAIL] = "SERVFAIL", + [DNS_RCODE_NXDOMAIN] = "NXDOMAIN", + [DNS_RCODE_NOTIMP] = "NOTIMP", + [DNS_RCODE_REFUSED] = "REFUSED", + [DNS_RCODE_YXDOMAIN] = "YXDOMAIN", + [DNS_RCODE_YXRRSET] = "YRRSET", + [DNS_RCODE_NXRRSET] = "NXRRSET", + [DNS_RCODE_NOTAUTH] = "NOTAUTH", + [DNS_RCODE_NOTZONE] = "NOTZONE", + [DNS_RCODE_BADVERS] = "BADVERS", + [DNS_RCODE_BADKEY] = "BADKEY", + [DNS_RCODE_BADTIME] = "BADTIME", + [DNS_RCODE_BADMODE] = "BADMODE", + [DNS_RCODE_BADNAME] = "BADNAME", + [DNS_RCODE_BADALG] = "BADALG", + [DNS_RCODE_BADTRUNC] = "BADTRUNC", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_rcode, int); + +static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = { + [DNS_PROTOCOL_DNS] = "dns", + [DNS_PROTOCOL_MDNS] = "mdns", + [DNS_PROTOCOL_LLMNR] = "llmnr", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_protocol, DnsProtocol); diff --git a/src/resolve/src/resolved-dns-packet.h b/src/resolve/src/resolved-dns-packet.h new file mode 100644 index 0000000000..0bf34d270c --- /dev/null +++ b/src/resolve/src/resolved-dns-packet.h @@ -0,0 +1,272 @@ +#pragma once + +/*** + 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 . + ***/ + +#include +#include + +#include "hashmap.h" +#include "in-addr-util.h" +#include "macro.h" +#include "sparse-endian.h" + +typedef struct DnsPacketHeader DnsPacketHeader; +typedef struct DnsPacket DnsPacket; + +#include "resolved-def.h" +#include "resolved-dns-answer.h" +#include "resolved-dns-question.h" +#include "resolved-dns-rr.h" + +typedef enum DnsProtocol { + DNS_PROTOCOL_DNS, + DNS_PROTOCOL_MDNS, + DNS_PROTOCOL_LLMNR, + _DNS_PROTOCOL_MAX, + _DNS_PROTOCOL_INVALID = -1 +} DnsProtocol; + +struct DnsPacketHeader { + uint16_t id; + be16_t flags; + be16_t qdcount; + be16_t ancount; + be16_t nscount; + be16_t arcount; +}; + +#define DNS_PACKET_HEADER_SIZE sizeof(DnsPacketHeader) +#define UDP_PACKET_HEADER_SIZE (sizeof(struct iphdr) + sizeof(struct udphdr)) + +/* The various DNS protocols deviate in how large a packet can grow, + but the TCP transport has a 16bit size field, hence that appears to + be the absolute maximum. */ +#define DNS_PACKET_SIZE_MAX 0xFFFF + +/* RFC 1035 say 512 is the maximum, for classic unicast DNS */ +#define DNS_PACKET_UNICAST_SIZE_MAX 512 + +/* With EDNS0 we can use larger packets, default to 4096, which is what is commonly used */ +#define DNS_PACKET_UNICAST_SIZE_LARGE_MAX 4096 + +#define DNS_PACKET_SIZE_START 512 + +struct DnsPacket { + int n_ref; + DnsProtocol protocol; + 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; + DnsAnswer *answer; + DnsResourceRecord *opt; + + /* Packet reception metadata */ + int ifindex; + int family, ipproto; + union in_addr_union sender, destination; + uint16_t sender_port, destination_port; + uint32_t ttl; + + /* For support of truncated packets */ + DnsPacket *more; + + bool on_stack:1; + bool extracted:1; + bool refuse_compression:1; + bool canonical_form:1; +}; + +static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) { + if (_unlikely_(!p)) + return NULL; + + if (p->_data) + return p->_data; + + return ((uint8_t*) p) + ALIGN(sizeof(DnsPacket)); +} + +#define DNS_PACKET_HEADER(p) ((DnsPacketHeader*) DNS_PACKET_DATA(p)) +#define DNS_PACKET_ID(p) DNS_PACKET_HEADER(p)->id +#define DNS_PACKET_QR(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 15) & 1) +#define DNS_PACKET_OPCODE(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 11) & 15) +#define DNS_PACKET_AA(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 10) & 1) +#define DNS_PACKET_TC(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 9) & 1) +#define DNS_PACKET_RD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 8) & 1) +#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) + +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) +#define DNS_PACKET_LLMNR_T(p) DNS_PACKET_RD(p) + +#define DNS_PACKET_QDCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->qdcount) +#define DNS_PACKET_ANCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->ancount) +#define DNS_PACKET_NSCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->nscount) +#define DNS_PACKET_ARCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->arcount) + +#define DNS_PACKET_MAKE_FLAGS(qr, opcode, aa, tc, rd, ra, ad, cd, rcode) \ + (((uint16_t) !!(qr) << 15) | \ + ((uint16_t) ((opcode) & 15) << 11) | \ + ((uint16_t) !!(aa) << 10) | /* on LLMNR: c */ \ + ((uint16_t) !!(tc) << 9) | \ + ((uint16_t) !!(rd) << 8) | /* on LLMNR: t */ \ + ((uint16_t) !!(ra) << 7) | \ + ((uint16_t) !!(ad) << 5) | \ + ((uint16_t) !!(cd) << 4) | \ + ((uint16_t) ((rcode) & 15))) + +static inline unsigned DNS_PACKET_RRCOUNT(DnsPacket *p) { + return + (unsigned) DNS_PACKET_ANCOUNT(p) + + (unsigned) DNS_PACKET_NSCOUNT(p) + + (unsigned) DNS_PACKET_ARCOUNT(p); +} + +int dns_packet_new(DnsPacket **p, DnsProtocol protocol, size_t mtu); +int dns_packet_new_query(DnsPacket **p, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled); + +void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated); + +DnsPacket *dns_packet_ref(DnsPacket *p); +DnsPacket *dns_packet_unref(DnsPacket *p); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsPacket*, dns_packet_unref); + +int dns_packet_validate(DnsPacket *p); +int dns_packet_validate_reply(DnsPacket *p); +int dns_packet_validate_query(DnsPacket *p); + +int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key); + +int dns_packet_append_blob(DnsPacket *p, const void *d, size_t sz, size_t *start); +int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start); +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, 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(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); +int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start); +int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start); +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, 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); + +int dns_packet_skip_question(DnsPacket *p); +int dns_packet_extract(DnsPacket *p); + +static inline bool DNS_PACKET_SHALL_CACHE(DnsPacket *p) { + /* Never cache data originating from localhost, under the + * assumption, that it's coming from a locally DNS forwarder + * or server, that is caching on its own. */ + + 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, + DNS_RCODE_SERVFAIL = 2, + DNS_RCODE_NXDOMAIN = 3, + DNS_RCODE_NOTIMP = 4, + DNS_RCODE_REFUSED = 5, + DNS_RCODE_YXDOMAIN = 6, + DNS_RCODE_YXRRSET = 7, + DNS_RCODE_NXRRSET = 8, + DNS_RCODE_NOTAUTH = 9, + DNS_RCODE_NOTZONE = 10, + DNS_RCODE_BADVERS = 16, + DNS_RCODE_BADSIG = 16, /* duplicate value! */ + DNS_RCODE_BADKEY = 17, + DNS_RCODE_BADTIME = 18, + DNS_RCODE_BADMODE = 19, + DNS_RCODE_BADNAME = 20, + DNS_RCODE_BADALG = 21, + DNS_RCODE_BADTRUNC = 22, + _DNS_RCODE_MAX_DEFINED +}; + +const char* dns_rcode_to_string(int i) _const_; +int dns_rcode_from_string(const char *s) _pure_; + +const char* dns_protocol_to_string(DnsProtocol p) _const_; +DnsProtocol dns_protocol_from_string(const char *s) _pure_; + +#define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) }) +#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, 0x00, 0x00, 0xfb } }) + +static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family, bool authenticated) { + uint64_t f; + + /* Converts a protocol + family into a flags field as used in queries and responses */ + + f = authenticated ? SD_RESOLVED_AUTHENTICATED : 0; + + switch (protocol) { + case DNS_PROTOCOL_DNS: + return f|SD_RESOLVED_DNS; + + case DNS_PROTOCOL_LLMNR: + return f|(family == AF_INET6 ? SD_RESOLVED_LLMNR_IPV6 : SD_RESOLVED_LLMNR_IPV4); + + case DNS_PROTOCOL_MDNS: + return family == AF_INET6 ? SD_RESOLVED_MDNS_IPV6 : SD_RESOLVED_MDNS_IPV4; + + default: + break; + } + + return 0; +} diff --git a/src/resolve/src/resolved-dns-query.c b/src/resolve/src/resolved-dns-query.c new file mode 100644 index 0000000000..a378b2b7f7 --- /dev/null +++ b/src/resolve/src/resolved-dns-query.c @@ -0,0 +1,1114 @@ +/*** + 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 . +***/ + +#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) + +#define CNAME_MAX 8 +#define QUERIES_MAX 2048 +#define AUXILIARY_QUERIES_MAX 64 + +static int dns_query_candidate_new(DnsQueryCandidate **ret, DnsQuery *q, DnsScope *s) { + DnsQueryCandidate *c; + + assert(ret); + assert(q); + assert(s); + + c = new0(DnsQueryCandidate, 1); + if (!c) + return -ENOMEM; + + c->query = q; + c->scope = s; + + LIST_PREPEND(candidates_by_query, q->candidates, c); + LIST_PREPEND(candidates_by_scope, s->query_candidates, c); + + *ret = c; + return 0; +} + +static void dns_query_candidate_stop(DnsQueryCandidate *c) { + DnsTransaction *t; + + assert(c); + + while ((t = set_steal_first(c->transactions))) { + set_remove(t->notify_query_candidates, c); + dns_transaction_gc(t); + } +} + +DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c) { + + if (!c) + return NULL; + + dns_query_candidate_stop(c); + + set_free(c->transactions); + dns_search_domain_unref(c->search_domain); + + if (c->query) + LIST_REMOVE(candidates_by_query, c->query->candidates, c); + + if (c->scope) + LIST_REMOVE(candidates_by_scope, c->scope->query_candidates, c); + + free(c); + + return NULL; +} + +static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) { + DnsSearchDomain *next = NULL; + + assert(c); + + 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; + + if (!next->route_only) + break; + + /* Skip over route-only domains */ + next = next->domains_next; + } + + dns_search_domain_unref(c->search_domain); + c->search_domain = dns_search_domain_ref(next); + + return 1; +} + +static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResourceKey *key) { + DnsTransaction *t; + int r; + + assert(c); + assert(key); + + t = dns_scope_find_transaction(c->scope, key, true); + if (!t) { + r = dns_transaction_new(&t, c->scope, key); + if (r < 0) + return r; + } else { + if (set_contains(c->transactions, t)) + return 0; + } + + r = set_ensure_allocated(&c->transactions, NULL); + if (r < 0) + goto gc; + + r = set_ensure_allocated(&t->notify_query_candidates, NULL); + if (r < 0) + goto gc; + + r = set_put(t->notify_query_candidates, c); + if (r < 0) + goto gc; + + r = set_put(c->transactions, t); + if (r < 0) { + (void) set_remove(t->notify_query_candidates, c); + goto gc; + } + + return 1; + +gc: + dns_transaction_gc(t); + return r; +} + +static int dns_query_candidate_go(DnsQueryCandidate *c) { + DnsTransaction *t; + Iterator i; + int r; + unsigned n = 0; + + assert(c); + + /* Start the transactions that are not started yet */ + SET_FOREACH(t, c->transactions, i) { + if (t->state != DNS_TRANSACTION_NULL) + continue; + + 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; +} + +static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { + DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; + DnsTransaction *t; + Iterator i; + + assert(c); + + if (c->error_code != 0) + 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 + * VALIDATING state, then this means there's + * also one in PENDING state, hence we can + * return PENDING immediately. */ + return DNS_TRANSACTION_PENDING; + + case DNS_TRANSACTION_SUCCESS: + state = t->state; + break; + + default: + if (state != DNS_TRANSACTION_SUCCESS) + state = t->state; + + break; + } + } + + 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; + + assert(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, 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; + + 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; + + n++; + } + + return n; + +fail: + dns_query_candidate_stop(c); + return r; +} + +void dns_query_candidate_notify(DnsQueryCandidate *c) { + DnsTransactionState state; + int r; + + assert(c); + + state = dns_query_candidate_state(c); + + if (DNS_TRANSACTION_IS_LIVE(state)) + return; + + if (state != DNS_TRANSACTION_SUCCESS && c->search_domain) { + + r = dns_query_candidate_next_search_domain(c); + if (r < 0) + goto fail; + + if (r > 0) { + /* OK, there's another search domain to try, let's do so. */ + + r = dns_query_candidate_setup_transactions(c); + if (r < 0) + goto fail; + + if (r > 0) { + /* New transactions where queued. Start them and wait */ + + r = dns_query_candidate_go(c); + if (r < 0) + goto fail; + + return; + } + } + + } + + dns_query_ready(c->query); + return; + +fail: + log_warning_errno(r, "Failed to follow search domains: %m"); + c->error_code = r; + dns_query_ready(c->query); +} + +static void dns_query_stop(DnsQuery *q) { + DnsQueryCandidate *c; + + assert(q); + + q->timeout_event_source = sd_event_source_unref(q->timeout_event_source); + + LIST_FOREACH(candidates_by_query, c, q->candidates) + 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; + + while (q->auxiliary_queries) + dns_query_free(q->auxiliary_queries); + + if (q->auxiliary_for) { + assert(q->auxiliary_for->n_auxiliary_queries > 0); + q->auxiliary_for->n_auxiliary_queries--; + LIST_REMOVE(auxiliary_queries, q->auxiliary_for->auxiliary_queries, q); + } + + dns_query_free_candidates(q); + + 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--; + } + + free(q); + + return NULL; +} + +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; + DnsResourceKey *key; + bool good = false; + int r; + + assert(m); + + 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; + + q = new0(DnsQuery, 1); + if (!q) + return -ENOMEM; + + q->question_utf8 = dns_question_ref(question_utf8); + q->question_idna = dns_question_ref(question_idna); + q->ifindex = ifindex; + q->flags = flags; + 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; + + r = dns_question_contains(question_utf8, key); + if (r < 0) + return r; + if (r > 0) + continue; + + r = dns_resource_key_to_string(key, &p); + if (r < 0) + return r; + + log_debug("Looking up IDNA RR for %s.", strstrip(p)); + } + + LIST_PREPEND(queries, m->dns_queries, q); + m->n_dns_queries++; + q->manager = m; + + if (ret) + *ret = q; + q = NULL; + + return 0; +} + +int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for) { + assert(q); + assert(auxiliary_for); + + /* Ensure that that the query is not auxiliary yet, and + * nothing else is auxiliary to it either */ + assert(!q->auxiliary_for); + assert(!q->auxiliary_queries); + + /* Ensure that the unit we shall be made auxiliary for isn't + * auxiliary itself */ + assert(!auxiliary_for->auxiliary_for); + + if (auxiliary_for->n_auxiliary_queries >= AUXILIARY_QUERIES_MAX) + return -EAGAIN; + + LIST_PREPEND(auxiliary_queries, auxiliary_for->auxiliary_queries, q); + q->auxiliary_for = auxiliary_for; + + auxiliary_for->n_auxiliary_queries++; + return 0; +} + +static void dns_query_complete(DnsQuery *q, DnsTransactionState state) { + assert(q); + assert(!DNS_TRANSACTION_IS_LIVE(state)); + assert(DNS_TRANSACTION_IS_LIVE(q->state)); + + /* Note that this call might invalidate the query. Callers + * should hence not attempt to access the query or transaction + * after calling this function. */ + + q->state = state; + + dns_query_stop(q); + if (q->complete) + q->complete(q); +} + +static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) { + DnsQuery *q = userdata; + + assert(s); + assert(q); + + dns_query_complete(q, DNS_TRANSACTION_TIMEOUT); + return 0; +} + +static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) { + DnsQueryCandidate *c; + int r; + + assert(q); + assert(s); + + r = dns_query_candidate_new(&c, q, s); + if (r < 0) + return r; + + /* 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_idna)); + if (r < 0) + goto fail; + if (r > 0) { + /* OK, we need a search domain now. Let's find one for this scope */ + + r = dns_query_candidate_next_search_domain(c); + if (r <= 0) /* if there's no search domain, then we won't add any transaction. */ + goto fail; + } + } + + r = dns_query_candidate_setup_transactions(c); + if (r < 0) + goto fail; + + return 0; + +fail: + dns_query_candidate_free(c); + return r; +} + +static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + int r; + + assert(q); + assert(state); + + /* 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_RCODE_FAILURE, + DNS_TRANSACTION_NO_SERVERS, + DNS_TRANSACTION_TIMEOUT, + DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, + DNS_TRANSACTION_NETWORK_DOWN, + DNS_TRANSACTION_NOT_FOUND)) + return 0; + + r = dns_synthesize_answer( + q->manager, + q->question_utf8, + q->ifindex, + &answer); + + if (r <= 0) + return r; + + dns_query_reset_answer(q); + + 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; + + *state = DNS_TRANSACTION_SUCCESS; + + return 1; +} + +static int dns_query_try_etc_hosts(DnsQuery *q) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + int r; + + assert(q); + + /* 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 = manager_etc_hosts_lookup( + q->manager, + q->question_utf8, + &answer); + if (r <= 0) + return r; + + dns_query_reset_answer(q); + + 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; + + return 1; +} + +int dns_query_go(DnsQuery *q) { + DnsScopeMatch found = DNS_SCOPE_NO; + DnsScope *s, *first = NULL; + DnsQueryCandidate *c; + int r; + + assert(q); + + if (q->state != DNS_TRANSACTION_NULL) + return 0; + + 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) + return match; + + if (match == DNS_SCOPE_NO) + continue; + + found = match; + + if (match == DNS_SCOPE_YES) { + first = s; + break; + } else { + assert(match == DNS_SCOPE_MAYBE); + + if (!first) + first = s; + } + } + + if (found == DNS_SCOPE_NO) { + DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; + + r = dns_query_synthesize_reply(q, &state); + if (r < 0) + return r; + + dns_query_complete(q, state); + return 1; + } + + r = dns_query_add_candidate(q, first); + if (r < 0) + goto fail; + + 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) + goto fail; + + if (match != found) + continue; + + r = dns_query_add_candidate(q, s); + if (r < 0) + goto fail; + } + + dns_query_reset_answer(q); + + r = sd_event_add_time( + q->manager->event, + &q->timeout_event_source, + clock_boottime_or_monotonic(), + now(clock_boottime_or_monotonic()) + QUERY_TIMEOUT_USEC, 0, + on_query_timeout, 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++; + + /* Start the transactions */ + LIST_FOREACH(candidates_by_query, c, q->candidates) { + r = dns_query_candidate_go(c); + if (r < 0) { + q->block_ready--; + goto fail; + } + } + + q->block_ready--; + dns_query_ready(q); + + return 1; + +fail: + dns_query_stop(q); + return r; +} + +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; + + assert(q); + + if (!c) { + 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) { + + 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) + goto fail; + + q->answer_rcode = t->answer_rcode; + q->answer_errno = 0; + + if (t->answer_authenticated) { + has_authenticated = true; + 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; + } + + case DNS_TRANSACTION_NULL: + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: + case DNS_TRANSACTION_ABORTED: + /* Ignore transactions that didn't complete */ + continue; + + default: + /* Any kind of failure? Store the data away, + * if there's nothing stored yet. */ + + if (state == DNS_TRANSACTION_SUCCESS) + continue; + + 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; + + dns_search_domain_unref(q->answer_search_domain); + q->answer_search_domain = dns_search_domain_ref(c->search_domain); + + 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) { + + DnsQueryCandidate *bad = NULL, *c; + bool pending = false; + + assert(q); + assert(DNS_TRANSACTION_IS_LIVE(q->state)); + + /* Note that this call might invalidate the query. Callers + * should hence not attempt to access the query or transaction + * after calling this function, unless the block_ready + * counter was explicitly bumped before doing so. */ + + if (q->block_ready > 0) + return; + + LIST_FOREACH(candidates_by_query, c, q->candidates) { + DnsTransactionState state; + + state = dns_query_candidate_state(c); + switch (state) { + + case DNS_TRANSACTION_SUCCESS: + /* One of the candidates is successful, + * let's use it, and copy its data out */ + dns_query_accept(q, c); + return; + + case DNS_TRANSACTION_NULL: + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: + /* One of the candidates is still going on, + * let's maybe wait for it */ + pending = true; + break; + + default: + /* Any kind of failure */ + bad = c; + break; + } + } + + if (pending) + return; + + dns_query_accept(q, bad); +} + +static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) { + _cleanup_(dns_question_unrefp) DnsQuestion *nq_idna = NULL, *nq_utf8 = NULL; + int r, k; + + assert(q); + + q->n_cname_redirects ++; + if (q->n_cname_redirects > CNAME_MAX) + return -ELOOP; + + 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)); + + 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); + + q->state = DNS_TRANSACTION_NULL; + + return 0; +} + +int dns_query_process_cname(DnsQuery *q) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL; + DnsQuestion *question; + DnsResourceRecord *rr; + int r; + + assert(q); + + if (!IN_SET(q->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_NULL)) + return DNS_QUERY_NOMATCH; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + DNS_ANSWER_FOREACH(rr, q->answer) { + r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + if (r < 0) + return r; + if (r > 0) + return DNS_QUERY_MATCH; /* The answer matches directly, no need to follow cnames */ + + r = dns_question_matches_cname_or_dname(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + if (r < 0) + return r; + if (r > 0 && !cname) + cname = dns_resource_record_ref(rr); + } + + if (!cname) + return DNS_QUERY_NOMATCH; /* No match and no cname to follow */ + + if (q->flags & SD_RESOLVED_NO_CNAME) + return -ELOOP; + + /* OK, let's actually follow the CNAME */ + r = dns_query_cname_redirect(q, cname); + if (r < 0) + return r; + + /* Let's see if the answer can already answer the new + * redirected question */ + 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 DNS_QUERY_RESTARTED; /* We restarted the query for a new cname */ +} + +static int on_bus_track(sd_bus_track *t, void *userdata) { + DnsQuery *q = userdata; + + assert(t); + assert(q); + + log_debug("Client of active query vanished, aborting query."); + dns_query_complete(q, DNS_TRANSACTION_ABORTED); + return 0; +} + +int dns_query_bus_track(DnsQuery *q, sd_bus_message *m) { + int r; + + assert(q); + assert(m); + + if (!q->bus_track) { + r = sd_bus_track_new(sd_bus_message_get_bus(m), &q->bus_track, on_bus_track, q); + if (r < 0) + return r; + } + + r = sd_bus_track_add_sender(q->bus_track, m); + if (r < 0) + return r; + + 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/src/resolved-dns-query.h b/src/resolve/src/resolved-dns-query.h new file mode 100644 index 0000000000..c2ac02f68b --- /dev/null +++ b/src/resolve/src/resolved-dns-query.h @@ -0,0 +1,133 @@ +#pragma once + +/*** + 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 . +***/ + + +#include "sd-bus.h" + +#include "set.h" + +typedef struct DnsQueryCandidate DnsQueryCandidate; +typedef struct DnsQuery DnsQuery; + +#include "resolved-dns-answer.h" +#include "resolved-dns-question.h" +#include "resolved-dns-stream.h" +#include "resolved-dns-search-domain.h" + +struct DnsQueryCandidate { + DnsQuery *query; + DnsScope *scope; + + DnsSearchDomain *search_domain; + + int error_code; + Set *transactions; + + LIST_FIELDS(DnsQueryCandidate, candidates_by_query); + LIST_FIELDS(DnsQueryCandidate, candidates_by_scope); +}; + +struct DnsQuery { + Manager *manager; + + /* When resolving a service, we first create a TXT+SRV query, + * and then for the hostnames we discover auxiliary A+AAAA + * queries. This pointer always points from the auxiliary + * queries back to the TXT+SRV query. */ + DnsQuery *auxiliary_for; + LIST_HEAD(DnsQuery, auxiliary_queries); + unsigned n_auxiliary_queries; + int auxiliary_result; + + /* 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; + + LIST_HEAD(DnsQueryCandidate, candidates); + sd_event_source *timeout_event_source; + + /* Discovered data */ + DnsAnswer *answer; + int answer_rcode; + DnssecResult answer_dnssec_result; + bool answer_authenticated; + DnsProtocol answer_protocol; + int answer_family; + DnsSearchDomain *answer_search_domain; + int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ + + /* Bus client information */ + sd_bus_message *request; + int request_family; + bool request_address_valid; + union in_addr_union request_address; + unsigned block_all_complete; + char *request_address_string; + + /* Completion callback */ + void (*complete)(DnsQuery* q); + unsigned block_ready; + + sd_bus_track *bus_track; + + LIST_FIELDS(DnsQuery, queries); + 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_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); + +int dns_query_go(DnsQuery *q); +void dns_query_ready(DnsQuery *q); + +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/src/resolved-dns-question.c b/src/resolve/src/resolved-dns-question.c new file mode 100644 index 0000000000..8e452e79a4 --- /dev/null +++ b/src/resolve/src/resolved-dns-question.c @@ -0,0 +1,468 @@ +/*** + 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 . +***/ + +#include "alloc-util.h" +#include "dns-domain.h" +#include "dns-type.h" +#include "resolved-dns-question.h" + +DnsQuestion *dns_question_new(unsigned n) { + DnsQuestion *q; + + assert(n > 0); + + q = malloc0(offsetof(DnsQuestion, keys) + sizeof(DnsResourceKey*) * n); + if (!q) + return NULL; + + q->n_ref = 1; + q->n_allocated = n; + + return q; +} + +DnsQuestion *dns_question_ref(DnsQuestion *q) { + if (!q) + return NULL; + + assert(q->n_ref > 0); + q->n_ref++; + return q; +} + +DnsQuestion *dns_question_unref(DnsQuestion *q) { + if (!q) + return NULL; + + assert(q->n_ref > 0); + + if (q->n_ref == 1) { + unsigned i; + + for (i = 0; i < q->n_keys; i++) + dns_resource_key_unref(q->keys[i]); + free(q); + } else + q->n_ref--; + + return NULL; +} + +int dns_question_add(DnsQuestion *q, DnsResourceKey *key) { + unsigned i; + int r; + + assert(key); + + if (!q) + return -ENOSPC; + + for (i = 0; i < q->n_keys; i++) { + r = dns_resource_key_equal(q->keys[i], key); + if (r < 0) + return r; + if (r > 0) + return 0; + } + + if (q->n_keys >= q->n_allocated) + return -ENOSPC; + + q->keys[q->n_keys++] = dns_resource_key_ref(key); + return 0; +} + +int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { + unsigned i; + int r; + + assert(rr); + + if (!q) + return 0; + + for (i = 0; i < q->n_keys; i++) { + r = dns_resource_key_match_rr(q->keys[i], rr, search_domain); + if (r != 0) + return r; + } + + return 0; +} + +int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { + unsigned i; + int r; + + assert(rr); + + 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; + } + + return 0; +} + +int dns_question_is_valid_for_query(DnsQuestion *q) { + const char *name; + unsigned i; + int r; + + if (!q) + return 0; + + if (q->n_keys <= 0) + return 0; + + if (q->n_keys > 65535) + return 0; + + name = DNS_RESOURCE_KEY_NAME(q->keys[0]); + if (!name) + return 0; + + /* Check that all keys in this question bear the same name */ + for (i = 0; i < q->n_keys; i++) { + assert(q->keys[i]); + + 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, const DnsResourceKey *k) { + unsigned j; + int r; + + assert(k); + + if (!a) + return 0; + + for (j = 0; j < a->n_keys; j++) { + r = dns_resource_key_equal(a->keys[j], k); + if (r != 0) + return r; + } + + return 0; +} + +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) + return a->n_keys == 0; + + /* Checks if all keys in a are also contained b, and vice versa */ + + for (j = 0; j < a->n_keys; j++) { + r = dns_question_contains(b, a->keys[j]); + if (r <= 0) + return r; + } + + for (j = 0; j < b->n_keys; j++) { + r = dns_question_contains(a, b->keys[j]); + if (r <= 0) + return r; + } + + return 1; +} + +int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret) { + _cleanup_(dns_question_unrefp) DnsQuestion *n = NULL; + DnsResourceKey *key; + bool same = true; + int r; + + assert(cname); + assert(ret); + assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); + + if (dns_question_size(q) <= 0) { + *ret = NULL; + return 0; + } + + 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(key), DNS_RESOURCE_KEY_NAME(cname->key), cname->dname.name, &destination); + if (r < 0) + return r; + if (r == 0) + continue; + + d = destination; + } + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), d); + if (r < 0) + return r; + + if (r == 0) { + same = false; + break; + } + } + + /* Fully the same, indicate we didn't do a thing */ + if (same) { + *ret = NULL; + return 0; + } + + n = dns_question_new(q->n_keys); + if (!n) + return -ENOMEM; + + /* Create a new question, and patch in the new name */ + DNS_QUESTION_FOREACH(key, q) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; + + k = dns_resource_key_new_redirect(key, cname); + if (!k) + return -ENOMEM; + + r = dns_question_add(n, k); + if (r < 0) + return r; + } + + *ret = n; + n = NULL; + + return 1; +} + +const char *dns_question_first_name(DnsQuestion *q) { + + if (!q) + return NULL; + + if (q->n_keys < 1) + return NULL; + + return DNS_RESOURCE_KEY_NAME(q->keys[0]); +} + +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); + assert(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; + + if (family != AF_INET6) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(q, key); + if (r < 0) + return r; + } + + if (family != AF_INET) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(q, key); + if (r < 0) + return r; + } + + *ret = q; + q = NULL; + + return 0; +} + +int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; + _cleanup_free_ char *reverse = NULL; + int r; + + assert(ret); + assert(a); + + if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) + return -EAFNOSUPPORT; + + r = dns_name_reverse(family, a, &reverse); + if (r < 0) + return r; + + q = dns_question_new(1); + if (!q) + return -ENOMEM; + + key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, reverse); + if (!key) + return -ENOMEM; + + reverse = NULL; + + r = dns_question_add(q, key); + if (r < 0) + return r; + + *ret = q; + q = NULL; + + return 0; +} + +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); + + /* 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) + return -ENOMEM; + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_SRV, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(q, key); + if (r < 0) + return r; + + if (with_txt) { + dns_resource_key_unref(key); + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_TXT, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(q, key); + if (r < 0) + return r; + } + + *ret = q; + q = NULL; + + return 0; +} diff --git a/src/resolve/src/resolved-dns-question.h b/src/resolve/src/resolved-dns-question.h new file mode 100644 index 0000000000..ea41478975 --- /dev/null +++ b/src/resolve/src/resolved-dns-question.h @@ -0,0 +1,69 @@ +#pragma once + +/*** + 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 . +***/ + +typedef struct DnsQuestion DnsQuestion; + +#include "macro.h" +#include "resolved-dns-rr.h" + +/* A simple array of resource keys */ + +struct DnsQuestion { + unsigned n_ref; + unsigned n_keys, n_allocated; + DnsResourceKey* keys[0]; +}; + +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, 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 *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_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, 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) \ + for (unsigned UNIQ_T(i, u) = ({ \ + (key) = ((q) && (q)->n_keys > 0) ? (q)->keys[0] : NULL; \ + 0; \ + }); \ + (q) && (UNIQ_T(i, u) < (q)->n_keys); \ + UNIQ_T(i, u)++, (key) = (UNIQ_T(i, u) < (q)->n_keys ? (q)->keys[UNIQ_T(i, u)] : NULL)) + +#define DNS_QUESTION_FOREACH(key, q) _DNS_QUESTION_FOREACH(UNIQ, key, q) diff --git a/src/resolve/src/resolved-dns-rr.c b/src/resolve/src/resolved-dns-rr.c new file mode 100644 index 0000000000..40f8e28dfd --- /dev/null +++ b/src/resolve/src/resolved-dns-rr.c @@ -0,0 +1,1521 @@ +/*** + 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 . +***/ + +#include + +#include "alloc-util.h" +#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; + size_t l; + + assert(name); + + l = strlen(name); + k = malloc0(sizeof(DnsResourceKey) + l + 1); + if (!k) + return NULL; + + k->n_ref = 1; + k->class = class; + k->type = type; + + strcpy((char*) k + sizeof(DnsResourceKey), name); + + return k; +} + +DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname) { + int r; + + assert(key); + assert(cname); + + assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); + + if (cname->key->type == DNS_TYPE_CNAME) + return dns_resource_key_new(key->class, key->type, cname->cname.name); + else { + DnsResourceKey *k; + char *destination = NULL; + + r = dns_name_change_suffix(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname->key), cname->dname.name, &destination); + if (r < 0) + return NULL; + if (r == 0) + return dns_resource_key_ref((DnsResourceKey*) key); + + k = dns_resource_key_new_consume(key->class, key->type, destination); + if (!k) { + free(destination); + return NULL; + } + + return k; + } +} + +int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name) { + DnsResourceKey *new_key; + char *joined; + int r; + + assert(ret); + assert(key); + assert(name); + + if (dns_name_is_root(name)) { + *ret = dns_resource_key_ref(key); + return 0; + } + + r = dns_name_concat(DNS_RESOURCE_KEY_NAME(key), name, &joined); + if (r < 0) + return r; + + new_key = dns_resource_key_new_consume(key->class, key->type, joined); + if (!new_key) { + free(joined); + return -ENOMEM; + } + + *ret = new_key; + return 0; +} + +DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name) { + DnsResourceKey *k; + + assert(name); + + k = new0(DnsResourceKey, 1); + if (!k) + return NULL; + + k->n_ref = 1; + k->class = class; + k->type = type; + k->_name = name; + + return k; +} + +DnsResourceKey* dns_resource_key_ref(DnsResourceKey *k) { + + if (!k) + return NULL; + + /* Static/const keys created with DNS_RESOURCE_KEY_CONST will + * set this to -1, they should not be reffed/unreffed */ + assert(k->n_ref != (unsigned) -1); + + assert(k->n_ref > 0); + k->n_ref++; + + return k; +} + +DnsResourceKey* dns_resource_key_unref(DnsResourceKey *k) { + if (!k) + return NULL; + + assert(k->n_ref != (unsigned) -1); + assert(k->n_ref > 0); + + if (k->n_ref == 1) { + free(k->_name); + free(k); + } else + k->n_ref--; + + return NULL; +} + +bool dns_resource_key_is_address(const DnsResourceKey *key) { + assert(key); + + /* Check if this is an A or AAAA resource key */ + + return key->class == DNS_CLASS_IN && IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_AAAA); +} + +int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) { + int r; + + if (a == b) + return 1; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(a), DNS_RESOURCE_KEY_NAME(b)); + if (r <= 0) + return r; + + if (a->class != b->class) + return 0; + + if (a->type != b->type) + return 0; + + return 1; +} + +int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain) { + int r; + + assert(key); + assert(rr); + + if (key == rr->key) + return 1; + + /* Checks if an rr matches the specified key. If a search + * domain is specified, it will also be checked if the key + * with the search domain suffixed might match the RR. */ + + if (rr->key->class != key->class && key->class != DNS_CLASS_ANY) + return 0; + + if (rr->key->type != key->type && key->type != DNS_TYPE_ANY) + return 0; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key)); + if (r != 0) + return r; + + if (search_domain) { + _cleanup_free_ char *joined = NULL; + + r = dns_name_concat(DNS_RESOURCE_KEY_NAME(key), search_domain, &joined); + if (r < 0) + return r; + + return dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), joined); + } + + return 0; +} + +int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain) { + int r; + + assert(key); + assert(cname); + + if (cname->class != key->class && key->class != DNS_CLASS_ANY) + return 0; + + if (cname->type == DNS_TYPE_CNAME) + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname)); + else if (cname->type == DNS_TYPE_DNAME) + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname)); + else + return 0; + + if (r != 0) + return r; + + if (search_domain) { + _cleanup_free_ char *joined = NULL; + + r = dns_name_concat(DNS_RESOURCE_KEY_NAME(key), search_domain, &joined); + if (r < 0) + return r; + + if (cname->type == DNS_TYPE_CNAME) + return dns_name_equal(joined, DNS_RESOURCE_KEY_NAME(cname)); + else if (cname->type == DNS_TYPE_DNAME) + return dns_name_endswith(joined, DNS_RESOURCE_KEY_NAME(cname)); + } + + return 0; +} + +int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa) { + assert(soa); + assert(key); + + /* Checks whether 'soa' is a SOA record for the specified key. */ + + if (soa->class != key->class) + return 0; + + if (soa->type != DNS_TYPE_SOA) + return 0; + + 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) { + const DnsResourceKey *k = i; + + assert(k); + + dns_name_hash_func(DNS_RESOURCE_KEY_NAME(k), state); + siphash24_compress(&k->class, sizeof(k->class), state); + siphash24_compress(&k->type, sizeof(k->type), state); +} + +static int dns_resource_key_compare_func(const void *a, const void *b) { + const DnsResourceKey *x = a, *y = b; + int ret; + + ret = dns_name_compare_func(DNS_RESOURCE_KEY_NAME(x), DNS_RESOURCE_KEY_NAME(y)); + if (ret != 0) + return ret; + + if (x->type < y->type) + return -1; + if (x->type > y->type) + return 1; + + if (x->class < y->class) + return -1; + if (x->class > y->class) + return 1; + + return 0; +} + +const struct hash_ops dns_resource_key_hash_ops = { + .hash = dns_resource_key_hash_func, + .compare = dns_resource_key_compare_func +}; + +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, *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); + c = cbuf; + } + + t = dns_type_to_string(key->type); + if (!t){ + sprintf(tbuf, "TYPE%u", key->type); + t = tbuf; + } + + 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; + + rr = new0(DnsResourceRecord, 1); + if (!rr) + return NULL; + + 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; +} + +DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + key = dns_resource_key_new(class, type, name); + if (!key) + return NULL; + + return dns_resource_record_new(key); +} + +DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr) { + if (!rr) + return NULL; + + assert(rr->n_ref > 0); + rr->n_ref++; + + return rr; +} + +DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) { + if (!rr) + return NULL; + + assert(rr->n_ref > 0); + + if (rr->n_ref > 1) { + rr->n_ref--; + return NULL; + } + + if (rr->key) { + switch(rr->key->type) { + + case DNS_TYPE_SRV: + free(rr->srv.name); + break; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + free(rr->ptr.name); + break; + + case DNS_TYPE_HINFO: + free(rr->hinfo.cpu); + free(rr->hinfo.os); + break; + + case DNS_TYPE_TXT: + case DNS_TYPE_SPF: + dns_txt_item_free_all(rr->txt.items); + break; + + case DNS_TYPE_SOA: + free(rr->soa.mname); + free(rr->soa.rname); + break; + + case DNS_TYPE_MX: + free(rr->mx.exchange); + break; + + case DNS_TYPE_DS: + free(rr->ds.digest); + break; + + case DNS_TYPE_SSHFP: + free(rr->sshfp.fingerprint); + break; + + case DNS_TYPE_DNSKEY: + free(rr->dnskey.key); + break; + + case DNS_TYPE_RRSIG: + free(rr->rrsig.signer); + free(rr->rrsig.signature); + break; + + case DNS_TYPE_NSEC: + free(rr->nsec.next_domain_name); + bitmap_free(rr->nsec.types); + break; + + case DNS_TYPE_NSEC3: + free(rr->nsec3.next_hashed_name); + free(rr->nsec3.salt); + bitmap_free(rr->nsec3.types); + break; + + case DNS_TYPE_LOC: + case DNS_TYPE_A: + case DNS_TYPE_AAAA: + break; + + case DNS_TYPE_TLSA: + free(rr->tlsa.data); + break; + + case DNS_TYPE_OPENPGPKEY: + default: + free(rr->generic.data); + } + + free(rr->wire_format); + dns_resource_key_unref(rr->key); + } + + free(rr->to_string); + free(rr); + + return NULL; +} + +int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *hostname) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ char *ptr = NULL; + int r; + + assert(ret); + assert(address); + assert(hostname); + + r = dns_name_reverse(family, address, &ptr); + if (r < 0) + return r; + + key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, ptr); + if (!key) + return -ENOMEM; + + ptr = NULL; + + rr = dns_resource_record_new(key); + if (!rr) + return -ENOMEM; + + rr->ptr.name = strdup(hostname); + if (!rr->ptr.name) + return -ENOMEM; + + *ret = rr; + rr = NULL; + + return 0; +} + +int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name) { + DnsResourceRecord *rr; + + assert(ret); + assert(address); + assert(family); + + if (family == AF_INET) { + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, name); + if (!rr) + return -ENOMEM; + + rr->a.in_addr = address->in; + + } else if (family == AF_INET6) { + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, name); + if (!rr) + return -ENOMEM; + + rr->aaaa.in6_addr = address->in6; + } else + return -EAFNOSUPPORT; + + *ret = rr; + + 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; + + assert(a); + assert(b); + + if (a == b) + return 1; + + r = dns_resource_key_equal(a->key, b->key); + if (r <= 0) + return r; + + if (a->unparseable != b->unparseable) + return 0; + + switch (a->unparseable ? _DNS_TYPE_INVALID : a->key->type) { + + case DNS_TYPE_SRV: + r = dns_name_equal(a->srv.name, b->srv.name); + if (r <= 0) + return r; + + return a->srv.priority == b->srv.priority && + a->srv.weight == b->srv.weight && + a->srv.port == b->srv.port; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + return dns_name_equal(a->ptr.name, b->ptr.name); + + case DNS_TYPE_HINFO: + return strcaseeq(a->hinfo.cpu, b->hinfo.cpu) && + strcaseeq(a->hinfo.os, b->hinfo.os); + + case DNS_TYPE_SPF: /* exactly the same as TXT */ + case DNS_TYPE_TXT: + return dns_txt_item_equal(a->txt.items, b->txt.items); + + case DNS_TYPE_A: + return memcmp(&a->a.in_addr, &b->a.in_addr, sizeof(struct in_addr)) == 0; + + case DNS_TYPE_AAAA: + return memcmp(&a->aaaa.in6_addr, &b->aaaa.in6_addr, sizeof(struct in6_addr)) == 0; + + case DNS_TYPE_SOA: + r = dns_name_equal(a->soa.mname, b->soa.mname); + if (r <= 0) + return r; + r = dns_name_equal(a->soa.rname, b->soa.rname); + if (r <= 0) + return r; + + return a->soa.serial == b->soa.serial && + a->soa.refresh == b->soa.refresh && + a->soa.retry == b->soa.retry && + a->soa.expire == b->soa.expire && + a->soa.minimum == b->soa.minimum; + + case DNS_TYPE_MX: + if (a->mx.priority != b->mx.priority) + return 0; + + return dns_name_equal(a->mx.exchange, b->mx.exchange); + + case DNS_TYPE_LOC: + assert(a->loc.version == b->loc.version); + + return a->loc.size == b->loc.size && + a->loc.horiz_pre == b->loc.horiz_pre && + a->loc.vert_pre == b->loc.vert_pre && + a->loc.latitude == b->loc.latitude && + a->loc.longitude == b->loc.longitude && + a->loc.altitude == b->loc.altitude; + + case DNS_TYPE_DS: + return a->ds.key_tag == b->ds.key_tag && + a->ds.algorithm == b->ds.algorithm && + a->ds.digest_type == b->ds.digest_type && + FIELD_EQUAL(a->ds, b->ds, digest); + + case DNS_TYPE_SSHFP: + return a->sshfp.algorithm == b->sshfp.algorithm && + a->sshfp.fptype == b->sshfp.fptype && + 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 && + FIELD_EQUAL(a->dnskey, b->dnskey, key); + + case DNS_TYPE_RRSIG: + /* do the fast comparisons first */ + 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) && + bitmap_equal(a->nsec.types, b->nsec.types); + + case DNS_TYPE_NSEC3: + return a->nsec3.algorithm == b->nsec3.algorithm && + 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 FIELD_EQUAL(a->generic, b->generic, data); + } +} + +static char* format_location(uint32_t latitude, uint32_t longitude, uint32_t altitude, + uint8_t size, uint8_t horiz_pre, uint8_t vert_pre) { + char *s; + char NS = latitude >= 1U<<31 ? 'N' : 'S'; + char EW = longitude >= 1U<<31 ? 'E' : 'W'; + + int lat = latitude >= 1U<<31 ? (int) (latitude - (1U<<31)) : (int) ((1U<<31) - latitude); + int lon = longitude >= 1U<<31 ? (int) (longitude - (1U<<31)) : (int) ((1U<<31) - longitude); + double alt = altitude >= 10000000u ? altitude - 10000000u : -(double)(10000000u - altitude); + double siz = (size >> 4) * exp10((double) (size & 0xF)); + double hor = (horiz_pre >> 4) * exp10((double) (horiz_pre & 0xF)); + double ver = (vert_pre >> 4) * exp10((double) (vert_pre & 0xF)); + + if (asprintf(&s, "%d %d %.3f %c %d %d %.3f %c %.2fm %.2fm %.2fm %.2fm", + (lat / 60000 / 60), + (lat / 60000) % 60, + (lat % 60000) / 1000., + NS, + (lon / 60000 / 60), + (lon / 60000) % 60, + (lon % 60000) / 1000., + EW, + alt / 100., + siz / 100., + hor / 100., + ver / 100.) < 0) + return NULL; + + return s; +} + +static int format_timestamp_dns(char *buf, size_t l, time_t sec) { + struct tm tm; + + assert(buf); + assert(l > strlen("YYYYMMDDHHmmSS")); + + if (!gmtime_r(&sec, &tm)) + return -EINVAL; + + if (strftime(buf, l, "%Y%m%d%H%M%S", &tm) <= 0) + return -EINVAL; + + return 0; +} + +static char *format_types(Bitmap *types) { + _cleanup_strv_free_ char **strv = NULL; + _cleanup_free_ char *str = NULL; + Iterator i; + unsigned type; + int r; + + BITMAP_FOREACH(type, types, i) { + if (dns_type_to_string(type)) { + r = strv_extend(&strv, dns_type_to_string(type)); + if (r < 0) + return NULL; + } else { + char *t; + + r = asprintf(&t, "TYPE%u", type); + if (r < 0) + return NULL; + + r = strv_consume(&strv, t); + if (r < 0) + return NULL; + } + } + + str = strv_join(strv, " "); + if (!str) + return NULL; + + return strjoin("( ", str, " )", NULL); +} + +static char *format_txt(DnsTxtItem *first) { + DnsTxtItem *i; + size_t c = 1; + char *p, *s; + + LIST_FOREACH(items, i, first) + c += i->length * 4 + 3; + + p = s = new(char, c); + if (!s) + return NULL; + + LIST_FOREACH(items, i, first) { + size_t j; + + if (i != first) + *(p++) = ' '; + + *(p++) = '"'; + + for (j = 0; j < i->length; j++) { + if (i->data[j] < ' ' || i->data[j] == '"' || i->data[j] >= 127) { + *(p++) = '\\'; + *(p++) = '0' + (i->data[j] / 100); + *(p++) = '0' + ((i->data[j] / 10) % 10); + *(p++) = '0' + (i->data[j] % 10); + } else + *(p++) = i->data[j]; + } + + *(p++) = '"'; + } + + *p = 0; + return s; +} + +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 NULL; + + switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { + + case DNS_TYPE_SRV: + r = asprintf(&s, "%s %u %u %u %s", + k, + rr->srv.priority, + rr->srv.weight, + rr->srv.port, + strna(rr->srv.name)); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + s = strjoin(k, " ", rr->ptr.name, NULL); + if (!s) + return NULL; + + break; + + case DNS_TYPE_HINFO: + s = strjoin(k, " ", rr->hinfo.cpu, " ", rr->hinfo.os, NULL); + if (!s) + 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 NULL; + + s = strjoin(k, " ", t, NULL); + if (!s) + return NULL; + break; + + case DNS_TYPE_A: { + _cleanup_free_ char *x = NULL; + + r = in_addr_to_string(AF_INET, (const union in_addr_union*) &rr->a.in_addr, &x); + if (r < 0) + return NULL; + + s = strjoin(k, " ", x, NULL); + if (!s) + 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 NULL; + + s = strjoin(k, " ", t, NULL); + if (!s) + return NULL; + break; + + case DNS_TYPE_SOA: + r = asprintf(&s, "%s %s %s %u %u %u %u %u", + k, + strna(rr->soa.mname), + strna(rr->soa.rname), + rr->soa.serial, + rr->soa.refresh, + rr->soa.retry, + rr->soa.expire, + rr->soa.minimum); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_MX: + r = asprintf(&s, "%s %u %s", + k, + rr->mx.priority, + rr->mx.exchange); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_LOC: + assert(rr->loc.version == 0); + + t = format_location(rr->loc.latitude, + rr->loc.longitude, + rr->loc.altitude, + rr->loc.size, + rr->loc.horiz_pre, + rr->loc.vert_pre); + if (!t) + return NULL; + + s = strjoin(k, " ", t, NULL); + if (!s) + return NULL; + break; + + case DNS_TYPE_DS: + t = hexmem(rr->ds.digest, rr->ds.digest_size); + if (!t) + return NULL; + + r = asprintf(&s, "%s %u %u %u %s", + k, + rr->ds.key_tag, + rr->ds.algorithm, + rr->ds.digest_type, + t); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_SSHFP: + t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); + if (!t) + return NULL; + + r = asprintf(&s, "%s %u %u %s", + k, + rr->sshfp.algorithm, + rr->sshfp.fptype, + t); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_DNSKEY: { + _cleanup_free_ char *alg = NULL; + char *ss; + int n, n1; + uint16_t key_tag; + + key_tag = dnssec_keytag(rr, true); + + r = dnssec_algorithm_to_string_alloc(rr->dnskey.algorithm, &alg); + if (r < 0) + return NULL; + + r = asprintf(&s, "%s %n%u %u %s %n", + k, + &n1, + rr->dnskey.flags, + rr->dnskey.protocol, + alg, + &n); + if (r < 0) + 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: { + _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); + + 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 NULL; + + r = format_timestamp_dns(inception, sizeof(inception), rr->rrsig.inception); + if (r < 0) + return NULL; + + /* TYPE?? follows + * http://tools.ietf.org/html/rfc3597#section-5 */ + + 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, + rr->rrsig.labels, + rr->rrsig.original_ttl, + expiration, + inception, + rr->rrsig.key_tag, + rr->rrsig.signer, + &n); + if (r < 0) + 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 NULL; + + r = asprintf(&s, "%s %s %s", + k, + rr->nsec.next_domain_name, + t); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_NSEC3: { + _cleanup_free_ char *salt = NULL, *hash = NULL; + + if (rr->nsec3.salt_size > 0) { + salt = hexmem(rr->nsec3.salt, rr->nsec3.salt_size); + if (!salt) + return NULL; + } + + hash = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false); + if (!hash) + return NULL; + + t = format_types(rr->nsec3.types); + if (!t) + return NULL; + + r = asprintf(&s, "%s %"PRIu8" %"PRIu8" %"PRIu16" %s %s %s", + k, + rr->nsec3.algorithm, + rr->nsec3.flags, + rr->nsec3.iterations, + rr->nsec3.salt_size > 0 ? salt : "-", + hash, + t); + if (r < 0) + 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.data_size); + if (!t) + return NULL; + + /* Format as documented in RFC 3597, Section 5 */ + r = asprintf(&s, "%s \\# %zu %s", k, rr->generic.data_size, t); + if (r < 0) + return NULL; + break; + } + + rr->to_string = s; + return s; +} + +int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) { + + DnsPacket packet = { + .n_ref = 1, + .protocol = DNS_PROTOCOL_DNS, + .on_stack = true, + .refuse_compression = true, + .canonical_form = canonical, + }; + + size_t start, rds; + int r; + + assert(rr); + + /* Generates the RR in wire-format, optionally in the + * canonical form as discussed in the DNSSEC RFC 4034, Section + * 6.2. We allocate a throw-away DnsPacket object on the stack + * here, because we need some book-keeping for memory + * management, and can reuse the DnsPacket serializer, that + * can generate the canonical form, too, but also knows label + * compression and suchlike. */ + + if (rr->wire_format && rr->wire_format_canonical == canonical) + return 0; + + r = dns_packet_append_rr(&packet, rr, &start, &rds); + if (r < 0) + return r; + + assert(start == 0); + assert(packet._data); + + free(rr->wire_format); + rr->wire_format = packet._data; + rr->wire_format_size = packet.size; + rr->wire_format_rdata_offset = rds; + rr->wire_format_canonical = canonical; + + packet._data = NULL; + dns_packet_unref(&packet); + + return 0; +} + +int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret) { + const char *n; + int r; + + assert(rr); + assert(ret); + + /* Returns the RRset's signer, if it is known. */ + + if (rr->n_skip_labels_signer == (unsigned) -1) + return -ENODATA; + + n = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_skip(n, rr->n_skip_labels_signer, &n); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + *ret = n; + return 0; +} + +int dns_resource_record_source(DnsResourceRecord *rr, const char **ret) { + const char *n; + int r; + + assert(rr); + assert(ret); + + /* Returns the RRset's synthesizing source, if it is known. */ + + if (rr->n_skip_labels_source == (unsigned) -1) + return -ENODATA; + + n = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_skip(n, rr->n_skip_labels_source, &n); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + *ret = n; + return 0; +} + +int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone) { + const char *signer; + int r; + + assert(rr); + + r = dns_resource_record_signer(rr, &signer); + if (r < 0) + return r; + + return dns_name_equal(zone, signer); +} + +int dns_resource_record_is_synthetic(DnsResourceRecord *rr) { + int r; + + assert(rr); + + /* Returns > 0 if the RR is generated from a wildcard, and is not the asterisk name itself */ + + if (rr->n_skip_labels_source == (unsigned) -1) + return -ENODATA; + + if (rr->n_skip_labels_source == 0) + return 0; + + if (rr->n_skip_labels_source > 1) + return 1; + + r = dns_name_startswith(DNS_RESOURCE_KEY_NAME(rr->key), "*"); + if (r < 0) + return r; + + return !r; +} + +static void dns_resource_record_hash_func(const void *i, struct siphash *state) { + const DnsResourceRecord *rr = i; + + assert(rr); + + dns_resource_key_hash_func(rr->key, state); + + switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { + + case DNS_TYPE_SRV: + siphash24_compress(&rr->srv.priority, sizeof(rr->srv.priority), state); + siphash24_compress(&rr->srv.weight, sizeof(rr->srv.weight), state); + siphash24_compress(&rr->srv.port, sizeof(rr->srv.port), state); + dns_name_hash_func(rr->srv.name, state); + break; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + dns_name_hash_func(rr->ptr.name, state); + break; + + case DNS_TYPE_HINFO: + string_hash_func(rr->hinfo.cpu, state); + string_hash_func(rr->hinfo.os, state); + break; + + case DNS_TYPE_TXT: + case DNS_TYPE_SPF: { + DnsTxtItem *j; + + LIST_FOREACH(items, j, rr->txt.items) { + siphash24_compress(j->data, j->length, state); + + /* Add an extra NUL byte, so that "a" followed by "b" doesn't result in the same hash as "ab" + * followed by "". */ + siphash24_compress_byte(0, state); + } + break; + } + + case DNS_TYPE_A: + siphash24_compress(&rr->a.in_addr, sizeof(rr->a.in_addr), state); + break; + + case DNS_TYPE_AAAA: + siphash24_compress(&rr->aaaa.in6_addr, sizeof(rr->aaaa.in6_addr), state); + break; + + case DNS_TYPE_SOA: + dns_name_hash_func(rr->soa.mname, state); + dns_name_hash_func(rr->soa.rname, state); + siphash24_compress(&rr->soa.serial, sizeof(rr->soa.serial), state); + siphash24_compress(&rr->soa.refresh, sizeof(rr->soa.refresh), state); + siphash24_compress(&rr->soa.retry, sizeof(rr->soa.retry), state); + siphash24_compress(&rr->soa.expire, sizeof(rr->soa.expire), state); + siphash24_compress(&rr->soa.minimum, sizeof(rr->soa.minimum), state); + break; + + case DNS_TYPE_MX: + siphash24_compress(&rr->mx.priority, sizeof(rr->mx.priority), state); + dns_name_hash_func(rr->mx.exchange, state); + break; + + case DNS_TYPE_LOC: + siphash24_compress(&rr->loc.version, sizeof(rr->loc.version), state); + siphash24_compress(&rr->loc.size, sizeof(rr->loc.size), state); + siphash24_compress(&rr->loc.horiz_pre, sizeof(rr->loc.horiz_pre), state); + siphash24_compress(&rr->loc.vert_pre, sizeof(rr->loc.vert_pre), state); + siphash24_compress(&rr->loc.latitude, sizeof(rr->loc.latitude), state); + siphash24_compress(&rr->loc.longitude, sizeof(rr->loc.longitude), state); + siphash24_compress(&rr->loc.altitude, sizeof(rr->loc.altitude), state); + break; + + case DNS_TYPE_SSHFP: + siphash24_compress(&rr->sshfp.algorithm, sizeof(rr->sshfp.algorithm), state); + siphash24_compress(&rr->sshfp.fptype, sizeof(rr->sshfp.fptype), state); + siphash24_compress(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, state); + break; + + case DNS_TYPE_DNSKEY: + siphash24_compress(&rr->dnskey.flags, sizeof(rr->dnskey.flags), state); + siphash24_compress(&rr->dnskey.protocol, sizeof(rr->dnskey.protocol), state); + siphash24_compress(&rr->dnskey.algorithm, sizeof(rr->dnskey.algorithm), state); + siphash24_compress(rr->dnskey.key, rr->dnskey.key_size, state); + break; + + case DNS_TYPE_RRSIG: + siphash24_compress(&rr->rrsig.type_covered, sizeof(rr->rrsig.type_covered), state); + siphash24_compress(&rr->rrsig.algorithm, sizeof(rr->rrsig.algorithm), state); + siphash24_compress(&rr->rrsig.labels, sizeof(rr->rrsig.labels), state); + siphash24_compress(&rr->rrsig.original_ttl, sizeof(rr->rrsig.original_ttl), state); + siphash24_compress(&rr->rrsig.expiration, sizeof(rr->rrsig.expiration), state); + siphash24_compress(&rr->rrsig.inception, sizeof(rr->rrsig.inception), state); + siphash24_compress(&rr->rrsig.key_tag, sizeof(rr->rrsig.key_tag), state); + dns_name_hash_func(rr->rrsig.signer, state); + siphash24_compress(rr->rrsig.signature, rr->rrsig.signature_size, state); + break; + + case DNS_TYPE_NSEC: + dns_name_hash_func(rr->nsec.next_domain_name, state); + /* FIXME: we leave out the type bitmap here. Hash + * would be better if we'd take it into account + * too. */ + break; + + case DNS_TYPE_DS: + siphash24_compress(&rr->ds.key_tag, sizeof(rr->ds.key_tag), state); + siphash24_compress(&rr->ds.algorithm, sizeof(rr->ds.algorithm), state); + siphash24_compress(&rr->ds.digest_type, sizeof(rr->ds.digest_type), state); + siphash24_compress(rr->ds.digest, rr->ds.digest_size, state); + break; + + case DNS_TYPE_NSEC3: + siphash24_compress(&rr->nsec3.algorithm, sizeof(rr->nsec3.algorithm), state); + siphash24_compress(&rr->nsec3.flags, sizeof(rr->nsec3.flags), state); + siphash24_compress(&rr->nsec3.iterations, sizeof(rr->nsec3.iterations), state); + siphash24_compress(rr->nsec3.salt, rr->nsec3.salt_size, state); + siphash24_compress(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, state); + /* FIXME: We leave the bitmaps out */ + break; + + 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 ordering, 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; + + if (!i) + return NULL; + + n = i->items_next; + + free(i); + return dns_txt_item_free_all(n); +} + +bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) { + + if (a == b) + return true; + + if (!a != !b) + return false; + + if (!a) + return true; + + if (a->length != b->length) + return false; + + if (memcmp(a->data, b->data, a->length) != 0) + return false; + + return dns_txt_item_equal(a->items_next, b->items_next); +} + +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", + [DNSSEC_ALGORITHM_ECC] = "ECC", + [DNSSEC_ALGORITHM_RSASHA1] = "RSASHA1", + [DNSSEC_ALGORITHM_DSA_NSEC3_SHA1] = "DSA-NSEC3-SHA1", + [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_WITH_FALLBACK(dnssec_algorithm, int, 255); + +static const char* const dnssec_digest_table[_DNSSEC_DIGEST_MAX_DEFINED] = { + /* 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_WITH_FALLBACK(dnssec_digest, int, 255); diff --git a/src/resolve/src/resolved-dns-rr.h b/src/resolve/src/resolved-dns-rr.h new file mode 100644 index 0000000000..2e0dfbaba3 --- /dev/null +++ b/src/resolve/src/resolved-dns-rr.h @@ -0,0 +1,333 @@ +#pragma once + +/*** + 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 . + ***/ + +#include + +#include "bitmap.h" +#include "dns-type.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "list.h" + +typedef struct DnsResourceKey DnsResourceKey; +typedef struct DnsResourceRecord DnsResourceRecord; +typedef struct DnsTxtItem DnsTxtItem; + +/* DNSKEY RR flags */ +#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) + +/* DNSSEC algorithm identifiers, see + * http://tools.ietf.org/html/rfc4034#appendix-A.1 and + * https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ +enum { + DNSSEC_ALGORITHM_RSAMD5 = 1, + DNSSEC_ALGORITHM_DH, + DNSSEC_ALGORITHM_DSA, + DNSSEC_ALGORITHM_ECC, + 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_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, + _DNSSEC_ALGORITHM_MAX_DEFINED +}; + +/* DNSSEC digest identifiers, see + * https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ +enum { + DNSSEC_DIGEST_SHA1 = 1, + 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 -1) for const keys, see below */ + uint16_t class, type; + char *_name; /* don't access directy, use DNS_RESOURCE_KEY_NAME()! */ +}; + +/* Creates a temporary resource key. This is only useful to quickly + * look up something, without allocating a full DnsResourceKey object + * for it. Note that it is not OK to take references to this kind of + * resource key object. */ +#define DNS_RESOURCE_KEY_CONST(c, t, n) \ + ((DnsResourceKey) { \ + .n_ref = (unsigned) -1, \ + .class = c, \ + .type = t, \ + ._name = (char*) n, \ + }) + + +struct DnsTxtItem { + size_t length; + LIST_FIELDS(DnsTxtItem, items); + uint8_t data[]; +}; + +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 data_size; + } generic, opt; + + struct { + uint16_t priority; + uint16_t weight; + uint16_t port; + char *name; + } srv; + + struct { + char *name; + } ptr, ns, cname, dname; + + struct { + char *cpu; + char *os; + } hinfo; + + struct { + DnsTxtItem *items; + } txt, spf; + + struct { + struct in_addr in_addr; + } a; + + struct { + struct in6_addr in6_addr; + } aaaa; + + struct { + char *mname; + char *rname; + uint32_t serial; + uint32_t refresh; + uint32_t retry; + uint32_t expire; + uint32_t minimum; + } soa; + + struct { + uint16_t priority; + char *exchange; + } mx; + + /* https://tools.ietf.org/html/rfc1876 */ + struct { + uint8_t version; + uint8_t size; + uint8_t horiz_pre; + uint8_t vert_pre; + uint32_t latitude; + uint32_t longitude; + uint32_t altitude; + } loc; + + /* https://tools.ietf.org/html/rfc4255#section-3.1 */ + struct { + uint8_t algorithm; + uint8_t fptype; + void *fingerprint; + size_t fingerprint_size; + } sshfp; + + /* http://tools.ietf.org/html/rfc4034#section-2.1 */ + struct { + uint16_t flags; + uint8_t protocol; + uint8_t algorithm; + void* key; + size_t key_size; + } dnskey; + + /* http://tools.ietf.org/html/rfc4034#section-3.1 */ + struct { + uint16_t type_covered; + uint8_t algorithm; + uint8_t labels; + uint32_t original_ttl; + uint32_t expiration; + uint32_t inception; + uint16_t key_tag; + char *signer; + void *signature; + size_t signature_size; + } rrsig; + + /* https://tools.ietf.org/html/rfc4034#section-4.1 */ + struct { + char *next_domain_name; + 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; + uint16_t iterations; + void *salt; + size_t salt_size; + void *next_hashed_name; + 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 (!key) + return NULL; + + if (key->_name) + return key->_name; + + 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); +DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name); +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, 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); +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceKey*, dns_resource_key_unref); + +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); +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); +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); + +extern const struct hash_ops dns_resource_key_hash_ops; +extern const struct hash_ops dns_resource_record_hash_ops; + +int dnssec_algorithm_to_string_alloc(int i, char **ret); +int dnssec_algorithm_from_string(const char *s) _pure_; + +int dnssec_digest_to_string_alloc(int i, char **ret); +int dnssec_digest_from_string(const char *s) _pure_; diff --git a/src/resolve/src/resolved-dns-scope.c b/src/resolve/src/resolved-dns-scope.c new file mode 100644 index 0000000000..a406872a38 --- /dev/null +++ b/src/resolve/src/resolved-dns-scope.c @@ -0,0 +1,1028 @@ +/*** + 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 . +***/ + +#include + +#include "af-list.h" +#include "alloc-util.h" +#include "dns-domain.h" +#include "fd-util.h" +#include "hostname-util.h" +#include "missing.h" +#include "random-util.h" +#include "resolved-dns-scope.h" +#include "resolved-llmnr.h" +#include "resolved-mdns.h" +#include "socket-util.h" +#include "strv.h" + +#define MULTICAST_RATELIMIT_INTERVAL_USEC (1*USEC_PER_SEC) +#define MULTICAST_RATELIMIT_BURST 1000 + +/* After how much time to repeat LLMNR requests, see RFC 4795 Section 7 */ +#define MULTICAST_RESEND_TIMEOUT_MIN_USEC (100 * USEC_PER_MSEC) +#define MULTICAST_RESEND_TIMEOUT_MAX_USEC (1 * USEC_PER_SEC) + +int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int family) { + DnsScope *s; + + assert(m); + assert(ret); + + s = new0(DnsScope, 1); + if (!s) + return -ENOMEM; + + s->manager = m; + s->link = l; + s->protocol = protocol; + 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); + dns_scope_mdns_membership(s, true); + + log_debug("New scope on link %s, protocol %s, family %s", l ? l->name : "*", dns_protocol_to_string(protocol), family == AF_UNSPEC ? "*" : af_to_name(family)); + + /* Enforce ratelimiting for the multicast protocols */ + RATELIMIT_INIT(s->ratelimit, MULTICAST_RATELIMIT_INTERVAL_USEC, MULTICAST_RATELIMIT_BURST); + + *ret = s; + return 0; +} + +static void dns_scope_abort_transactions(DnsScope *s) { + assert(s); + + while (s->transactions) { + DnsTransaction *t = s->transactions; + + /* Abort the transaction, but make sure it is not + * freed while we still look at it */ + + t->block_gc++; + if (DNS_TRANSACTION_IS_LIVE(t->state)) + dns_transaction_complete(t, DNS_TRANSACTION_ABORTED); + t->block_gc--; + + dns_transaction_free(t); + } +} + +DnsScope* dns_scope_free(DnsScope *s) { + DnsResourceRecord *rr; + + if (!s) + return NULL; + + log_debug("Removing scope on link %s, protocol %s, family %s", s->link ? s->link->name : "*", dns_protocol_to_string(s->protocol), s->family == AF_UNSPEC ? "*" : af_to_name(s->family)); + + dns_scope_llmnr_membership(s, false); + dns_scope_mdns_membership(s, false); + dns_scope_abort_transactions(s); + + while (s->query_candidates) + dns_query_candidate_free(s->query_candidates); + + hashmap_free(s->transactions_by_key); + + while ((rr = ordered_hashmap_steal_first(s->conflict_queue))) + dns_resource_record_unref(rr); + + ordered_hashmap_free(s->conflict_queue); + sd_event_source_unref(s->conflict_event_source); + + dns_cache_flush(&s->cache); + dns_zone_flush(&s->zone); + + LIST_REMOVE(scopes, s->manager->dns_scopes, s); + free(s); + + return NULL; +} + +DnsServer *dns_scope_get_dns_server(DnsScope *s) { + assert(s); + + if (s->protocol != DNS_PROTOCOL_DNS) + return NULL; + + if (s->link) + return link_get_dns_server(s->link); + else + return manager_get_dns_server(s->manager); +} + +void dns_scope_next_dns_server(DnsScope *s) { + assert(s); + + if (s->protocol != DNS_PROTOCOL_DNS) + return; + + if (s->link) + link_next_dns_server(s->link); + else + manager_next_dns_server(s->manager); +} + +void dns_scope_packet_received(DnsScope *s, usec_t rtt) { + assert(s); + + if (rtt <= s->max_rtt) + return; + + s->max_rtt = rtt; + s->resend_timeout = MIN(MAX(MULTICAST_RESEND_TIMEOUT_MIN_USEC, s->max_rtt * 2), MULTICAST_RESEND_TIMEOUT_MAX_USEC); +} + +void dns_scope_packet_lost(DnsScope *s, usec_t usec) { + assert(s); + + if (s->resend_timeout <= usec) + s->resend_timeout = MIN(s->resend_timeout * 2, MULTICAST_RESEND_TIMEOUT_MAX_USEC); +} + +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; + + assert(s); + assert(p); + assert(p->protocol == s->protocol); + + if (s->link) { + mtu = s->link->mtu; + ifindex = s->link->ifindex; + } else + mtu = manager_find_mtu(s->manager); + + switch (s->protocol) { + + case DNS_PROTOCOL_DNS: + assert(fd >= 0); + + if (DNS_PACKET_QDCOUNT(p) > 1) + return -EOPNOTSUPP; + + if (p->size > DNS_PACKET_UNICAST_SIZE_MAX) + return -EMSGSIZE; + + if (p->size + UDP_PACKET_HEADER_SIZE > mtu) + return -EMSGSIZE; + + r = manager_write(s->manager, fd, p); + if (r < 0) + return r; + + break; + + case DNS_PROTOCOL_LLMNR: + assert(fd < 0); + + if (DNS_PACKET_QDCOUNT(p) > 1) + return -EOPNOTSUPP; + + if (!ratelimit_test(&s->ratelimit)) + return -EBUSY; + + family = s->family; + + if (family == AF_INET) { + addr.in = LLMNR_MULTICAST_IPV4_ADDRESS; + fd = manager_llmnr_ipv4_udp_fd(s->manager); + } else if (family == AF_INET6) { + addr.in6 = LLMNR_MULTICAST_IPV6_ADDRESS; + fd = manager_llmnr_ipv6_udp_fd(s->manager); + } else + return -EAFNOSUPPORT; + if (fd < 0) + return fd; + + r = manager_send(s->manager, fd, ifindex, family, &addr, LLMNR_PORT, p); + if (r < 0) + return r; + + break; + + case DNS_PROTOCOL_MDNS: + assert(fd < 0); + + if (!ratelimit_test(&s->ratelimit)) + return -EBUSY; + + family = s->family; + + if (family == AF_INET) { + addr.in = MDNS_MULTICAST_IPV4_ADDRESS; + fd = manager_mdns_ipv4_fd(s->manager); + } else if (family == AF_INET6) { + addr.in6 = MDNS_MULTICAST_IPV6_ADDRESS; + fd = manager_mdns_ipv6_fd(s->manager); + } else + return -EAFNOSUPPORT; + if (fd < 0) + return fd; + + r = manager_send(s->manager, fd, ifindex, family, &addr, MDNS_PORT, p); + if (r < 0) + return r; + + break; + + default: + return -EAFNOSUPPORT; + } + + return 1; +} + +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)); + + do { + /* If there are multiple linked packets, set the TC bit in all but the last of them */ + if (p->more) { + assert(p->protocol == DNS_PROTOCOL_MDNS); + dns_packet_set_flags(p, true, true); + } + + r = dns_scope_emit_one(s, fd, p); + if (r < 0) + return r; + + p = p->more; + } while (p); + + return 0; +} + +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; + static const int one = 1; + int ret, r; + + assert(s); + + if (server) { + assert(family == AF_UNSPEC); + assert(!address); + + sa.sa.sa_family = server->family; + if (server->family == AF_INET) { + sa.in.sin_port = htobe16(port); + sa.in.sin_addr = server->address.in; + salen = sizeof(sa.in); + } else if (server->family == AF_INET6) { + sa.in6.sin6_port = htobe16(port); + 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) { + sa.in.sin_port = htobe16(port); + sa.in.sin_addr = address->in; + salen = sizeof(sa.in); + } else if (family == AF_INET6) { + sa.in6.sin6_port = htobe16(port); + sa.in6.sin6_addr = address->in6; + sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0; + salen = sizeof(sa.in6); + } else + return -EAFNOSUPPORT; + } + + fd = socket(sa.sa.sa_family, type|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) + return -errno; + + if (type == SOCK_STREAM) { + r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); + if (r < 0) + return -errno; + } + + if (s->link) { + uint32_t ifindex = htobe32(s->link->ifindex); + + if (sa.sa.sa_family == AF_INET) { + r = setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex)); + if (r < 0) + return -errno; + } else if (sa.sa.sa_family == AF_INET6) { + r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex)); + if (r < 0) + return -errno; + } + } + + if (s->protocol == DNS_PROTOCOL_LLMNR) { + /* RFC 4795, section 2.5 requires the TTL to be set to 1 */ + + if (sa.sa.sa_family == AF_INET) { + r = setsockopt(fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); + if (r < 0) + return -errno; + } else if (sa.sa.sa_family == AF_INET6) { + r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one)); + if (r < 0) + return -errno; + } + } + + r = connect(fd, &sa.sa, salen); + if (r < 0 && errno != EINPROGRESS) + return -errno; + + ret = fd; + fd = -1; + + return ret; +} + +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_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) { + DnsSearchDomain *d; + + assert(s); + assert(domain); + + /* Checks if the specified domain is something to look up on + * this scope. Note that this accepts non-qualified hostnames, + * i.e. those without any search path prefixed yet. */ + + if (ifindex != 0 && (!s->link || s->link->ifindex != ifindex)) + return DNS_SCOPE_NO; + + if ((SD_RESOLVED_FLAGS_MAKE(s->protocol, s->family, 0) & flags) == 0) + return DNS_SCOPE_NO; + + /* Never resolve any loopback hostname or IP address via DNS, + * LLMNR or mDNS. Instead, always rely on synthesized RRs for + * these. */ + if (is_localhost(domain) || + dns_name_endswith(domain, "127.in-addr.arpa") > 0 || + dns_name_equal(domain, "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 DNS_SCOPE_NO; + + /* Never respond to some of the domains listed in RFC6303 */ + if (dns_name_endswith(domain, "0.in-addr.arpa") > 0 || + dns_name_equal(domain, "255.255.255.255.in-addr.arpa") > 0 || + dns_name_equal(domain, "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.0.ip6.arpa") > 0) + return DNS_SCOPE_NO; + + /* Never respond to some of the domains listed in RFC6761 */ + if (dns_name_endswith(domain, "invalid") > 0) + return DNS_SCOPE_NO; + + /* Always honour search domains for routing queries. Note that + * we return DNS_SCOPE_YES here, rather than just + * DNS_SCOPE_MAYBE, which means wildcard scopes won't be + * considered anymore. */ + LIST_FOREACH(domains, d, dns_scope_get_search_domains(s)) + if (dns_name_endswith(domain, d->name) > 0) + return DNS_SCOPE_YES; + + switch (s->protocol) { + + case DNS_PROTOCOL_DNS: + + /* Exclude link-local IP ranges */ + if (dns_name_endswith(domain, "254.169.in-addr.arpa") == 0 && + dns_name_endswith(domain, "8.e.f.ip6.arpa") == 0 && + dns_name_endswith(domain, "9.e.f.ip6.arpa") == 0 && + dns_name_endswith(domain, "a.e.f.ip6.arpa") == 0 && + dns_name_endswith(domain, "b.e.f.ip6.arpa") == 0 && + /* If networks use .local in their private setups, they are supposed to also add .local to their search + * domains, which we already checked above. Otherwise, we consider .local specific to mDNS and won't + * send such queries ordinary DNS servers. */ + dns_name_endswith(domain, "local") == 0) + return DNS_SCOPE_MAYBE; + + return DNS_SCOPE_NO; + + case DNS_PROTOCOL_MDNS: + if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || + (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || + (dns_name_endswith(domain, "local") > 0 && /* only resolve names ending in .local via mDNS */ + dns_name_equal(domain, "local") == 0 && /* but not the single-label "local" name itself */ + manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via mDNS */ + return DNS_SCOPE_MAYBE; + + return DNS_SCOPE_NO; + + case DNS_PROTOCOL_LLMNR: + if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || + (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || + (dns_name_is_single_label(domain) && /* only resolve single label names via LLMNR */ + !is_gateway_hostname(domain) && /* don't resolve "gateway" with LLMNR, let nss-myhostname handle this */ + manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via LLMNR */ + return DNS_SCOPE_MAYBE; + + return DNS_SCOPE_NO; + + default: + assert_not_reached("Unknown scope protocol"); + } +} + +bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key) { + int key_family; + + assert(s); + assert(key); + + /* Check if it makes sense to resolve the specified key on + * 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, 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.) */ + if (!dns_resource_key_is_address(key)) + return true; + + /* However, we refuse to look up A and AAAA RRs on the + * root and single-label domains, under the assumption + * that those should be resolved via LLMNR or search + * path only, and should not be leaked onto the + * internet. */ + return !(dns_name_is_single_label(DNS_RESOURCE_KEY_NAME(key)) || + dns_name_is_root(DNS_RESOURCE_KEY_NAME(key))); + } + + /* On mDNS and LLMNR, send A and AAAA queries only on the + * respective scopes */ + + key_family = dns_type_to_af(key->type); + if (key_family < 0) + 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) { + int fd; + + assert(s); + assert(s->link); + + if (s->family == AF_INET) { + struct ip_mreqn mreqn = { + .imr_multiaddr = in, + .imr_ifindex = s->link->ifindex, + }; + + fd = manager_llmnr_ipv4_udp_fd(s->manager); + if (fd < 0) + return fd; + + /* Always first try to drop membership before we add + * one. This is necessary on some devices, such as + * veth. */ + if (b) + (void) setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn)); + + if (setsockopt(fd, IPPROTO_IP, b ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn)) < 0) + return -errno; + + } else if (s->family == AF_INET6) { + struct ipv6_mreq mreq = { + .ipv6mr_multiaddr = in6, + .ipv6mr_interface = s->link->ifindex, + }; + + fd = manager_llmnr_ipv6_udp_fd(s->manager); + if (fd < 0) + return fd; + + if (b) + (void) setsockopt(fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); + + if (setsockopt(fd, IPPROTO_IPV6, b ? IPV6_ADD_MEMBERSHIP : IPV6_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) + return -errno; + } else + return -EAFNOSUPPORT; + + return 0; +} + +int dns_scope_llmnr_membership(DnsScope *s, bool b) { + + if (s->protocol != DNS_PROTOCOL_LLMNR) + return 0; + + return dns_scope_multicast_membership(s, b, LLMNR_MULTICAST_IPV4_ADDRESS, LLMNR_MULTICAST_IPV6_ADDRESS); +} + +int dns_scope_mdns_membership(DnsScope *s, bool b) { + + if (s->protocol != DNS_PROTOCOL_MDNS) + return 0; + + return dns_scope_multicast_membership(s, b, MDNS_MULTICAST_IPV4_ADDRESS, MDNS_MULTICAST_IPV6_ADDRESS); +} + +static int dns_scope_make_reply_packet( + DnsScope *s, + uint16_t id, + int rcode, + DnsQuestion *q, + DnsAnswer *answer, + DnsAnswer *soa, + bool tentative, + DnsPacket **ret) { + + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + unsigned i; + int r; + + assert(s); + assert(ret); + + if ((!q || q->n_keys <= 0) + && (!answer || answer->n_rrs <= 0) + && (!soa || soa->n_rrs <= 0)) + return -EINVAL; + + r = dns_packet_new(&p, s->protocol, 0); + if (r < 0) + return r; + + DNS_PACKET_HEADER(p)->id = id; + DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( + 1 /* qr */, + 0 /* opcode */, + 0 /* c */, + 0 /* tc */, + tentative, + 0 /* (ra) */, + 0 /* (ad) */, + 0 /* (cd) */, + rcode)); + + if (q) { + for (i = 0; i < q->n_keys; i++) { + r = dns_packet_append_key(p, q->keys[i], NULL); + if (r < 0) + return r; + } + + DNS_PACKET_HEADER(p)->qdcount = htobe16(q->n_keys); + } + + if (answer) { + for (i = 0; i < answer->n_rrs; i++) { + r = dns_packet_append_rr(p, answer->items[i].rr, NULL, NULL); + if (r < 0) + return r; + } + + DNS_PACKET_HEADER(p)->ancount = htobe16(answer->n_rrs); + } + + if (soa) { + for (i = 0; i < soa->n_rrs; i++) { + r = dns_packet_append_rr(p, soa->items[i].rr, NULL, NULL); + if (r < 0) + return r; + } + + DNS_PACKET_HEADER(p)->arcount = htobe16(soa->n_rrs); + } + + *ret = p; + p = NULL; + + return 0; +} + +static void dns_scope_verify_conflicts(DnsScope *s, DnsPacket *p) { + unsigned n; + + assert(s); + assert(p); + + if (p->question) + for (n = 0; n < p->question->n_keys; n++) + dns_zone_verify_conflicts(&s->zone, p->question->keys[n]); + if (p->answer) + for (n = 0; n < p->answer->n_rrs; n++) + dns_zone_verify_conflicts(&s->zone, p->answer->items[n].rr->key); +} + +void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) { + _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; + DnsResourceKey *key = NULL; + bool tentative = false; + int r, fd; + + assert(s); + assert(p); + + if (p->protocol != DNS_PROTOCOL_LLMNR) + return; + + if (p->ipproto == IPPROTO_UDP) { + /* Don't accept UDP queries directed to anything but + * the LLMNR multicast addresses. See RFC 4795, + * section 2.5. */ + + if (p->family == AF_INET && !in_addr_equal(AF_INET, &p->destination, (union in_addr_union*) &LLMNR_MULTICAST_IPV4_ADDRESS)) + return; + + if (p->family == AF_INET6 && !in_addr_equal(AF_INET6, &p->destination, (union in_addr_union*) &LLMNR_MULTICAST_IPV6_ADDRESS)) + return; + } + + r = dns_packet_extract(p); + if (r < 0) { + log_debug_errno(r, "Failed to extract resources from incoming packet: %m"); + return; + } + + if (DNS_PACKET_LLMNR_C(p)) { + /* Somebody notified us about a possible conflict */ + dns_scope_verify_conflicts(s, p); + return; + } + + assert(p->question->n_keys == 1); + key = p->question->keys[0]; + + r = dns_zone_lookup(&s->zone, key, &answer, &soa, &tentative); + if (r < 0) { + log_debug_errno(r, "Failed to lookup key: %m"); + return; + } + if (r == 0) + return; + + if (answer) + dns_answer_order_by_scope(answer, in_addr_is_link_local(p->family, &p->sender) > 0); + + r = dns_scope_make_reply_packet(s, DNS_PACKET_ID(p), DNS_RCODE_SUCCESS, p->question, answer, soa, tentative, &reply); + if (r < 0) { + log_debug_errno(r, "Failed to build reply packet: %m"); + return; + } + + if (stream) + r = dns_stream_write_packet(stream, reply); + else { + if (!ratelimit_test(&s->ratelimit)) + return; + + if (p->family == AF_INET) + fd = manager_llmnr_ipv4_udp_fd(s->manager); + else if (p->family == AF_INET6) + fd = manager_llmnr_ipv6_udp_fd(s->manager); + else { + log_debug("Unknown protocol"); + return; + } + if (fd < 0) { + log_debug_errno(fd, "Failed to get reply socket: %m"); + return; + } + + /* Note that we always immediately reply to all LLMNR + * requests, and do not wait any time, since we + * verified uniqueness for all records. Also see RFC + * 4795, Section 2.7 */ + + r = manager_send(s->manager, fd, p->ifindex, p->family, &p->sender, p->sender_port, reply); + } + + if (r < 0) { + log_debug_errno(r, "Failed to send reply packet: %m"); + return; + } +} + +DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok) { + DnsTransaction *t; + + assert(scope); + assert(key); + + /* Try to find an ongoing transaction that is a equal to the + * specified question */ + t = hashmap_get(scope->transactions_by_key, key); + if (!t) + return NULL; + + /* 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_RCODE_FAILURE) && + t->answer_source != DNS_TRANSACTION_NETWORK) + return NULL; + + return t; +} + +static int dns_scope_make_conflict_packet( + DnsScope *s, + DnsResourceRecord *rr, + DnsPacket **ret) { + + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + int r; + + assert(s); + assert(rr); + assert(ret); + + r = dns_packet_new(&p, s->protocol, 0); + if (r < 0) + return r; + + DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( + 0 /* qr */, + 0 /* opcode */, + 1 /* conflict */, + 0 /* tc */, + 0 /* t */, + 0 /* (ra) */, + 0 /* (ad) */, + 0 /* (cd) */, + 0)); + + /* For mDNS, the transaction ID should always be 0 */ + if (s->protocol != DNS_PROTOCOL_MDNS) + random_bytes(&DNS_PACKET_HEADER(p)->id, sizeof(uint16_t)); + + DNS_PACKET_HEADER(p)->qdcount = htobe16(1); + DNS_PACKET_HEADER(p)->arcount = htobe16(1); + + r = dns_packet_append_key(p, rr->key, NULL); + if (r < 0) + return r; + + r = dns_packet_append_rr(p, rr, NULL, NULL); + if (r < 0) + return r; + + *ret = p; + p = NULL; + + return 0; +} + +static int on_conflict_dispatch(sd_event_source *es, usec_t usec, void *userdata) { + DnsScope *scope = userdata; + int r; + + assert(es); + assert(scope); + + scope->conflict_event_source = sd_event_source_unref(scope->conflict_event_source); + + for (;;) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + + rr = ordered_hashmap_steal_first(scope->conflict_queue); + if (!rr) + break; + + r = dns_scope_make_conflict_packet(scope, rr, &p); + if (r < 0) { + log_error_errno(r, "Failed to make conflict packet: %m"); + return 0; + } + + r = dns_scope_emit_udp(scope, -1, p); + if (r < 0) + log_debug_errno(r, "Failed to send conflict packet: %m"); + } + + return 0; +} + +int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr) { + usec_t jitter; + int r; + + assert(scope); + assert(rr); + + /* We don't send these queries immediately. Instead, we queue + * them, and send them after some jitter delay. */ + r = ordered_hashmap_ensure_allocated(&scope->conflict_queue, &dns_resource_key_hash_ops); + if (r < 0) { + log_oom(); + return r; + } + + /* We only place one RR per key in the conflict + * messages, not all of them. That should be enough to + * indicate where there might be a conflict */ + r = ordered_hashmap_put(scope->conflict_queue, rr->key, rr); + if (r == -EEXIST || r == 0) + return 0; + if (r < 0) + return log_debug_errno(r, "Failed to queue conflicting RR: %m"); + + dns_resource_record_ref(rr); + + if (scope->conflict_event_source) + return 0; + + random_bytes(&jitter, sizeof(jitter)); + jitter %= LLMNR_JITTER_INTERVAL_USEC; + + r = sd_event_add_time(scope->manager->event, + &scope->conflict_event_source, + clock_boottime_or_monotonic(), + now(clock_boottime_or_monotonic()) + jitter, + LLMNR_JITTER_INTERVAL_USEC, + on_conflict_dispatch, scope); + 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; +} + +void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p) { + unsigned i; + int r; + + assert(scope); + assert(p); + + if (p->protocol != DNS_PROTOCOL_LLMNR) + return; + + if (DNS_PACKET_RRCOUNT(p) <= 0) + return; + + if (DNS_PACKET_LLMNR_C(p) != 0) + return; + + if (DNS_PACKET_LLMNR_T(p) != 0) + return; + + if (manager_our_packet(scope->manager, p)) + return; + + r = dns_packet_extract(p); + if (r < 0) { + log_debug_errno(r, "Failed to extract packet: %m"); + return; + } + + log_debug("Checking for conflicts..."); + + for (i = 0; i < p->answer->n_rrs; i++) { + + /* Check for conflicts against the local zone. If we + * found one, we won't check any further */ + r = dns_zone_check_conflicts(&scope->zone, p->answer->items[i].rr); + if (r != 0) + continue; + + /* Check for conflicts against the local cache. If so, + * send out an advisory query, to inform everybody */ + r = dns_cache_check_conflicts(&scope->cache, p->answer->items[i].rr, p->family, &p->sender); + if (r <= 0) + continue; + + dns_scope_notify_conflict(scope, p->answer->items[i].rr); + } +} + +void dns_scope_dump(DnsScope *s, FILE *f) { + assert(s); + + if (!f) + f = stdout; + + fputs("[Scope protocol=", f); + fputs(dns_protocol_to_string(s->protocol), f); + + if (s->link) { + fputs(" interface=", f); + fputs(s->link->name, f); + } + + if (s->family != AF_UNSPEC) { + fputs(" family=", f); + fputs(af_to_name(s->family), f); + } + + fputs("]\n", f); + + if (!dns_zone_is_empty(&s->zone)) { + fputs("ZONE:\n", f); + dns_zone_dump(&s->zone, f); + } + + if (!dns_cache_is_empty(&s->cache)) { + fputs("CACHE:\n", f); + dns_cache_dump(&s->cache, f); + } +} + +DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s) { + assert(s); + + if (s->protocol != DNS_PROTOCOL_DNS) + return NULL; + + if (s->link) + return s->link->search_domains; + + return s->manager->search_domains; +} + +bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name) { + assert(s); + + if (s->protocol != DNS_PROTOCOL_DNS) + return false; + + 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/src/resolved-dns-scope.h b/src/resolve/src/resolved-dns-scope.h new file mode 100644 index 0000000000..291e5817d0 --- /dev/null +++ b/src/resolve/src/resolved-dns-scope.h @@ -0,0 +1,109 @@ +#pragma once + +/*** + 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 . +***/ + +#include "list.h" + +typedef struct DnsScope DnsScope; + +#include "resolved-dns-cache.h" +#include "resolved-dns-dnssec.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-server.h" +#include "resolved-dns-zone.h" +#include "resolved-link.h" + +typedef enum DnsScopeMatch { + DNS_SCOPE_NO, + DNS_SCOPE_MAYBE, + DNS_SCOPE_YES, + _DNS_SCOPE_MATCH_MAX, + _DNS_SCOPE_INVALID = -1 +} DnsScopeMatch; + +struct DnsScope { + Manager *manager; + + DnsProtocol protocol; + int family; + DnssecMode dnssec_mode; + + Link *link; + + DnsCache cache; + DnsZone zone; + + OrderedHashmap *conflict_queue; + sd_event_source *conflict_event_source; + + RateLimit ratelimit; + + usec_t resend_timeout; + usec_t max_rtt; + + LIST_HEAD(DnsQueryCandidate, query_candidates); + + /* Note that we keep track of ongoing transactions in two + * ways: once in a hashmap, indexed by the rr key, and once in + * a linked list. We use the hashmap to quickly find + * transactions we can reuse for a key. But note that there + * might be multiple transactions for the same key (because + * the zone probing can't reuse a transaction answered from + * the zone or the cache), and the hashmap only tracks the + * most recent entry. */ + Hashmap *transactions_by_key; + LIST_HEAD(DnsTransaction, transactions); + + LIST_FIELDS(DnsScope, scopes); +}; + +int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol p, int family); +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_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); +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); + +int dns_scope_llmnr_membership(DnsScope *s, bool b); +int dns_scope_mdns_membership(DnsScope *s, bool b); + +void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p); + +DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok); + +int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr); +void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p); + +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/src/resolved-dns-search-domain.c b/src/resolve/src/resolved-dns-search-domain.c new file mode 100644 index 0000000000..732471027b --- /dev/null +++ b/src/resolve/src/resolved-dns-search-domain.c @@ -0,0 +1,227 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include "alloc-util.h" +#include "dns-domain.h" +#include "resolved-dns-search-domain.h" + +int dns_search_domain_new( + Manager *m, + DnsSearchDomain **ret, + DnsSearchDomainType type, + Link *l, + const char *name) { + + _cleanup_free_ char *normalized = NULL; + DnsSearchDomain *d; + int r; + + assert(m); + assert((type == DNS_SEARCH_DOMAIN_LINK) == !!l); + assert(name); + + r = dns_name_normalize(name, &normalized); + if (r < 0) + return r; + + if (l) { + if (l->n_search_domains >= LINK_SEARCH_DOMAINS_MAX) + return -E2BIG; + } else { + if (m->n_search_domains >= MANAGER_SEARCH_DOMAINS_MAX) + return -E2BIG; + } + + d = new0(DnsSearchDomain, 1); + if (!d) + return -ENOMEM; + + d->n_ref = 1; + d->manager = m; + d->type = type; + d->name = normalized; + normalized = NULL; + + switch (type) { + + case DNS_SEARCH_DOMAIN_LINK: + d->link = l; + LIST_APPEND(domains, l->search_domains, d); + l->n_search_domains++; + break; + + case DNS_SERVER_SYSTEM: + LIST_APPEND(domains, m->search_domains, d); + m->n_search_domains++; + break; + + default: + assert_not_reached("Unknown search domain type"); + } + + d->linked = true; + + if (ret) + *ret = d; + + return 0; +} + +DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d) { + if (!d) + return NULL; + + assert(d->n_ref > 0); + d->n_ref++; + + return d; +} + +DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d) { + if (!d) + return NULL; + + assert(d->n_ref > 0); + d->n_ref--; + + if (d->n_ref > 0) + return NULL; + + free(d->name); + free(d); + + return NULL; +} + +void dns_search_domain_unlink(DnsSearchDomain *d) { + assert(d); + assert(d->manager); + + if (!d->linked) + return; + + switch (d->type) { + + case DNS_SEARCH_DOMAIN_LINK: + assert(d->link); + assert(d->link->n_search_domains > 0); + LIST_REMOVE(domains, d->link->search_domains, d); + d->link->n_search_domains--; + break; + + case DNS_SEARCH_DOMAIN_SYSTEM: + assert(d->manager->n_search_domains > 0); + LIST_REMOVE(domains, d->manager->search_domains, d); + d->manager->n_search_domains--; + break; + } + + d->linked = false; + + dns_search_domain_unref(d); +} + +void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d) { + DnsSearchDomain *tail; + + assert(d); + + if (!d->marked) + return; + + d->marked = false; + + if (!d->linked || !d->domains_next) + return; + + switch (d->type) { + + case DNS_SEARCH_DOMAIN_LINK: + assert(d->link); + LIST_FIND_TAIL(domains, d, tail); + LIST_REMOVE(domains, d->link->search_domains, d); + LIST_INSERT_AFTER(domains, d->link->search_domains, tail, d); + break; + + case DNS_SEARCH_DOMAIN_SYSTEM: + LIST_FIND_TAIL(domains, d, tail); + LIST_REMOVE(domains, d->manager->search_domains, d); + LIST_INSERT_AFTER(domains, d->manager->search_domains, tail, d); + break; + + default: + assert_not_reached("Unknown search domain type"); + } +} + +void dns_search_domain_unlink_all(DnsSearchDomain *first) { + DnsSearchDomain *next; + + if (!first) + return; + + next = first->domains_next; + dns_search_domain_unlink(first); + + dns_search_domain_unlink_all(next); +} + +void dns_search_domain_unlink_marked(DnsSearchDomain *first) { + DnsSearchDomain *next; + + if (!first) + return; + + next = first->domains_next; + + if (first->marked) + dns_search_domain_unlink(first); + + dns_search_domain_unlink_marked(next); +} + +void dns_search_domain_mark_all(DnsSearchDomain *first) { + if (!first) + return; + + first->marked = true; + dns_search_domain_mark_all(first->domains_next); +} + +int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret) { + DnsSearchDomain *d; + int r; + + assert(name); + assert(ret); + + LIST_FOREACH(domains, d, first) { + + r = dns_name_equal(name, d->name); + if (r < 0) + return r; + if (r > 0) { + *ret = d; + return 1; + } + } + + *ret = NULL; + return 0; +} diff --git a/src/resolve/src/resolved-dns-search-domain.h b/src/resolve/src/resolved-dns-search-domain.h new file mode 100644 index 0000000000..eaacef4edc --- /dev/null +++ b/src/resolve/src/resolved-dns-search-domain.h @@ -0,0 +1,74 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include "macro.h" + +typedef struct DnsSearchDomain DnsSearchDomain; + +typedef enum DnsSearchDomainType { + DNS_SEARCH_DOMAIN_SYSTEM, + DNS_SEARCH_DOMAIN_LINK, +} DnsSearchDomainType; + +#include "resolved-link.h" +#include "resolved-manager.h" + +struct DnsSearchDomain { + Manager *manager; + + unsigned n_ref; + + DnsSearchDomainType type; + Link *link; + + char *name; + + bool marked:1; + bool route_only:1; + + bool linked:1; + LIST_FIELDS(DnsSearchDomain, domains); +}; + +int dns_search_domain_new( + Manager *m, + DnsSearchDomain **ret, + DnsSearchDomainType type, + Link *link, + const char *name); + +DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d); +DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d); + +void dns_search_domain_unlink(DnsSearchDomain *d); +void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d); + +void dns_search_domain_unlink_all(DnsSearchDomain *first); +void dns_search_domain_unlink_marked(DnsSearchDomain *first); +void dns_search_domain_mark_all(DnsSearchDomain *first); + +int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret); + +static inline const char* DNS_SEARCH_DOMAIN_NAME(DnsSearchDomain *d) { + return d ? d->name : NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsSearchDomain*, dns_search_domain_unref); diff --git a/src/resolve/src/resolved-dns-server.c b/src/resolve/src/resolved-dns-server.c new file mode 100644 index 0000000000..27342a0e04 --- /dev/null +++ b/src/resolve/src/resolved-dns-server.c @@ -0,0 +1,740 @@ +/*** + 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 . +***/ + +#include + +#include "alloc-util.h" +#include "resolved-dns-server.h" +#include "resolved-resolv-conf.h" +#include "siphash24.h" +#include "string-table.h" +#include "string-util.h" + +/* After how much time to repeat classic DNS requests */ +#define DNS_TIMEOUT_MIN_USEC (500 * USEC_PER_MSEC) +#define DNS_TIMEOUT_MAX_USEC (5 * USEC_PER_SEC) + +/* The amount of time to wait before retrying with a full feature set */ +#define DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC (6 * USEC_PER_HOUR) +#define DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC (5 * USEC_PER_MINUTE) + +/* The number of times we will attempt a certain feature set before degrading */ +#define DNS_SERVER_FEATURE_RETRY_ATTEMPTS 3 + +int dns_server_new( + Manager *m, + DnsServer **ret, + DnsServerType type, + Link *l, + int family, + const union in_addr_union *in_addr) { + + DnsServer *s; + + assert(m); + assert((type == DNS_SERVER_LINK) == !!l); + assert(in_addr); + + if (!IN_SET(family, AF_INET, AF_INET6)) + return -EAFNOSUPPORT; + + if (l) { + if (l->n_dns_servers >= LINK_DNS_SERVERS_MAX) + return -E2BIG; + } else { + if (m->n_dns_servers >= MANAGER_DNS_SERVERS_MAX) + return -E2BIG; + } + + s = new0(DnsServer, 1); + if (!s) + return -ENOMEM; + + s->n_ref = 1; + s->manager = m; + 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; + s->family = family; + s->address = *in_addr; + s->resend_timeout = DNS_TIMEOUT_MIN_USEC; + + switch (type) { + + case DNS_SERVER_LINK: + s->link = l; + LIST_APPEND(servers, l->dns_servers, s); + l->n_dns_servers++; + break; + + case DNS_SERVER_SYSTEM: + LIST_APPEND(servers, m->dns_servers, s); + m->n_dns_servers++; + break; + + case DNS_SERVER_FALLBACK: + LIST_APPEND(servers, m->fallback_dns_servers, s); + m->n_dns_servers++; + break; + + default: + assert_not_reached("Unknown server type"); + } + + s->linked = true; + + /* A new DNS server that isn't fallback is added and the one + * we used so far was a fallback one? Then let's try to pick + * the new one */ + if (type != DNS_SERVER_FALLBACK && + m->current_dns_server && + m->current_dns_server->type == DNS_SERVER_FALLBACK) + manager_set_dns_server(m, NULL); + + if (ret) + *ret = s; + + return 0; +} + +DnsServer* dns_server_ref(DnsServer *s) { + if (!s) + return NULL; + + assert(s->n_ref > 0); + s->n_ref ++; + + return s; +} + +DnsServer* dns_server_unref(DnsServer *s) { + if (!s) + return NULL; + + assert(s->n_ref > 0); + s->n_ref --; + + if (s->n_ref > 0) + return NULL; + + free(s->server_string); + free(s); + return NULL; +} + +void dns_server_unlink(DnsServer *s) { + assert(s); + assert(s->manager); + + /* This removes the specified server from the linked list of + * servers, but any server might still stay around if it has + * refs, for example from an ongoing transaction. */ + + if (!s->linked) + return; + + switch (s->type) { + + case DNS_SERVER_LINK: + assert(s->link); + assert(s->link->n_dns_servers > 0); + LIST_REMOVE(servers, s->link->dns_servers, s); + break; + + case DNS_SERVER_SYSTEM: + assert(s->manager->n_dns_servers > 0); + LIST_REMOVE(servers, s->manager->dns_servers, s); + s->manager->n_dns_servers--; + break; + + case DNS_SERVER_FALLBACK: + assert(s->manager->n_dns_servers > 0); + LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); + s->manager->n_dns_servers--; + break; + } + + s->linked = false; + + if (s->link && s->link->current_dns_server == s) + link_set_dns_server(s->link, NULL); + + if (s->manager->current_dns_server == s) + manager_set_dns_server(s->manager, NULL); + + dns_server_unref(s); +} + +void dns_server_move_back_and_unmark(DnsServer *s) { + DnsServer *tail; + + assert(s); + + if (!s->marked) + return; + + s->marked = false; + + if (!s->linked || !s->servers_next) + return; + + /* Move us to the end of the list, so that the order is + * strictly kept, if we are not at the end anyway. */ + + switch (s->type) { + + case DNS_SERVER_LINK: + assert(s->link); + LIST_FIND_TAIL(servers, s, tail); + LIST_REMOVE(servers, s->link->dns_servers, s); + LIST_INSERT_AFTER(servers, s->link->dns_servers, tail, s); + break; + + case DNS_SERVER_SYSTEM: + LIST_FIND_TAIL(servers, s, tail); + LIST_REMOVE(servers, s->manager->dns_servers, s); + LIST_INSERT_AFTER(servers, s->manager->dns_servers, tail, s); + break; + + case DNS_SERVER_FALLBACK: + LIST_FIND_TAIL(servers, s, tail); + LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); + LIST_INSERT_AFTER(servers, s->manager->fallback_dns_servers, tail, s); + break; + + default: + assert_not_reached("Unknown server type"); + } +} + +static void dns_server_verified(DnsServer *s, DnsServerFeatureLevel level) { + assert(s); + + 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; + } + + 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 (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 = CLAMP(s->max_rtt * 2, DNS_TIMEOUT_MIN_USEC, DNS_TIMEOUT_MAX_USEC); + } +} + +void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec) { + assert(s); + assert(s->manager); + + if (s->possible_feature_level == level) { + if (protocol == IPPROTO_UDP) + s->n_failed_udp ++; + else if (protocol == IPPROTO_TCP) + s->n_failed_tcp ++; + } + + if (s->resend_timeout > usec) + return; + + s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC); +} + +void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel level) { + assert(s); + + /* 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; + + /* 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) { + usec_t ts; + + assert(s); + assert(s->manager); + + if (s->verified_usec == 0) + return false; + + assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + + if (s->verified_usec + s->features_grace_period_usec > ts) + return false; + + s->features_grace_period_usec = MIN(s->features_grace_period_usec * 2, DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC); + + return true; +} + +static void dns_server_reset_counters(DnsServer *s) { + assert(s); + + s->n_failed_udp = 0; + s->n_failed_tcp = 0; + s->packet_failed = false; + s->packet_truncated = false; + s->verified_usec = 0; + + /* Note that we do not reset s->packet_bad_opt and s->packet_rrsig_missing here. We reset them only when the + * grace period ends, but not when lowering the possible feature level, as a lower level feature level should + * not make RRSIGs appear or OPT appear, but rather make them disappear. If the reappear anyway, then that's + * indication for a differently broken OPT/RRSIG implementation, and we really don't want to support that + * either. + * + * This is particularly important to deal with certain Belkin routers which break OPT for certain lookups (A), + * but pass traffic through for others (AAAA). If we detect the broken behaviour on one lookup we should not + * reenable it for another, because we cannot validate things anyway, given that the RRSIG/OPT data will be + * incomplete. */ +} + +DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s) { + assert(s); + + if (s->possible_feature_level != DNS_SERVER_FEATURE_LEVEL_BEST && + dns_server_grace_period_expired(s)) { + + 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_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) { + const DnsServer *s = p; + + assert(s); + + siphash24_compress(&s->family, sizeof(s->family), state); + siphash24_compress(&s->address, FAMILY_ADDRESS_SIZE(s->family), state); +} + +static int dns_server_compare_func(const void *a, const void *b) { + const DnsServer *x = a, *y = b; + + if (x->family < y->family) + return -1; + if (x->family > y->family) + return 1; + + return memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family)); +} + +const struct hash_ops dns_server_hash_ops = { + .hash = dns_server_hash_func, + .compare = dns_server_compare_func +}; + +void dns_server_unlink_all(DnsServer *first) { + DnsServer *next; + + if (!first) + return; + + next = first->servers_next; + dns_server_unlink(first); + + dns_server_unlink_all(next); +} + +void dns_server_unlink_marked(DnsServer *first) { + DnsServer *next; + + if (!first) + return; + + next = first->servers_next; + + if (first->marked) + dns_server_unlink(first); + + dns_server_unlink_marked(next); +} + +void dns_server_mark_all(DnsServer *first) { + if (!first) + return; + + first->marked = true; + dns_server_mark_all(first->servers_next); +} + +DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr) { + DnsServer *s; + + LIST_FOREACH(servers, s, first) + if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0) + return s; + + return NULL; +} + +DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t) { + assert(m); + + switch (t) { + + case DNS_SERVER_SYSTEM: + return m->dns_servers; + + case DNS_SERVER_FALLBACK: + return m->fallback_dns_servers; + + default: + return NULL; + } +} + +DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) { + assert(m); + + if (m->current_dns_server == s) + return s; + + 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); + + if (m->unicast_scope) + dns_cache_flush(&m->unicast_scope->cache); + + return s; +} + +DnsServer *manager_get_dns_server(Manager *m) { + Link *l; + assert(m); + + /* Try to read updates resolv.conf */ + manager_read_resolv_conf(m); + + /* 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); + + if (!m->current_dns_server) { + bool found = false; + Iterator i; + + /* No DNS servers configured, let's see if there are + * any on any links. If not, we use the fallback + * servers */ + + HASHMAP_FOREACH(l, m->links, i) + if (l->dns_servers) { + found = true; + break; + } + + if (!found) + manager_set_dns_server(m, m->fallback_dns_servers); + } + + return m->current_dns_server; +} + +void manager_next_dns_server(Manager *m) { + assert(m); + + /* If there's currently no DNS server set, then the next + * manager_get_dns_server() will find one */ + if (!m->current_dns_server) + return; + + /* Change to the next one, but make sure to follow the linked + * list only if the server is still linked. */ + if (m->current_dns_server->linked && m->current_dns_server->servers_next) { + manager_set_dns_server(m, m->current_dns_server->servers_next); + return; + } + + /* If there was no next one, then start from the beginning of + * the list */ + if (m->current_dns_server->type == DNS_SERVER_FALLBACK) + manager_set_dns_server(m, m->fallback_dns_servers); + else + 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", + [DNS_SERVER_FEATURE_LEVEL_EDNS0] = "UDP+EDNS0", + [DNS_SERVER_FEATURE_LEVEL_DO] = "UDP+EDNS0+DO", + [DNS_SERVER_FEATURE_LEVEL_LARGE] = "UDP+EDNS0+DO+LARGE", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_server_feature_level, DnsServerFeatureLevel); diff --git a/src/resolve/src/resolved-dns-server.h b/src/resolve/src/resolved-dns-server.h new file mode 100644 index 0000000000..9f4a69c37a --- /dev/null +++ b/src/resolve/src/resolved-dns-server.h @@ -0,0 +1,143 @@ +#pragma once + +/*** + 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 . +***/ + +#include "in-addr-util.h" + +typedef struct DnsServer DnsServer; + +typedef enum DnsServerType { + DNS_SERVER_SYSTEM, + 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, + DNS_SERVER_FEATURE_LEVEL_UDP, + DNS_SERVER_FEATURE_LEVEL_EDNS0, + DNS_SERVER_FEATURE_LEVEL_DO, + DNS_SERVER_FEATURE_LEVEL_LARGE, + _DNS_SERVER_FEATURE_LEVEL_MAX, + _DNS_SERVER_FEATURE_LEVEL_INVALID = -1 +} DnsServerFeatureLevel; + +#define DNS_SERVER_FEATURE_LEVEL_WORST 0 +#define DNS_SERVER_FEATURE_LEVEL_BEST (_DNS_SERVER_FEATURE_LEVEL_MAX - 1) + +const char* dns_server_feature_level_to_string(int i) _const_; +int dns_server_feature_level_from_string(const char *s) _pure_; + +#include "resolved-link.h" +#include "resolved-manager.h" + +struct DnsServer { + Manager *manager; + + unsigned n_ref; + + DnsServerType type; + Link *link; + + int family; + union in_addr_union address; + + char *server_string; + + usec_t resend_timeout; + usec_t max_rtt; + + DnsServerFeatureLevel verified_feature_level; + DnsServerFeatureLevel possible_feature_level; + + size_t received_udp_packet_max; + + unsigned n_failed_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); +}; + +int dns_server_new( + Manager *m, + DnsServer **ret, + DnsServerType type, + Link *link, + int family, + const union in_addr_union *address); + +DnsServer* dns_server_ref(DnsServer *s); +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, 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); + +void dns_server_unlink_all(DnsServer *first); +void dns_server_unlink_marked(DnsServer *first); +void dns_server_mark_all(DnsServer *first); + +DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t); + +DnsServer *manager_set_dns_server(Manager *m, DnsServer *s); +DnsServer *manager_get_dns_server(Manager *m); +void manager_next_dns_server(Manager *m); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref); + +extern const struct hash_ops dns_server_hash_ops; diff --git a/src/resolve/src/resolved-dns-stream.c b/src/resolve/src/resolved-dns-stream.c new file mode 100644 index 0000000000..a1040aeff4 --- /dev/null +++ b/src/resolve/src/resolved-dns-stream.c @@ -0,0 +1,403 @@ +/*** + 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 . +***/ + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "missing.h" +#include "resolved-dns-stream.h" + +#define DNS_STREAM_TIMEOUT_USEC (10 * USEC_PER_SEC) +#define DNS_STREAMS_MAX 128 + +static void dns_stream_stop(DnsStream *s) { + assert(s); + + s->io_event_source = sd_event_source_unref(s->io_event_source); + s->timeout_event_source = sd_event_source_unref(s->timeout_event_source); + s->fd = safe_close(s->fd); +} + +static int dns_stream_update_io(DnsStream *s) { + int f = 0; + + assert(s); + + if (s->write_packet && s->n_written < sizeof(s->write_size) + s->write_packet->size) + f |= EPOLLOUT; + if (!s->read_packet || s->n_read < sizeof(s->read_size) + s->read_packet->size) + f |= EPOLLIN; + + return sd_event_source_set_io_events(s->io_event_source, f); +} + +static int dns_stream_complete(DnsStream *s, int error) { + assert(s); + + dns_stream_stop(s); + + if (s->complete) + s->complete(s, error); + else + dns_stream_free(s); + + return 0; +} + +static int dns_stream_identify(DnsStream *s) { + union { + struct cmsghdr header; /* For alignment */ + uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo)) + + EXTRA_CMSG_SPACE /* kernel appears to require extra space */]; + } control; + struct msghdr mh = {}; + struct cmsghdr *cmsg; + socklen_t sl; + int r; + + assert(s); + + if (s->identified) + return 0; + + /* Query the local side */ + s->local_salen = sizeof(s->local); + r = getsockname(s->fd, &s->local.sa, &s->local_salen); + if (r < 0) + return -errno; + if (s->local.sa.sa_family == AF_INET6 && s->ifindex <= 0) + s->ifindex = s->local.in6.sin6_scope_id; + + /* Query the remote side */ + s->peer_salen = sizeof(s->peer); + r = getpeername(s->fd, &s->peer.sa, &s->peer_salen); + if (r < 0) + return -errno; + if (s->peer.sa.sa_family == AF_INET6 && s->ifindex <= 0) + s->ifindex = s->peer.in6.sin6_scope_id; + + /* Check consistency */ + assert(s->peer.sa.sa_family == s->local.sa.sa_family); + assert(IN_SET(s->peer.sa.sa_family, AF_INET, AF_INET6)); + + /* Query connection meta information */ + sl = sizeof(control); + if (s->peer.sa.sa_family == AF_INET) { + r = getsockopt(s->fd, IPPROTO_IP, IP_PKTOPTIONS, &control, &sl); + if (r < 0) + return -errno; + } else if (s->peer.sa.sa_family == AF_INET6) { + + r = getsockopt(s->fd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, &control, &sl); + if (r < 0) + return -errno; + } else + return -EAFNOSUPPORT; + + mh.msg_control = &control; + mh.msg_controllen = sl; + + CMSG_FOREACH(cmsg, &mh) { + + if (cmsg->cmsg_level == IPPROTO_IPV6) { + assert(s->peer.sa.sa_family == AF_INET6); + + switch (cmsg->cmsg_type) { + + case IPV6_PKTINFO: { + struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); + + if (s->ifindex <= 0) + s->ifindex = i->ipi6_ifindex; + break; + } + + case IPV6_HOPLIMIT: + s->ttl = *(int *) CMSG_DATA(cmsg); + break; + } + + } else if (cmsg->cmsg_level == IPPROTO_IP) { + assert(s->peer.sa.sa_family == AF_INET); + + switch (cmsg->cmsg_type) { + + case IP_PKTINFO: { + struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); + + if (s->ifindex <= 0) + s->ifindex = i->ipi_ifindex; + break; + } + + case IP_TTL: + s->ttl = *(int *) CMSG_DATA(cmsg); + break; + } + } + } + + /* The Linux kernel sets the interface index to the loopback + * device if the connection came from the local host since it + * avoids the routing table in such a case. Let's unset the + * interface index in such a case. */ + if (s->ifindex == LOOPBACK_IFINDEX) + s->ifindex = 0; + + /* If we don't know the interface index still, we look for the + * first local interface with a matching address. Yuck! */ + if (s->ifindex <= 0) + s->ifindex = manager_find_ifindex(s->manager, s->local.sa.sa_family, s->local.sa.sa_family == AF_INET ? (union in_addr_union*) &s->local.in.sin_addr : (union in_addr_union*) &s->local.in6.sin6_addr); + + if (s->protocol == DNS_PROTOCOL_LLMNR && s->ifindex > 0) { + uint32_t ifindex = htobe32(s->ifindex); + + /* Make sure all packets for this connection are sent on the same interface */ + if (s->local.sa.sa_family == AF_INET) { + r = setsockopt(s->fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex)); + if (r < 0) + log_debug_errno(errno, "Failed to invoke IP_UNICAST_IF: %m"); + } else if (s->local.sa.sa_family == AF_INET6) { + r = setsockopt(s->fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex)); + if (r < 0) + log_debug_errno(errno, "Failed to invoke IPV6_UNICAST_IF: %m"); + } + } + + s->identified = true; + + return 0; +} + +static int on_stream_timeout(sd_event_source *es, usec_t usec, void *userdata) { + DnsStream *s = userdata; + + assert(s); + + return dns_stream_complete(s, ETIMEDOUT); +} + +static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *userdata) { + DnsStream *s = userdata; + int r; + + assert(s); + + r = dns_stream_identify(s); + if (r < 0) + return dns_stream_complete(s, -r); + + if ((revents & EPOLLOUT) && + s->write_packet && + s->n_written < sizeof(s->write_size) + s->write_packet->size) { + + struct iovec iov[2]; + ssize_t ss; + + iov[0].iov_base = &s->write_size; + iov[0].iov_len = sizeof(s->write_size); + iov[1].iov_base = DNS_PACKET_DATA(s->write_packet); + iov[1].iov_len = s->write_packet->size; + + IOVEC_INCREMENT(iov, 2, s->n_written); + + ss = writev(fd, iov, 2); + if (ss < 0) { + if (errno != EINTR && errno != EAGAIN) + return dns_stream_complete(s, errno); + } else + s->n_written += ss; + + /* Are we done? If so, disable the event source for EPOLLOUT */ + if (s->n_written >= sizeof(s->write_size) + s->write_packet->size) { + r = dns_stream_update_io(s); + if (r < 0) + return dns_stream_complete(s, -r); + } + } + + if ((revents & (EPOLLIN|EPOLLHUP|EPOLLRDHUP)) && + (!s->read_packet || + s->n_read < sizeof(s->read_size) + s->read_packet->size)) { + + if (s->n_read < sizeof(s->read_size)) { + ssize_t ss; + + ss = read(fd, (uint8_t*) &s->read_size + s->n_read, sizeof(s->read_size) - s->n_read); + if (ss < 0) { + if (errno != EINTR && errno != EAGAIN) + return dns_stream_complete(s, errno); + } else if (ss == 0) + return dns_stream_complete(s, ECONNRESET); + else + s->n_read += ss; + } + + if (s->n_read >= sizeof(s->read_size)) { + + if (be16toh(s->read_size) < DNS_PACKET_HEADER_SIZE) + return dns_stream_complete(s, EBADMSG); + + if (s->n_read < sizeof(s->read_size) + be16toh(s->read_size)) { + ssize_t ss; + + if (!s->read_packet) { + r = dns_packet_new(&s->read_packet, s->protocol, be16toh(s->read_size)); + if (r < 0) + return dns_stream_complete(s, -r); + + s->read_packet->size = be16toh(s->read_size); + s->read_packet->ipproto = IPPROTO_TCP; + s->read_packet->family = s->peer.sa.sa_family; + s->read_packet->ttl = s->ttl; + s->read_packet->ifindex = s->ifindex; + + if (s->read_packet->family == AF_INET) { + s->read_packet->sender.in = s->peer.in.sin_addr; + s->read_packet->sender_port = be16toh(s->peer.in.sin_port); + s->read_packet->destination.in = s->local.in.sin_addr; + s->read_packet->destination_port = be16toh(s->local.in.sin_port); + } else { + assert(s->read_packet->family == AF_INET6); + s->read_packet->sender.in6 = s->peer.in6.sin6_addr; + s->read_packet->sender_port = be16toh(s->peer.in6.sin6_port); + s->read_packet->destination.in6 = s->local.in6.sin6_addr; + s->read_packet->destination_port = be16toh(s->local.in6.sin6_port); + + if (s->read_packet->ifindex == 0) + s->read_packet->ifindex = s->peer.in6.sin6_scope_id; + if (s->read_packet->ifindex == 0) + s->read_packet->ifindex = s->local.in6.sin6_scope_id; + } + } + + ss = read(fd, + (uint8_t*) DNS_PACKET_DATA(s->read_packet) + s->n_read - sizeof(s->read_size), + sizeof(s->read_size) + be16toh(s->read_size) - s->n_read); + if (ss < 0) { + if (errno != EINTR && errno != EAGAIN) + return dns_stream_complete(s, errno); + } else if (ss == 0) + return dns_stream_complete(s, ECONNRESET); + else + s->n_read += ss; + } + + /* Are we done? If so, disable the event source for EPOLLIN */ + if (s->n_read >= sizeof(s->read_size) + be16toh(s->read_size)) { + r = dns_stream_update_io(s); + if (r < 0) + return dns_stream_complete(s, -r); + + /* If there's a packet handler + * installed, call that. Note that + * this is optional... */ + if (s->on_packet) + return s->on_packet(s); + } + } + } + + if ((s->write_packet && s->n_written >= sizeof(s->write_size) + s->write_packet->size) && + (s->read_packet && s->n_read >= sizeof(s->read_size) + s->read_packet->size)) + return dns_stream_complete(s, 0); + + return 0; +} + +DnsStream *dns_stream_free(DnsStream *s) { + if (!s) + return NULL; + + dns_stream_stop(s); + + if (s->manager) { + LIST_REMOVE(streams, s->manager->dns_streams, s); + s->manager->n_dns_streams--; + } + + dns_packet_unref(s->write_packet); + dns_packet_unref(s->read_packet); + + free(s); + + return 0; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free); + +int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { + _cleanup_(dns_stream_freep) DnsStream *s = NULL; + int r; + + assert(m); + assert(fd >= 0); + + if (m->n_dns_streams > DNS_STREAMS_MAX) + return -EBUSY; + + s = new0(DnsStream, 1); + if (!s) + return -ENOMEM; + + s->fd = -1; + s->protocol = protocol; + + 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, + clock_boottime_or_monotonic(), + now(clock_boottime_or_monotonic()) + DNS_STREAM_TIMEOUT_USEC, 0, + on_stream_timeout, s); + 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; + m->n_dns_streams++; + + *ret = s; + s = NULL; + + return 0; +} + +int dns_stream_write_packet(DnsStream *s, DnsPacket *p) { + assert(s); + + if (s->write_packet) + return -EBUSY; + + s->write_packet = dns_packet_ref(p); + s->write_size = htobe16(p->size); + s->n_written = 0; + + return dns_stream_update_io(s); +} diff --git a/src/resolve/src/resolved-dns-stream.h b/src/resolve/src/resolved-dns-stream.h new file mode 100644 index 0000000000..5ccc842249 --- /dev/null +++ b/src/resolve/src/resolved-dns-stream.h @@ -0,0 +1,61 @@ +#pragma once + +/*** + 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 . +***/ + +#include "socket-util.h" + +typedef struct DnsStream DnsStream; + +#include "resolved-dns-packet.h" +#include "resolved-dns-transaction.h" + +struct DnsStream { + Manager *manager; + + DnsProtocol protocol; + + int fd; + union sockaddr_union peer; + socklen_t peer_salen; + union sockaddr_union local; + socklen_t local_salen; + int ifindex; + uint32_t ttl; + bool identified; + + sd_event_source *io_event_source; + sd_event_source *timeout_event_source; + + be16_t write_size, read_size; + DnsPacket *write_packet, *read_packet; + size_t n_written, n_read; + + int (*on_packet)(DnsStream *s); + int (*complete)(DnsStream *s, int error); + + DnsTransaction *transaction; + + LIST_FIELDS(DnsStream, streams); +}; + +int dns_stream_new(Manager *m, DnsStream **s, DnsProtocol protocol, int fd); +DnsStream *dns_stream_free(DnsStream *s); + +int dns_stream_write_packet(DnsStream *s, DnsPacket *p); diff --git a/src/resolve/src/resolved-dns-synthesize.c b/src/resolve/src/resolved-dns-synthesize.c new file mode 100644 index 0000000000..f4a43dee8c --- /dev/null +++ b/src/resolve/src/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 . +***/ + +#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/src/resolved-dns-synthesize.h b/src/resolve/src/resolved-dns-synthesize.h new file mode 100644 index 0000000000..5d829bb2e7 --- /dev/null +++ b/src/resolve/src/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 . +***/ + +#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/src/resolved-dns-transaction.c b/src/resolve/src/resolved-dns-transaction.c new file mode 100644 index 0000000000..d48fdd1281 --- /dev/null +++ b/src/resolve/src/resolved-dns-transaction.c @@ -0,0 +1,3026 @@ +/*** + 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 . +***/ + +#include + +#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" +#include "resolved-dns-transaction.h" +#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; + DnsTransaction *z; + + if (!t) + return NULL; + + log_debug("Freeing transaction %" PRIu16 ".", t->id); + + dns_transaction_close_connection(t); + dns_transaction_stop_timeout(t); + + dns_packet_unref(t->sent); + dns_transaction_reset_answer(t); + + dns_server_unref(t->server); + + if (t->scope) { + hashmap_remove_value(t->scope->transactions_by_key, t->key, t); + LIST_REMOVE(transactions_by_scope, t->scope->transactions, t); + + if (t->id != 0) + hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id)); + } + + while ((c = set_steal_first(t->notify_query_candidates))) + set_remove(c->transactions, t); + set_free(t->notify_query_candidates); + + while ((i = set_steal_first(t->notify_zone_items))) + i->probe_transaction = NULL; + set_free(t->notify_zone_items); + + while ((z = set_steal_first(t->notify_transactions))) + set_remove(z->dnssec_transactions, t); + set_free(t->notify_transactions); + + 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; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsTransaction*, dns_transaction_free); + +bool dns_transaction_gc(DnsTransaction *t) { + assert(t); + + if (t->block_gc > 0) + return true; + + if (set_isempty(t->notify_query_candidates) && + set_isempty(t->notify_zone_items) && + 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) { + _cleanup_(dns_transaction_freep) DnsTransaction *t = NULL; + int r; + + assert(ret); + assert(s); + assert(key); + + /* Don't allow looking up invalid or pseudo RRs */ + if (!dns_type_is_valid_query(key->type)) + return -EINVAL; + if (dns_type_is_obsolete(key->type)) + return -EOPNOTSUPP; + + /* We only support the IN class */ + if (key->class != DNS_CLASS_IN && key->class != DNS_CLASS_ANY) + return -EOPNOTSUPP; + + if (hashmap_size(s->manager->dns_transactions) >= TRANSACTIONS_MAX) + return -EBUSY; + + r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&s->transactions_by_key, &dns_resource_key_hash_ops); + if (r < 0) + return r; + + t = new0(DnsTransaction, 1); + if (!t) + return -ENOMEM; + + t->dns_udp_fd = -1; + t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; + t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + t->answer_nsec_ttl = (uint32_t) -1; + t->key = dns_resource_key_ref(key); + t->current_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; + + t->id = pick_new_id(s->manager); + + r = hashmap_put(s->manager->dns_transactions, UINT_TO_PTR(t->id), t); + if (r < 0) { + t->id = 0; + return r; + } + + r = hashmap_replace(s->transactions_by_key, t->key, t); + if (r < 0) { + hashmap_remove(s->manager->dns_transactions, UINT_TO_PTR(t->id)); + return r; + } + + LIST_PREPEND(transactions_by_scope, s->transactions, t); + t->scope = s; + + s->manager->n_transactions_total ++; + + if (ret) + *ret = t; + + t = NULL; + + return 0; +} + +static void dns_transaction_shuffle_id(DnsTransaction *t) { + uint16_t new_id; + assert(t); + + /* Pick a new ID for this transaction. */ + + new_id = pick_new_id(t->scope->manager); + assert_se(hashmap_remove_and_put(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id), UINT_TO_PTR(new_id), t) >= 0); + + log_debug("Transaction %" PRIu16 " is now %" PRIu16 ".", t->id, new_id); + t->id = new_id; + + /* Make sure we generate a new packet with the new ID */ + t->sent = dns_packet_unref(t->sent); +} + +static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { + _cleanup_free_ char *pretty = NULL; + DnsZoneItem *z; + + assert(t); + assert(p); + + if (manager_our_packet(t->scope->manager, p) != 0) + return; + + in_addr_to_string(p->family, &p->sender, &pretty); + + 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), + pretty); + + /* RFC 4795, Section 4.1 says that the peer with the + * lexicographically smaller IP address loses */ + if (memcmp(&p->sender, &p->destination, FAMILY_ADDRESS_SIZE(p->family)) >= 0) { + log_debug("Peer has lexicographically larger IP address and thus lost in the conflict."); + return; + } + + log_debug("We have the lexicographically larger IP address and thus lost in the conflict."); + + t->block_gc++; + while ((z = set_first(t->notify_zone_items))) { + /* First, make sure the zone item drops the reference + * to us */ + dns_zone_item_probe_stop(z); + + /* Secondly, report this as conflict, so that we might + * look for a different hostname */ + dns_zone_item_conflict(z); + } + t->block_gc--; + + dns_transaction_gc(t); +} + +void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { + DnsQueryCandidate *c; + 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. */ + + 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), + st, + t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source), + t->answer_authenticated ? "authenticated" : "unsigned"); + + t->state = state; + + 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); + + 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; + + assert(s); + assert(s->transaction); + + /* Copy the data we care about out of the stream before we + * destroy it. */ + t = s->transaction; + p = dns_packet_ref(s->read_packet); + + 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) { + t->answer_errno = error; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); + return 0; + } + + if (dns_packet_validate_reply(p) <= 0) { + log_debug("Invalid TCP reply packet."); + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return 0; + } + + dns_scope_check_conflicts(t->scope, p); + + t->block_gc++; + dns_transaction_process_reply(t, p); + t->block_gc--; + + /* 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) { + _cleanup_close_ int fd = -1; + int r; + + assert(t); + + dns_transaction_close_connection(t); + + switch (t->scope->protocol) { + + case DNS_PROTOCOL_DNS: + 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_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; + + /* Otherwise, try to talk to the owner of a + * the IP address, in case this is a reverse + * PTR lookup */ + + r = dns_name_address(DNS_RESOURCE_KEY_NAME(t->key), &family, &address); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + if (family != t->scope->family) + return -ESRCH; + + fd = dns_scope_socket_tcp(t->scope, family, &address, NULL, LLMNR_PORT); + } + + break; + + default: + return -EAFNOSUPPORT; + } + + if (fd < 0) + return fd; + + 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); + if (r < 0) { + t->stream = dns_stream_free(t->stream); + return r; + } + + t->stream->complete = on_stream_complete; + t->stream->transaction = t; + + /* The interface index is difficult to determine if we are + * connecting to the local host, hence fill this in right away + * instead of determining it from the socket */ + if (t->scope->link) + t->stream->ifindex = t->scope->link->ifindex; + + dns_transaction_reset_answer(t); + + t->tried_stream = true; + + return 0; +} + +static void dns_transaction_cache_answer(DnsTransaction *t) { + assert(t); + + /* For mDNS we cache whenever we get the packet, rather than + * in each transaction. */ + if (!IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) + return; + + /* We never cache if this packet is from the local host, under + * the assumption that a locally running DNS server would + * cache this anyway, and probably knows better when to flush + * the cache then we could. */ + if (!DNS_PACKET_SHALL_CACHE(t->received)) + return; + + dns_cache_put(&t->scope->cache, + t->key, + t->answer_rcode, + t->answer, + 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. */ + 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 == -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->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_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) { + usec_t ts; + int r; + + assert(t); + assert(p); + 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. */ + + log_debug("Processing incoming packet on transaction %" PRIu16".", t->id); + + switch (t->scope->protocol) { + + case DNS_PROTOCOL_LLMNR: + assert(t->scope->link); + + /* For LLMNR we will not accept any packets from other + * interfaces */ + + if (p->ifindex != t->scope->link->ifindex) + return; + + if (p->family != t->scope->family) + return; + + /* Tentative packets are not full responses but still + * useful for identifying uniqueness conflicts during + * probing. */ + if (DNS_PACKET_LLMNR_T(p)) { + dns_transaction_tentative(t, p); + return; + } + + break; + + case DNS_PROTOCOL_MDNS: + assert(t->scope->link); + + /* For mDNS we will not accept any packets from other interfaces */ + if (p->ifindex != t->scope->link->ifindex) + return; + + if (p->family != t->scope->family) + return; + + 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: + assert_not_reached("Invalid DNS protocol."); + } + + if (t->received != p) { + dns_packet_unref(t->received); + t->received = dns_packet_ref(p); + } + + t->answer_source = DNS_TRANSACTION_NETWORK; + + if (p->ipproto == IPPROTO_TCP) { + if (DNS_PACKET_TC(p)) { + /* Truncated via TCP? Somebody must be fucking with us */ + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + + if (DNS_PACKET_ID(p) != t->id) { + /* Not the reply to our query? Somebody must be fucking with us */ + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + } + + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + + switch (t->scope->protocol) { + + case DNS_PROTOCOL_DNS: + assert(t->server); + + if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) { + + /* 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_feature_level); + dns_transaction_retry(t); + return; + } else if (DNS_PACKET_TC(p)) + dns_server_packet_truncated(t->server, t->current_feature_level); + + break; + + case DNS_PROTOCOL_LLMNR: + case DNS_PROTOCOL_MDNS: + dns_scope_packet_received(t->scope, ts - t->start_usec); + break; + + default: + assert_not_reached("Invalid DNS protocol."); + } + + if (DNS_PACKET_TC(p)) { + + /* Truncated packets for mDNS are not allowed. Give up immediately. */ + if (t->scope->protocol == DNS_PROTOCOL_MDNS) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + 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) { + /* No servers found? Damn! */ + 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_DNS) + goto fail; + + /* On DNS, couldn't send? Try immediately again, with a new server */ + dns_transaction_retry(t); + } + + return; + } + + /* 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) + goto fail; + if (r == 0) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + + /* Install the answer as answer to the transaction */ + dns_answer_unref(t->answer); + t->answer = dns_answer_ref(p->answer); + t->answer_rcode = DNS_PACKET_RCODE(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); + 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) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + DnsTransaction *t = userdata; + int r; + + assert(t); + assert(t->scope); + + r = manager_recv(t->scope->manager, fd, DNS_PROTOCOL_DNS, &p); + if (ERRNO_IS_DISCONNECT(-r)) { + usec_t usec; + + /* 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_udp(DnsTransaction *t) { + int r; + + assert(t); + + if (t->scope->protocol == DNS_PROTOCOL_DNS) { + + r = dns_transaction_pick_server(t); + if (r < 0) + return r; + + if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_UDP) + return -EAGAIN; + + if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) + return -EOPNOTSUPP; + + if (r > 0 || t->dns_udp_fd < 0) { /* Server changed, or no connection yet. */ + int fd; + + 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; + + dns_transaction_reset_answer(t); + + return 0; +} + +static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) { + DnsTransaction *t = userdata; + + assert(s); + assert(t); + + 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->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."); + } + + if (t->initial_jitter_scheduled) + t->initial_jitter_elapsed = true; + } + + log_debug("Timeout reached on transaction %" PRIu16 ".", t->id); + + dns_transaction_retry(t); + return 0; +} + +static usec_t transaction_get_resend_timeout(DnsTransaction *t) { + assert(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) { + int r; + + assert(t); + + dns_transaction_stop_timeout(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 && 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); + return 0; + } + + t->n_attempts++; + t->start_usec = ts; + + dns_transaction_reset_answer(t); + dns_transaction_flush_dnssec_transactions(t); + + /* Check the trust anchor. Do so only on classic DNS, since DNSSEC does not apply otherwise. */ + if (t->scope->protocol == DNS_PROTOCOL_DNS) { + r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, t->key, &t->answer); + if (r < 0) + return r; + if (r > 0) { + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR; + t->answer_authenticated = true; + 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 + * for probing or verifying a zone item. */ + if (set_isempty(t->notify_zone_items)) { + + r = dns_zone_lookup(&t->scope->zone, t->key, &t->answer, NULL, NULL); + if (r < 0) + return r; + if (r > 0) { + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_source = DNS_TRANSACTION_ZONE; + t->answer_authenticated = true; + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + return 0; + } + } + + /* Check the cache, but only if this transaction is not used + * for probing or verifying a zone item. */ + if (set_isempty(t->notify_zone_items)) { + + /* Before trying the cache, let's make sure we figured out a + * server to use. Should this cause a change of server this + * might flush the cache. */ + dns_scope_get_dns_server(t->scope); + + /* Let's then prune all outdated entries */ + dns_cache_prune(&t->scope->cache); + + r = dns_cache_lookup(&t->scope->cache, t->key, &t->answer_rcode, &t->answer, &t->answer_authenticated); + if (r < 0) + return r; + if (r > 0) { + t->answer_source = DNS_TRANSACTION_CACHE; + if (t->answer_rcode == DNS_RCODE_SUCCESS) + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + else + dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); + return 0; + } + } + + return 1; +} + +static int dns_transaction_make_packet_mdns(DnsTransaction *t) { + + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + bool add_known_answers = false; + DnsTransaction *other; + unsigned qdcount; + usec_t ts; + int r; + + assert(t); + assert(t->scope->protocol == DNS_PROTOCOL_MDNS); + + /* 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); + if (r < 0) + return r; + + r = dns_packet_append_key(p, t->key, NULL); + if (r < 0) + return r; + + qdcount = 1; + + if (dns_key_is_shared(t->key)) + add_known_answers = true; + + /* + * For mDNS, we want to coalesce as many open queries in pending transactions into one single + * query packet on the wire as possible. To achieve that, we iterate through all pending transactions + * in our current scope, and see whether their timing contraints allow them to be sent. + */ + + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + + LIST_FOREACH(transactions_by_scope, other, t->scope->transactions) { + + /* Skip ourselves */ + if (other == t) + continue; + + if (other->state != DNS_TRANSACTION_PENDING) + continue; + + if (other->next_attempt_after > ts) + continue; + + if (qdcount >= UINT16_MAX) + break; + + r = dns_packet_append_key(p, other->key, NULL); + + /* + * If we can't stuff more questions into the packet, just give up. + * One of the 'other' transactions will fire later and take care of the rest. + */ + if (r == -EMSGSIZE) + break; + + if (r < 0) + return r; + + r = dns_transaction_prepare(other, ts); + if (r <= 0) + continue; + + ts += transaction_get_resend_timeout(other); + + r = sd_event_add_time( + other->scope->manager->event, + &other->timeout_event_source, + clock_boottime_or_monotonic(), + ts, 0, + on_transaction_timeout, other); + 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; + + qdcount ++; + + if (dns_key_is_shared(other->key)) + add_known_answers = true; + } + + DNS_PACKET_HEADER(p)->qdcount = htobe16(qdcount); + + /* Append known answer section if we're asking for any shared record */ + if (add_known_answers) { + r = dns_cache_export_shared_to_packet(&t->scope->cache, p); + if (r < 0) + return r; + } + + t->sent = p; + p = NULL; + + return 0; +} + +static int dns_transaction_make_packet(DnsTransaction *t) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + int r; + + assert(t); + + if (t->scope->protocol == DNS_PROTOCOL_MDNS) + return dns_transaction_make_packet_mdns(t); + + if (t->sent) + return 0; + + r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode != DNSSEC_NO); + if (r < 0) + return r; + + r = dns_packet_append_key(p, t->key, NULL); + if (r < 0) + return r; + + DNS_PACKET_HEADER(p)->qdcount = htobe16(1); + DNS_PACKET_HEADER(p)->id = t->id; + + t->sent = p; + p = NULL; + + return 0; +} + +int dns_transaction_go(DnsTransaction *t) { + usec_t ts; + int r; + + assert(t); + + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + + r = dns_transaction_prepare(t, ts); + if (r <= 0) + return r; + + 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 || + t->scope->protocol == DNS_PROTOCOL_MDNS)) { + usec_t jitter, accuracy; + + /* RFC 4795 Section 2.7 suggests all queries should be + * delayed by a random time from 0 to JITTER_INTERVAL. */ + + t->initial_jitter_scheduled = true; + + 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; + accuracy = MDNS_JITTER_RANGE_USEC; + break; + default: + assert_not_reached("bad protocol"); + } + + r = sd_event_add_time( + t->scope->manager->event, + &t->timeout_event_source, + clock_boottime_or_monotonic(), + ts + jitter, accuracy, + on_transaction_timeout, 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; + + log_debug("Delaying %s transaction for " USEC_FMT "us.", dns_protocol_to_string(t->scope->protocol), jitter); + return 0; + } + + /* Otherwise, we need to ask the network */ + r = dns_transaction_make_packet(t); + if (r < 0) + return r; + + if (t->scope->protocol == DNS_PROTOCOL_LLMNR && + (dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), "in-addr.arpa") > 0 || + dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), "ip6.arpa") > 0)) { + + /* RFC 4795, Section 2.4. says reverse lookups shall + * always be made via TCP on LLMNR */ + r = dns_transaction_open_tcp(t); + } else { + /* Try via UDP, and if that fails due to large size or lack of + * support try via TCP */ + r = dns_transaction_emit_udp(t); + if (r == -EMSGSIZE) + log_debug("Sending query via TCP since it is too large."); + if (r == -EAGAIN) + log_debug("Sending query via TCP since server doesn't support UDP."); + if (r == -EMSGSIZE || r == -EAGAIN) + r = dns_transaction_open_tcp(t); + } + + if (r == -ESRCH) { + /* No servers to send this to? */ + dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); + 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_scope_next_dns_server(t->scope); + + return dns_transaction_go(t); + } + + ts += transaction_get_resend_timeout(t); + + r = sd_event_add_time( + t->scope->manager->event, + &t->timeout_event_source, + clock_boottime_or_monotonic(), + ts, 0, + on_transaction_timeout, 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; + + assert(t); + assert(ret); + assert(key); + + aux = dns_scope_find_transaction(t->scope, key, true); + if (!aux) { + r = dns_transaction_new(&aux, t->scope, key); + if (r < 0) + return r; + } else { + if (set_contains(t->dnssec_transactions, aux)) { + *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); + if (r < 0) + goto gc; + + r = set_ensure_allocated(&aux->notify_transactions, NULL); + if (r < 0) + goto gc; + + r = set_put(t->dnssec_transactions, aux); + if (r < 0) + goto gc; + + r = set_put(aux->notify_transactions, t); + if (r < 0) { + (void) set_remove(t->dnssec_transactions, aux); + goto gc; + } + + *ret = aux; + return 1; + +gc: + dns_transaction_gc(aux); + return r; +} + +static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *key) { + _cleanup_(dns_answer_unrefp) DnsAnswer *a = NULL; + DnsTransaction *aux; + int r; + + assert(t); + assert(key); + + /* Try to get the data from the trust anchor */ + r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, key, &a); + if (r < 0) + return r; + if (r > 0) { + r = dns_answer_extend(&t->validated_keys, a); + if (r < 0) + return r; + + return 0; + } + + /* 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; + + if (aux->state == DNS_TRANSACTION_NULL) { + r = dns_transaction_go(aux); + if (r < 0) + return r; + } + + 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); + + /* + * 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: { + /* For each RRSIG we request the matching DNSKEY */ + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *dnskey = NULL; + + /* If this RRSIG is about a DNSKEY RR and the + * signer is the same as the owner, then we + * already have the DNSKEY, and we don't have + * to look for more. */ + if (rr->rrsig.type_covered == DNS_TYPE_DNSKEY) { + r = dns_name_equal(rr->rrsig.signer, DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r > 0) + continue; + } + + /* 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) + continue; + + dnskey = dns_resource_key_new(rr->key->class, DNS_TYPE_DNSKEY, rr->rrsig.signer); + if (!dnskey) + return -ENOMEM; + + 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; + break; + } + + case DNS_TYPE_DNSKEY: { + /* 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" (%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; + }} + } + + /* 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(rr); + + /* Checks if the RR we are looking for must be signed with an + * RRSIG. This is used for positive responses. */ + + 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; + } + + default: { + DnsTransaction *dt; + Iterator i; + + /* 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; + DnsAnswerFlags flags; + int r; + + assert(t); + + /* We have now collected all DS and DNSKEY RRs in + * t->validated_keys, let's see which RRs we can now + * authenticate with that. */ + + if (t->scope->dnssec_mode == DNSSEC_NO) + return 0; + + /* Already validated */ + 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->answer_dnssec_result = DNSSEC_VALIDATED; + t->answer_authenticated = true; + return 0; + } + + /* Cached stuff is not affected by validation. */ + if (t->answer_source != DNS_TRANSACTION_NETWORK) + return 0; + + 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; + } + + log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, dns_transaction_key_string(t)); + + /* First, see if this response contains any revoked trust + * anchors we care about */ + r = dns_transaction_check_revoked_trust_anchors(t); + if (r < 0) + return r; + + /* Third, copy all RRs we acquired successfully from auxiliary RRs over. */ + r = dns_transaction_copy_validated(t); + if (r < 0) + return r; + + /* Second, see if there are DNSKEYs we already know a + * validated DS for. */ + r = dns_transaction_validate_dnskey_by_ds(t); + if (r < 0) + return r; + + /* Fourth, remove all DNSKEY and DS RRs again that our trust + * anchor says are revoked. After all we might have marked + * some keys revoked above, but they might still be lingering + * in our validated_keys list. */ + r = dns_transaction_invalidate_revoked_keys(t); + if (r < 0) + return r; + + phase = PHASE_DNSKEY; + for (;;) { + bool changed = false, have_nsec = false; + + DNS_ANSWER_FOREACH(rr, t->answer) { + DnsResourceRecord *rrsig = NULL; + DnssecResult result; + + switch (rr->key->type) { + + case DNS_TYPE_RRSIG: + continue; + + case DNS_TYPE_DNSKEY: + /* We validate DNSKEYs only in the DNSKEY and ALL phases */ + if (phase == PHASE_NSEC) + continue; + break; + + case DNS_TYPE_NSEC: + case DNS_TYPE_NSEC3: + have_nsec = true; + + /* We validate NSEC/NSEC3 only in the NSEC and ALL phases */ + if (phase == PHASE_DNSKEY) + continue; + + break; + + default: + /* We validate all other RRs only in the ALL phases */ + if (phase != PHASE_ALL) + continue; + + break; + } + + r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result, &rrsig); + if (r < 0) + return r; + + log_debug("Looking at %s: %s", strna(dns_resource_record_to_string(rr)), dnssec_result_to_string(result)); + + if (result == DNSSEC_VALIDATED) { + + if (rr->key->type == DNS_TYPE_DNSKEY) { + /* If we just validated a + * DNSKEY RRset, then let's + * add these keys to the set + * of validated keys for this + * transaction. */ + + r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + /* some of the DNSKEYs we just + * added might already have + * been revoked, remove them + * again in that case. */ + r = dns_transaction_invalidate_revoked_keys(t); + if (r < 0) + return r; + } + + /* Add the validated RRset to the new + * 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; + + if (result == DNSSEC_VALIDATED_WILDCARD) { + bool authenticated = false; + const char *source; + + /* 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; + + 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; + } + + r = dns_transaction_known_signed(t, rr); + if (r < 0) + return r; + if (r > 0) { + /* This is an RR we know has to be signed. If it isn't this means + * the server is not attaching RRSIGs, hence complain. */ + + 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; + } + + r = dns_transaction_in_private_tld(t, rr->key); + if (r < 0) + return r; + if (r > 0) { + _cleanup_free_ char *s = NULL; + + /* The data is from a TLD that is proven not to exist, and we are in downgrade + * mode, hence ignore the fact that this was not signed. */ + + (void) dns_resource_key_to_string(rr->key, &s); + log_info("Detected RRset %s is in a private DNS zone, permitting unsigned RRs.", strna(s ? strstrip(s) : NULL)); + + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); + changed = true; + break; + } + } + + if (IN_SET(result, + DNSSEC_MISSING_KEY, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_UNSUPPORTED_ALGORITHM)) { + + r = dns_transaction_dnskey_authenticated(t, rr); + if (r < 0 && r != -ENXIO) + return r; + if (r == 0) { + /* The DNSKEY transaction was not authenticated, this means there's + * no DS for this, which means it's OK if no keys are found for this signature. */ + + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + 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) { + /* Also look among the stuff we already validated */ + r = dns_answer_has_dname_for_cname(validated, rr); + if (r < 0) + return r; + } + + if (r == 0) { + 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; + } + + /* This is a primary response, but we do have a DNAME RR in the RR that can replay this + * CNAME, hence rely on that, and we can remove the CNAME in favour of it. */ + } + + /* This is just some auxiliary data. Just remove the RRset and continue. */ + r = dns_answer_remove_by_key(&t->answer, rr->key); + if (r < 0) + return r; + + /* Exit the loop, we dropped something from the answer, start from the beginning */ + changed = true; + break; + } + + /* Restart the inner loop as long as we managed to achieve something */ + if (changed) + continue; + + if (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; + } + + dns_answer_unref(t->answer); + t->answer = validated; + validated = NULL; + + /* 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_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_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); + +static const char* const dns_transaction_source_table[_DNS_TRANSACTION_SOURCE_MAX] = { + [DNS_TRANSACTION_NETWORK] = "network", + [DNS_TRANSACTION_CACHE] = "cache", + [DNS_TRANSACTION_ZONE] = "zone", + [DNS_TRANSACTION_TRUST_ANCHOR] = "trust-anchor", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_transaction_source, DnsTransactionSource); diff --git a/src/resolve/src/resolved-dns-transaction.h b/src/resolve/src/resolved-dns-transaction.h new file mode 100644 index 0000000000..4617194711 --- /dev/null +++ b/src/resolve/src/resolved-dns-transaction.h @@ -0,0 +1,177 @@ +#pragma once + +/*** + 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 . +***/ + +typedef struct DnsTransaction DnsTransaction; +typedef enum DnsTransactionState DnsTransactionState; +typedef enum DnsTransactionSource DnsTransactionSource; + +enum DnsTransactionState { + DNS_TRANSACTION_NULL, + DNS_TRANSACTION_PENDING, + DNS_TRANSACTION_VALIDATING, + 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_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 +}; + +#define DNS_TRANSACTION_IS_LIVE(state) IN_SET((state), DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING) + +enum DnsTransactionSource { + DNS_TRANSACTION_NETWORK, + DNS_TRANSACTION_CACHE, + DNS_TRANSACTION_ZONE, + DNS_TRANSACTION_TRUST_ANCHOR, + _DNS_TRANSACTION_SOURCE_MAX, + _DNS_TRANSACTION_SOURCE_INVALID = -1 +}; + +#include "resolved-dns-answer.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-question.h" +#include "resolved-dns-scope.h" + +struct DnsTransaction { + DnsScope *scope; + + DnsResourceKey *key; + char *key_string; + + DnsTransactionState state; + + uint16_t id; + + 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 DNSKEY, DS, SOA RRs we already verified and need + * to authenticate this reply */ + DnsAnswer *validated_keys; + + usec_t start_usec; + usec_t next_attempt_after; + 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_feature_level; + + /* Query candidates this transaction is referenced by and that + * shall be notified about this specific transaction + * completing. */ + Set *notify_query_candidates; + + /* Zone items this transaction is referenced by and that shall + * be notified about completion. */ + Set *notify_zone_items; + + /* Other transactions that this transactions is referenced by + * and that shall be notified about completion. This is used + * when transactions want to validate their RRsets, but need + * another DNSKEY or DS RR to do so. */ + Set *notify_transactions; + + /* The opposite direction: the transactions this transaction + * created in order to request DNSKEY or DS RRs. */ + Set *dnssec_transactions; + + unsigned block_gc; + + LIST_FIELDS(DnsTransaction, transactions_by_scope); +}; + +int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key); +DnsTransaction* dns_transaction_free(DnsTransaction *t); + +bool dns_transaction_gc(DnsTransaction *t); +int dns_transaction_go(DnsTransaction *t); + +void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p); +void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state); + +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_; + +const char* dns_transaction_source_to_string(DnsTransactionSource p) _const_; +DnsTransactionSource dns_transaction_source_from_string(const char *s) _pure_; + +/* LLMNR Jitter interval, see RFC 4795 Section 7 */ +#define LLMNR_JITTER_INTERVAL_USEC (100 * USEC_PER_MSEC) + +/* mDNS Jitter interval, see RFC 6762 Section 5.2 */ +#define MDNS_JITTER_MIN_USEC (20 * USEC_PER_MSEC) +#define MDNS_JITTER_RANGE_USEC (100 * USEC_PER_MSEC) + +/* Maximum attempts to send DNS requests, across all DNS servers */ +#define DNS_TRANSACTION_ATTEMPTS_MAX 16 + +/* Maximum attempts to send LLMNR requests, see RFC 4795 Section 2.7 */ +#define LLMNR_TRANSACTION_ATTEMPTS_MAX 3 + +#define TRANSACTION_ATTEMPTS_MAX(p) ((p) == DNS_PROTOCOL_LLMNR ? LLMNR_TRANSACTION_ATTEMPTS_MAX : DNS_TRANSACTION_ATTEMPTS_MAX) diff --git a/src/resolve/src/resolved-dns-trust-anchor.c b/src/resolve/src/resolved-dns-trust-anchor.c new file mode 100644 index 0000000000..a75337eb6a --- /dev/null +++ b/src/resolve/src/resolved-dns-trust-anchor.c @@ -0,0 +1,743 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include + +#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, retrieved December 2015 */ +static const uint8_t root_digest[] = + { 0x49, 0xAA, 0xC1, 0x1D, 0x7B, 0x6F, 0x64, 0x46, 0x70, 0x2E, 0x54, 0xA1, 0x60, 0x73, 0x71, 0x60, + 0x7A, 0x1A, 0x41, 0x85, 0x52, 0x00, 0xFD, 0x2C, 0xE1, 0xCD, 0xDE, 0x32, 0xF2, 0x4E, 0x8F, 0xB5 }; + +static 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->positive_by_key, &dns_resource_key_hash_ops); + if (r < 0) + return r; + + /* 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 */ + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, ""); + if (!rr) + return -ENOMEM; + + rr->ds.key_tag = 19036; + rr->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; + rr->ds.digest_type = DNSSEC_DIGEST_SHA256; + rr->ds.digest_size = sizeof(root_digest); + rr->ds.digest = memdup(root_digest, rr->ds.digest_size); + if (!rr->ds.digest) + return -ENOMEM; + + answer = dns_answer_new(1); + if (!answer) + return -ENOMEM; + + 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; + + 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->positive_by_key))) + dns_answer_unref(a); + d->positive_by_key = hashmap_free(d->positive_by_key); + + while ((rr = set_steal_first(d->revoked_by_rr))) + dns_resource_record_unref(rr); + d->revoked_by_rr = set_free(d->revoked_by_rr); + + d->negative_by_name = set_free_free(d->negative_by_name); +} + +int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey *key, DnsAnswer **ret) { + DnsAnswer *a; + + assert(d); + assert(key); + assert(ret); + + /* We only serve DS and DNSKEY RRs. */ + if (!IN_SET(key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY)) + return 0; + + 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/src/resolved-dns-trust-anchor.h b/src/resolve/src/resolved-dns-trust-anchor.h new file mode 100644 index 0000000000..635c75fde5 --- /dev/null +++ b/src/resolve/src/resolved-dns-trust-anchor.h @@ -0,0 +1,43 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +typedef struct DnsTrustAnchor DnsTrustAnchor; + +#include "hashmap.h" +#include "resolved-dns-answer.h" +#include "resolved-dns-rr.h" + +/* This contains a fixed database mapping domain names to DS or DNSKEY records. */ + +struct DnsTrustAnchor { + 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_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/src/resolved-dns-zone.c b/src/resolve/src/resolved-dns-zone.c new file mode 100644 index 0000000000..f52383cfd1 --- /dev/null +++ b/src/resolve/src/resolved-dns-zone.c @@ -0,0 +1,656 @@ +/*** + 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 . +***/ + +#include "alloc-util.h" +#include "dns-domain.h" +#include "list.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-zone.h" +#include "string-util.h" + +/* Never allow more than 1K entries */ +#define ZONE_MAX 1024 + +void dns_zone_item_probe_stop(DnsZoneItem *i) { + DnsTransaction *t; + assert(i); + + if (!i->probe_transaction) + return; + + t = i->probe_transaction; + i->probe_transaction = NULL; + + set_remove(t->notify_zone_items, i); + dns_transaction_gc(t); +} + +static void dns_zone_item_free(DnsZoneItem *i) { + if (!i) + return; + + dns_zone_item_probe_stop(i); + dns_resource_record_unref(i->rr); + + free(i); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free); + +static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) { + DnsZoneItem *first; + + assert(z); + + if (!i) + return; + + first = hashmap_get(z->by_key, i->rr->key); + LIST_REMOVE(by_key, first, i); + if (first) + assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0); + else + hashmap_remove(z->by_key, i->rr->key); + + first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key)); + LIST_REMOVE(by_name, first, i); + if (first) + assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0); + else + hashmap_remove(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key)); + + dns_zone_item_free(i); +} + +void dns_zone_flush(DnsZone *z) { + DnsZoneItem *i; + + assert(z); + + while ((i = hashmap_first(z->by_key))) + dns_zone_item_remove_and_free(z, i); + + assert(hashmap_size(z->by_key) == 0); + assert(hashmap_size(z->by_name) == 0); + + z->by_key = hashmap_free(z->by_key); + z->by_name = hashmap_free(z->by_name); +} + +static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) { + DnsZoneItem *i; + + assert(z); + assert(rr); + + LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key)) + if (dns_resource_record_equal(i->rr, rr) > 0) + return i; + + return NULL; +} + +void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) { + DnsZoneItem *i; + + assert(z); + assert(rr); + + i = dns_zone_get(z, rr); + if (i) + dns_zone_item_remove_and_free(z, i); +} + +static int dns_zone_init(DnsZone *z) { + int r; + + assert(z); + + r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops); + if (r < 0) + return r; + + return 0; +} + +static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) { + DnsZoneItem *first; + int r; + + first = hashmap_get(z->by_key, i->rr->key); + if (first) { + LIST_PREPEND(by_key, first, i); + assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0); + } else { + r = hashmap_put(z->by_key, i->rr->key, i); + if (r < 0) + return r; + } + + first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key)); + if (first) { + LIST_PREPEND(by_name, first, i); + assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0); + } else { + r = hashmap_put(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key), i); + if (r < 0) + return r; + } + + return 0; +} + +static int dns_zone_item_probe_start(DnsZoneItem *i) { + DnsTransaction *t; + int r; + + assert(i); + + if (i->probe_transaction) + return 0; + + t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key)), false); + if (!t) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key)); + if (!key) + return -ENOMEM; + + r = dns_transaction_new(&t, i->scope, key); + if (r < 0) + return r; + } + + r = set_ensure_allocated(&t->notify_zone_items, NULL); + if (r < 0) + goto gc; + + r = set_put(t->notify_zone_items, i); + if (r < 0) + goto gc; + + i->probe_transaction = t; + + if (t->state == DNS_TRANSACTION_NULL) { + + i->block_ready++; + r = dns_transaction_go(t); + i->block_ready--; + + if (r < 0) { + dns_zone_item_probe_stop(i); + return r; + } + } + + dns_zone_item_notify(i); + return 0; + +gc: + dns_transaction_gc(t); + return r; +} + +int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) { + _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL; + DnsZoneItem *existing; + int r; + + assert(z); + assert(s); + assert(rr); + + if (dns_class_is_pseudo(rr->key->class)) + return -EINVAL; + if (dns_type_is_pseudo(rr->key->type)) + return -EINVAL; + + existing = dns_zone_get(z, rr); + if (existing) + return 0; + + r = dns_zone_init(z); + if (r < 0) + return r; + + i = new0(DnsZoneItem, 1); + if (!i) + return -ENOMEM; + + i->scope = s; + i->rr = dns_resource_record_ref(rr); + i->probing_enabled = probe; + + r = dns_zone_link_item(z, i); + if (r < 0) + return r; + + if (probe) { + DnsZoneItem *first, *j; + bool established = false; + + /* Check if there's already an RR with the same name + * established. If so, it has been probed already, and + * we don't ned to probe again. */ + + LIST_FIND_HEAD(by_name, i, first); + LIST_FOREACH(by_name, j, first) { + if (i == j) + continue; + + if (j->state == DNS_ZONE_ITEM_ESTABLISHED) + established = true; + } + + if (established) + i->state = DNS_ZONE_ITEM_ESTABLISHED; + else { + i->state = DNS_ZONE_ITEM_PROBING; + + r = dns_zone_item_probe_start(i); + if (r < 0) { + dns_zone_item_remove_and_free(z, i); + i = NULL; + return r; + } + } + } else + i->state = DNS_ZONE_ITEM_ESTABLISHED; + + i = NULL; + return 0; +} + +int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, DnsAnswer **ret_soa, bool *ret_tentative) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL; + unsigned n_answer = 0; + DnsZoneItem *j, *first; + bool tentative = true, need_soa = false; + int r; + + assert(z); + assert(key); + assert(ret_answer); + + /* First iteration, count what we have */ + + if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { + bool found = false, added = false; + int k; + + /* If this is a generic match, then we have to + * go through the list by the name and look + * for everything manually */ + + first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key)); + LIST_FOREACH(by_name, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + found = true; + + k = dns_resource_key_match_rr(key, j->rr, NULL); + if (k < 0) + return k; + if (k > 0) { + n_answer++; + added = true; + } + + } + + if (found && !added) + need_soa = true; + + } else { + bool found = false; + + /* If this is a specific match, then look for + * the right key immediately */ + + first = hashmap_get(z->by_key, key); + LIST_FOREACH(by_key, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + found = true; + n_answer++; + } + + if (!found) { + first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key)); + LIST_FOREACH(by_name, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + need_soa = true; + break; + } + } + } + + if (n_answer <= 0 && !need_soa) + goto return_empty; + + if (n_answer > 0) { + answer = dns_answer_new(n_answer); + if (!answer) + return -ENOMEM; + } + + if (need_soa) { + soa = dns_answer_new(1); + if (!soa) + return -ENOMEM; + } + + /* Second iteration, actually add the RRs to the answers */ + if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { + bool found = false, added = false; + int k; + + first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key)); + LIST_FOREACH(by_name, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + found = true; + + if (j->state != DNS_ZONE_ITEM_PROBING) + tentative = false; + + k = dns_resource_key_match_rr(key, j->rr, NULL); + if (k < 0) + return k; + if (k > 0) { + r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + added = true; + } + } + + if (found && !added) { + r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(key), LLMNR_DEFAULT_TTL); + if (r < 0) + return r; + } + } else { + bool found = false; + + first = hashmap_get(z->by_key, key); + LIST_FOREACH(by_key, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + found = true; + + if (j->state != DNS_ZONE_ITEM_PROBING) + tentative = false; + + r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + if (!found) { + bool add_soa = false; + + first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key)); + LIST_FOREACH(by_name, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + if (j->state != DNS_ZONE_ITEM_PROBING) + tentative = false; + + add_soa = true; + } + + if (add_soa) { + r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(key), LLMNR_DEFAULT_TTL); + if (r < 0) + return r; + } + } + } + + /* If the caller sets ret_tentative to NULL, then use this as + * indication to not return tentative entries */ + + if (!ret_tentative && tentative) + goto return_empty; + + *ret_answer = answer; + answer = NULL; + + if (ret_soa) { + *ret_soa = soa; + soa = NULL; + } + + if (ret_tentative) + *ret_tentative = tentative; + + return 1; + +return_empty: + *ret_answer = NULL; + + if (ret_soa) + *ret_soa = NULL; + + if (ret_tentative) + *ret_tentative = false; + + return 0; +} + +void dns_zone_item_conflict(DnsZoneItem *i) { + assert(i); + + if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED)) + return; + + log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr))); + + dns_zone_item_probe_stop(i); + + /* Withdraw the conflict item */ + i->state = DNS_ZONE_ITEM_WITHDRAWN; + + /* Maybe change the hostname */ + if (manager_is_own_hostname(i->scope->manager, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0) + manager_next_hostname(i->scope->manager); +} + +void dns_zone_item_notify(DnsZoneItem *i) { + assert(i); + assert(i->probe_transaction); + + if (i->block_ready > 0) + return; + + if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)) + return; + + if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) { + bool we_lost = false; + + /* The probe got a successful reply. If we so far + * weren't established we just give up. If we already + * were established, and the peer has the + * lexicographically larger IP address we continue + * and defend it. */ + + if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) { + log_debug("Got a successful probe for not yet established RR, we lost."); + we_lost = true; + } else { + assert(i->probe_transaction->received); + we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0; + if (we_lost) + log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost."); + } + + if (we_lost) { + dns_zone_item_conflict(i); + return; + } + + log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost."); + } + + 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) { + int r; + + assert(i); + + if (i->state != DNS_ZONE_ITEM_ESTABLISHED) + return 0; + + 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); + if (r < 0) { + log_error_errno(r, "Failed to start probing for verifying RR: %m"); + i->state = DNS_ZONE_ITEM_ESTABLISHED; + return r; + } + + return 0; +} + +int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) { + DnsZoneItem *i, *first; + int c = 0; + + assert(zone); + assert(rr); + + /* This checks whether a response RR we received from somebody + * else is one that we actually thought was uniquely ours. If + * so, we'll verify our RRs. */ + + /* No conflict if we don't have the name at all. */ + first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(rr->key)); + if (!first) + return 0; + + /* No conflict if we have the exact same RR */ + if (dns_zone_get(zone, rr)) + return 0; + + /* OK, somebody else has RRs for the same name. Yuck! Let's + * start probing again */ + + LIST_FOREACH(by_name, i, first) { + if (dns_resource_record_equal(i->rr, rr)) + continue; + + dns_zone_item_verify(i); + c++; + } + + return c; +} + +int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) { + DnsZoneItem *i, *first; + int c = 0; + + assert(zone); + + /* Somebody else notified us about a possible conflict. Let's + * verify if that's true. */ + + first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(key)); + if (!first) + return 0; + + LIST_FOREACH(by_name, i, first) { + dns_zone_item_verify(i); + c++; + } + + return c; +} + +void dns_zone_verify_all(DnsZone *zone) { + DnsZoneItem *i; + Iterator iterator; + + assert(zone); + + HASHMAP_FOREACH(i, zone->by_key, iterator) { + DnsZoneItem *j; + + LIST_FOREACH(by_key, j, i) + dns_zone_item_verify(j); + } +} + +void dns_zone_dump(DnsZone *zone, FILE *f) { + Iterator iterator; + DnsZoneItem *i; + + if (!zone) + return; + + if (!f) + f = stdout; + + HASHMAP_FOREACH(i, zone->by_key, iterator) { + DnsZoneItem *j; + + LIST_FOREACH(by_key, j, i) { + const char *t; + + t = dns_resource_record_to_string(j->rr); + if (!t) { + log_oom(); + continue; + } + + fputc('\t', f); + fputs(t, f); + fputc('\n', f); + } + } +} + +bool dns_zone_is_empty(DnsZone *zone) { + if (!zone) + return true; + + return hashmap_isempty(zone->by_key); +} diff --git a/src/resolve/src/resolved-dns-zone.h b/src/resolve/src/resolved-dns-zone.h new file mode 100644 index 0000000000..408833c359 --- /dev/null +++ b/src/resolve/src/resolved-dns-zone.h @@ -0,0 +1,81 @@ +#pragma once + +/*** + 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 . +***/ + +#include "hashmap.h" + +typedef struct DnsZone { + Hashmap *by_key; + Hashmap *by_name; +} DnsZone; + +typedef struct DnsZoneItem DnsZoneItem; +typedef enum DnsZoneItemState DnsZoneItemState; + +#include "resolved-dns-answer.h" +#include "resolved-dns-question.h" +#include "resolved-dns-rr.h" +#include "resolved-dns-transaction.h" + +/* RFC 4795 Section 2.8. suggests a TTL of 30s by default */ +#define LLMNR_DEFAULT_TTL (30) + +enum DnsZoneItemState { + DNS_ZONE_ITEM_PROBING, + DNS_ZONE_ITEM_ESTABLISHED, + DNS_ZONE_ITEM_VERIFYING, + DNS_ZONE_ITEM_WITHDRAWN, +}; + +struct DnsZoneItem { + DnsScope *scope; + DnsResourceRecord *rr; + + DnsZoneItemState state; + + unsigned block_ready; + + bool probing_enabled; + + LIST_FIELDS(DnsZoneItem, by_key); + LIST_FIELDS(DnsZoneItem, by_name); + + DnsTransaction *probe_transaction; +}; + +void dns_zone_flush(DnsZone *z); + +int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe); +void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr); + +int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **answer, DnsAnswer **soa, bool *tentative); + +void dns_zone_item_conflict(DnsZoneItem *i); +void dns_zone_item_notify(DnsZoneItem *i); + +int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr); +int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key); + +void dns_zone_verify_all(DnsZone *zone); + +void dns_zone_item_probe_stop(DnsZoneItem *i); + +void dns_zone_dump(DnsZone *zone, FILE *f); +bool dns_zone_is_empty(DnsZone *zone); diff --git a/src/resolve/src/resolved-etc-hosts.c b/src/resolve/src/resolved-etc-hosts.c new file mode 100644 index 0000000000..ee82c96822 --- /dev/null +++ b/src/resolve/src/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 . +***/ + +#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/src/resolved-etc-hosts.h b/src/resolve/src/resolved-etc-hosts.h new file mode 100644 index 0000000000..9d5a175f18 --- /dev/null +++ b/src/resolve/src/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 . +***/ + +#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/src/resolved-gperf.gperf b/src/resolve/src/resolved-gperf.gperf new file mode 100644 index 0000000000..82f26215df --- /dev/null +++ b/src/resolve/src/resolved-gperf.gperf @@ -0,0 +1,21 @@ +%{ +#include +#include "conf-parser.h" +#include "resolved-conf.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name resolved_gperf_hash +%define lookup-function-name resolved_gperf_lookup +%readonly-tables +%omit-struct-type +%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_resolve_support, 0, offsetof(Manager, llmnr_support) +Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode) diff --git a/src/resolve/src/resolved-link-bus.c b/src/resolve/src/resolved-link-bus.c new file mode 100644 index 0000000000..df7516f4f4 --- /dev/null +++ b/src/resolve/src/resolved-link-bus.c @@ -0,0 +1,550 @@ +/*** + 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 . +***/ + +#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/src/resolved-link-bus.h b/src/resolve/src/resolved-link-bus.h new file mode 100644 index 0000000000..31e6cd2b45 --- /dev/null +++ b/src/resolve/src/resolved-link-bus.h @@ -0,0 +1,38 @@ +#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 . +***/ + +#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/src/resolved-link.c b/src/resolve/src/resolved-link.c new file mode 100644 index 0000000000..c5863b3aa2 --- /dev/null +++ b/src/resolve/src/resolved-link.c @@ -0,0 +1,840 @@ +/*** + 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 . +***/ + +#include + +#include "sd-network.h" + +#include "alloc-util.h" +#include "missing.h" +#include "parse-util.h" +#include "resolved-link.h" +#include "string-util.h" +#include "strv.h" + +int link_new(Manager *m, Link **ret, int ifindex) { + _cleanup_(link_freep) Link *l = NULL; + int r; + + assert(m); + assert(ifindex > 0); + + r = hashmap_ensure_allocated(&m->links, NULL); + if (r < 0) + return r; + + l = new0(Link, 1); + if (!l) + return -ENOMEM; + + l->ifindex = ifindex; + 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) + return r; + + l->manager = m; + + if (ret) + *ret = l; + l = NULL; + + 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; + + link_flush_settings(l); + + while (l->addresses) + (void) link_address_free(l->addresses); + + if (l->manager) + hashmap_remove(l->manager->links, INT_TO_PTR(l->ifindex)); + + dns_scope_free(l->unicast_scope); + dns_scope_free(l->llmnr_ipv4_scope); + dns_scope_free(l->llmnr_ipv6_scope); + dns_scope_free(l->mdns_ipv4_scope); + dns_scope_free(l->mdns_ipv6_scope); + + free(l); + return NULL; +} + +void link_allocate_scopes(Link *l) { + int r; + + assert(l); + + 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) + log_warning_errno(r, "Failed to allocate DNS scope: %m"); + } + } else + l->unicast_scope = dns_scope_free(l->unicast_scope); + + 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) + log_warning_errno(r, "Failed to allocate LLMNR IPv4 scope: %m"); + } + } else + l->llmnr_ipv4_scope = dns_scope_free(l->llmnr_ipv4_scope); + + 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); + if (r < 0) + log_warning_errno(r, "Failed to allocate LLMNR IPv6 scope: %m"); + } + } else + l->llmnr_ipv6_scope = dns_scope_free(l->llmnr_ipv6_scope); + + 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) + log_warning_errno(r, "Failed to allocate mDNS IPv4 scope: %m"); + } + } else + l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope); + + 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) + log_warning_errno(r, "Failed to allocate mDNS IPv6 scope: %m"); + } + } else + l->mdns_ipv6_scope = dns_scope_free(l->mdns_ipv6_scope); +} + +void link_add_rrs(Link *l, bool force_remove) { + LinkAddress *a; + + LIST_FOREACH(addresses, a, l->addresses) + link_address_add_rrs(a, force_remove); +} + +int link_update_rtnl(Link *l, sd_netlink_message *m) { + const char *n = NULL; + int r; + + assert(l); + assert(m); + + r = sd_rtnl_message_link_get_flags(m, &l->flags); + if (r < 0) + return r; + + (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); + char_array_0(l->name); + } + + link_allocate_scopes(l); + link_add_rrs(l, false); + + return 0; +} + +static int link_update_dns_servers(Link *l) { + _cleanup_strv_free_ char **nameservers = NULL; + char **nameserver; + int r; + + assert(l); + + r = sd_network_link_get_dns(l->ifindex, &nameservers); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + dns_server_mark_all(l->dns_servers); + + STRV_FOREACH(nameserver, nameservers) { + union in_addr_union a; + DnsServer *s; + int family; + + r = in_addr_from_string_auto(*nameserver, &family, &a); + if (r < 0) + goto clear; + + s = dns_server_find(l->dns_servers, family, &a); + if (s) + dns_server_move_back_and_unmark(s); + else { + r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a); + if (r < 0) + goto clear; + } + } + + dns_server_unlink_marked(l->dns_servers); + return 0; + +clear: + dns_server_unlink_all(l->dns_servers); + return r; +} + +static int link_update_llmnr_support(Link *l) { + _cleanup_free_ char *b = NULL; + int r; + + assert(l); + + r = sd_network_link_get_llmnr(l->ifindex, &b); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + l->llmnr_support = resolve_support_from_string(b); + if (l->llmnr_support < 0) { + r = -EINVAL; + goto clear; + } + + return 0; + +clear: + l->llmnr_support = RESOLVE_SUPPORT_YES; + return r; +} + +static int link_update_mdns_support(Link *l) { + _cleanup_free_ char *b = NULL; + int r; + + assert(l); + + r = sd_network_link_get_mdns(l->ifindex, &b); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + l->mdns_support = resolve_support_from_string(b); + if (l->mdns_support < 0) { + r = -EINVAL; + goto clear; + } + + return 0; + +clear: + l->mdns_support = RESOLVE_SUPPORT_NO; + return r; +} + +void link_set_dnssec_mode(Link *l, DnssecMode mode) { + + assert(l); + + if (l->dnssec_mode == mode) + return; + + if ((l->dnssec_mode == _DNSSEC_MODE_INVALID) || + (l->dnssec_mode == DNSSEC_NO && mode != DNSSEC_NO) || + (l->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE && mode == DNSSEC_YES)) { + + /* When switching from non-DNSSEC mode to DNSSEC mode, flush the cache. Also when switching from the + * allow-downgrade mode to full DNSSEC mode, flush it too. */ + if (l->unicast_scope) + dns_cache_flush(&l->unicast_scope->cache); + } + + l->dnssec_mode = mode; +} + +static int link_update_dnssec_mode(Link *l) { + _cleanup_free_ char *m = NULL; + DnssecMode mode; + int r; + + assert(l); + + r = sd_network_link_get_dnssec(l->ifindex, &m); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + mode = dnssec_mode_from_string(m); + if (mode < 0) { + r = -EINVAL; + goto clear; + } + + link_set_dnssec_mode(l, mode); + + return 0; + +clear: + l->dnssec_mode = _DNSSEC_MODE_INVALID; + return r; +} + +static int link_update_dnssec_negative_trust_anchors(Link *l) { + _cleanup_strv_free_ char **ntas = NULL; + _cleanup_set_free_free_ Set *ns = NULL; + char **i; + int r; + + assert(l); + + r = sd_network_link_get_dnssec_negative_trust_anchors(l->ifindex, &ntas); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + ns = set_new(&dns_name_hash_ops); + if (!ns) + return -ENOMEM; + + STRV_FOREACH(i, ntas) { + r = set_put_strdup(ns, *i); + if (r < 0) + return r; + } + + set_free_free(l->dnssec_negative_trust_anchors); + l->dnssec_negative_trust_anchors = ns; + ns = NULL; + + return 0; + +clear: + l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors); + return r; +} + +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; + } + + d->route_only = route_only; + return 0; +} + +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; + } + + 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); + return 0; + +clear: + dns_search_domain_unlink_all(l->search_domains); + return r; +} + +static int link_is_unmanaged(Link *l) { + _cleanup_free_ char *state = NULL; + int r; + + assert(l); + + r = sd_network_link_get_setup_state(l->ifindex, &state); + if (r == -ENODATA) + return 1; + if (r < 0) + return r; + + return STR_IN_SET(state, "pending", "unmanaged"); +} + +static void link_read_settings(Link *l) { + int r; + + assert(l); + + /* Read settings from networkd, except when networkd is not managing this interface. */ + + r = link_is_unmanaged(l); + if (r < 0) { + log_warning_errno(r, "Failed to determine whether interface %s is managed: %m", l->name); + return; + } + if (r > 0) { + + /* If this link used to be managed, but is now unmanaged, flush all our settings -- but only once. */ + if (l->is_managed) + link_flush_settings(l); + + l->is_managed = false; + return; + } + + l->is_managed = true; + + r = link_update_dns_servers(l); + if (r < 0) + log_warning_errno(r, "Failed to read DNS servers for interface %s, ignoring: %m", l->name); + + 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 local_multicast) { + _cleanup_free_ char *state = NULL; + LinkAddress *a; + + assert(l); + + /* 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_DORMANT)) + return false; + + if ((l->flags & (IFF_UP|IFF_LOWER_UP)) != (IFF_UP|IFF_LOWER_UP)) + return false; + + 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 ((family == AF_UNSPEC || a->family == family) && link_address_relevant(a, local_multicast)) + return true; + + return false; +} + +LinkAddress *link_find_address(Link *l, int family, const union in_addr_union *in_addr) { + LinkAddress *a; + + assert(l); + + LIST_FOREACH(addresses, a, l->addresses) + if (a->family == family && in_addr_equal(family, &a->in_addr, in_addr)) + return a; + + return NULL; +} + +DnsServer* link_set_dns_server(Link *l, DnsServer *s) { + assert(l); + + if (l->current_dns_server == s) + return s; + + 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); + + if (l->unicast_scope) + dns_cache_flush(&l->unicast_scope->cache); + + return s; +} + +DnsServer *link_get_dns_server(Link *l) { + assert(l); + + if (!l->current_dns_server) + link_set_dns_server(l, l->dns_servers); + + return l->current_dns_server; +} + +void link_next_dns_server(Link *l) { + assert(l); + + if (!l->current_dns_server) + return; + + /* Change to the next one, but make sure to follow the linked + * list only if this server is actually still linked. */ + if (l->current_dns_server->linked && l->current_dns_server->servers_next) { + link_set_dns_server(l, l->current_dns_server->servers_next); + return; + } + + 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; + + assert(l); + assert(in_addr); + + a = new0(LinkAddress, 1); + if (!a) + return -ENOMEM; + + a->family = family; + a->in_addr = *in_addr; + + a->link = l; + LIST_PREPEND(addresses, l->addresses, a); + + if (ret) + *ret = a; + + return 0; +} + +LinkAddress *link_address_free(LinkAddress *a) { + if (!a) + return NULL; + + if (a->link) { + LIST_REMOVE(addresses, a->link->addresses, a); + + if (a->llmnr_address_rr) { + if (a->family == AF_INET && a->link->llmnr_ipv4_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr); + else if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_address_rr); + } + + if (a->llmnr_ptr_rr) { + if (a->family == AF_INET && a->link->llmnr_ipv4_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_ptr_rr); + else if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr); + } + } + + dns_resource_record_unref(a->llmnr_address_rr); + dns_resource_record_unref(a->llmnr_ptr_rr); + + free(a); + return NULL; +} + +void link_address_add_rrs(LinkAddress *a, bool force_remove) { + int r; + + assert(a); + + if (a->family == AF_INET) { + + if (!force_remove && + link_address_relevant(a, true) && + a->link->llmnr_ipv4_scope && + 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); + if (!a->link->manager->llmnr_host_ipv4_key) { + r = -ENOMEM; + goto fail; + } + } + + if (!a->llmnr_address_rr) { + a->llmnr_address_rr = dns_resource_record_new(a->link->manager->llmnr_host_ipv4_key); + if (!a->llmnr_address_rr) { + r = -ENOMEM; + goto fail; + } + + a->llmnr_address_rr->a.in_addr = a->in_addr.in; + a->llmnr_address_rr->ttl = LLMNR_DEFAULT_TTL; + } + + if (!a->llmnr_ptr_rr) { + r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->llmnr_hostname); + if (r < 0) + goto fail; + + a->llmnr_ptr_rr->ttl = LLMNR_DEFAULT_TTL; + } + + r = dns_zone_put(&a->link->llmnr_ipv4_scope->zone, a->link->llmnr_ipv4_scope, a->llmnr_address_rr, true); + if (r < 0) + log_warning_errno(r, "Failed to add A record to LLMNR zone: %m"); + + r = dns_zone_put(&a->link->llmnr_ipv4_scope->zone, a->link->llmnr_ipv4_scope, a->llmnr_ptr_rr, false); + if (r < 0) + log_warning_errno(r, "Failed to add IPv6 PTR record to LLMNR zone: %m"); + } else { + if (a->llmnr_address_rr) { + if (a->link->llmnr_ipv4_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr); + a->llmnr_address_rr = dns_resource_record_unref(a->llmnr_address_rr); + } + + if (a->llmnr_ptr_rr) { + if (a->link->llmnr_ipv4_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_ptr_rr); + a->llmnr_ptr_rr = dns_resource_record_unref(a->llmnr_ptr_rr); + } + } + } + + if (a->family == AF_INET6) { + + if (!force_remove && + link_address_relevant(a, true) && + a->link->llmnr_ipv6_scope && + 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); + if (!a->link->manager->llmnr_host_ipv6_key) { + r = -ENOMEM; + goto fail; + } + } + + if (!a->llmnr_address_rr) { + a->llmnr_address_rr = dns_resource_record_new(a->link->manager->llmnr_host_ipv6_key); + if (!a->llmnr_address_rr) { + r = -ENOMEM; + goto fail; + } + + a->llmnr_address_rr->aaaa.in6_addr = a->in_addr.in6; + a->llmnr_address_rr->ttl = LLMNR_DEFAULT_TTL; + } + + if (!a->llmnr_ptr_rr) { + r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->llmnr_hostname); + if (r < 0) + goto fail; + + a->llmnr_ptr_rr->ttl = LLMNR_DEFAULT_TTL; + } + + r = dns_zone_put(&a->link->llmnr_ipv6_scope->zone, a->link->llmnr_ipv6_scope, a->llmnr_address_rr, true); + if (r < 0) + log_warning_errno(r, "Failed to add AAAA record to LLMNR zone: %m"); + + r = dns_zone_put(&a->link->llmnr_ipv6_scope->zone, a->link->llmnr_ipv6_scope, a->llmnr_ptr_rr, false); + if (r < 0) + log_warning_errno(r, "Failed to add IPv6 PTR record to LLMNR zone: %m"); + } else { + if (a->llmnr_address_rr) { + if (a->link->llmnr_ipv6_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_address_rr); + a->llmnr_address_rr = dns_resource_record_unref(a->llmnr_address_rr); + } + + if (a->llmnr_ptr_rr) { + if (a->link->llmnr_ipv6_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr); + a->llmnr_ptr_rr = dns_resource_record_unref(a->llmnr_ptr_rr); + } + } + } + + return; + +fail: + log_debug_errno(r, "Failed to update address RRs: %m"); +} + +int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m) { + int r; + assert(a); + assert(m); + + r = sd_rtnl_message_addr_get_flags(m, &a->flags); + if (r < 0) + return r; + + sd_rtnl_message_addr_get_scope(m, &a->scope); + + link_allocate_scopes(a->link); + link_add_rrs(a->link, false); + + return 0; +} + +bool link_address_relevant(LinkAddress *a, bool local_multicast) { + assert(a); + + if (a->flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE)) + return false; + + if (a->scope >= (local_multicast ? RT_SCOPE_HOST : RT_SCOPE_LINK)) + return false; + + return true; +} diff --git a/src/resolve/src/resolved-link.h b/src/resolve/src/resolved-link.h new file mode 100644 index 0000000000..f534c12824 --- /dev/null +++ b/src/resolve/src/resolved-link.h @@ -0,0 +1,111 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#include "in-addr-util.h" +#include "ratelimit.h" +#include "resolve-util.h" + +typedef struct Link Link; +typedef struct LinkAddress LinkAddress; + +#include "resolved-dns-rr.h" +#include "resolved-dns-search-domain.h" +#include "resolved-dns-server.h" +#include "resolved-manager.h" + +#define LINK_SEARCH_DOMAINS_MAX 32 +#define LINK_DNS_SERVERS_MAX 32 + +struct LinkAddress { + Link *link; + + int family; + union in_addr_union in_addr; + + unsigned char flags, scope; + + DnsResourceRecord *llmnr_address_rr; + DnsResourceRecord *llmnr_ptr_rr; + + LIST_FIELDS(LinkAddress, addresses); +}; + +struct Link { + Manager *manager; + + int ifindex; + unsigned flags; + + LIST_HEAD(LinkAddress, addresses); + + LIST_HEAD(DnsServer, dns_servers); + DnsServer *current_dns_server; + unsigned n_dns_servers; + + LIST_HEAD(DnsSearchDomain, search_domains); + unsigned n_search_domains; + + ResolveSupport llmnr_support; + ResolveSupport mdns_support; + DnssecMode dnssec_mode; + Set *dnssec_negative_trust_anchors; + + DnsScope *unicast_scope; + DnsScope *llmnr_ipv4_scope; + DnsScope *llmnr_ipv6_scope; + 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 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 local_multicast); +void link_address_add_rrs(LinkAddress *a, bool force_remove); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free); diff --git a/src/resolve/src/resolved-llmnr.c b/src/resolve/src/resolved-llmnr.c new file mode 100644 index 0000000000..ef12abfbb5 --- /dev/null +++ b/src/resolve/src/resolved-llmnr.c @@ -0,0 +1,476 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . + ***/ + +#include +#include + +#include "fd-util.h" +#include "resolved-llmnr.h" +#include "resolved-manager.h" + +void manager_llmnr_stop(Manager *m) { + assert(m); + + m->llmnr_ipv4_udp_event_source = sd_event_source_unref(m->llmnr_ipv4_udp_event_source); + m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); + + m->llmnr_ipv6_udp_event_source = sd_event_source_unref(m->llmnr_ipv6_udp_event_source); + m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); + + m->llmnr_ipv4_tcp_event_source = sd_event_source_unref(m->llmnr_ipv4_tcp_event_source); + m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); + + m->llmnr_ipv6_tcp_event_source = sd_event_source_unref(m->llmnr_ipv6_tcp_event_source); + m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); +} + +int manager_llmnr_start(Manager *m) { + int r; + + assert(m); + + if (m->llmnr_support == RESOLVE_SUPPORT_NO) + return 0; + + r = manager_llmnr_ipv4_udp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + r = manager_llmnr_ipv4_tcp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + if (socket_ipv6_is_supported()) { + r = manager_llmnr_ipv6_udp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + r = manager_llmnr_ipv6_tcp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + } + + return 0; + +eaddrinuse: + log_warning("There appears to be another LLMNR responder running. Turning off LLMNR support."); + m->llmnr_support = RESOLVE_SUPPORT_NO; + manager_llmnr_stop(m); + + return 0; +} + +static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + DnsTransaction *t = NULL; + Manager *m = userdata; + DnsScope *scope; + int r; + + r = manager_recv(m, fd, DNS_PROTOCOL_LLMNR, &p); + if (r <= 0) + return r; + + scope = manager_find_scope(m, p); + if (!scope) { + log_warning("Got LLMNR UDP packet on unknown scope. Ignoring."); + return 0; + } + + if (dns_packet_validate_reply(p) > 0) { + log_debug("Got LLMNR reply packet for id %u", DNS_PACKET_ID(p)); + + dns_scope_check_conflicts(scope, p); + + t = hashmap_get(m->dns_transactions, UINT_TO_PTR(DNS_PACKET_ID(p))); + if (t) + dns_transaction_process_reply(t, p); + + } else if (dns_packet_validate_query(p) > 0) { + log_debug("Got LLMNR query packet for id %u", DNS_PACKET_ID(p)); + + dns_scope_process_query(scope, NULL, p); + } else + log_debug("Invalid LLMNR UDP packet, ignoring."); + + return 0; +} + +int manager_llmnr_ipv4_udp_fd(Manager *m) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(LLMNR_PORT), + }; + static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; + int r; + + assert(m); + + if (m->llmnr_ipv4_udp_fd >= 0) + return m->llmnr_ipv4_udp_fd; + + m->llmnr_ipv4_udp_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->llmnr_ipv4_udp_fd < 0) + return -errno; + + /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* Disable Don't-Fragment bit in the IP header */ + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->llmnr_ipv4_udp_fd, &sa.sa, sizeof(sa.in)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->llmnr_ipv4_udp_event_source, m->llmnr_ipv4_udp_fd, EPOLLIN, on_llmnr_packet, 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: + m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); + return r; +} + +int manager_llmnr_ipv6_udp_fd(Manager *m) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(LLMNR_PORT), + }; + static const int one = 1, ttl = 255; + int r; + + assert(m); + + if (m->llmnr_ipv6_udp_fd >= 0) + return m->llmnr_ipv6_udp_fd; + + m->llmnr_ipv6_udp_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->llmnr_ipv6_udp_fd < 0) + return -errno; + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->llmnr_ipv6_udp_fd, &sa.sa, sizeof(sa.in6)); + if (r < 0) { + r = -errno; + goto fail; + } + + 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) + goto fail; + + (void) sd_event_source_set_description(m->llmnr_ipv6_udp_event_source, "llmnr-ipv6-udp"); + + return m->llmnr_ipv6_udp_fd; + +fail: + m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); + return r; +} + +static int on_llmnr_stream_packet(DnsStream *s) { + DnsScope *scope; + + assert(s); + + scope = manager_find_scope(s->manager, s->read_packet); + if (!scope) { + log_warning("Got LLMNR TCP packet on unknown scope. Ignroing."); + return 0; + } + + if (dns_packet_validate_query(s->read_packet) > 0) { + log_debug("Got query packet for id %u", DNS_PACKET_ID(s->read_packet)); + + dns_scope_process_query(scope, s, s->read_packet); + + /* If no reply packet was set, we free the stream */ + if (s->write_packet) + return 0; + } else + log_debug("Invalid LLMNR TCP packet."); + + dns_stream_free(s); + return 0; +} + +static int on_llmnr_stream(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + DnsStream *stream; + Manager *m = userdata; + int cfd, r; + + cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); + if (cfd < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return -errno; + } + + r = dns_stream_new(m, &stream, DNS_PROTOCOL_LLMNR, cfd); + if (r < 0) { + safe_close(cfd); + return r; + } + + stream->on_packet = on_llmnr_stream_packet; + return 0; +} + +int manager_llmnr_ipv4_tcp_fd(Manager *m) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(LLMNR_PORT), + }; + static const int one = 1, pmtu = IP_PMTUDISC_DONT; + int r; + + assert(m); + + if (m->llmnr_ipv4_tcp_fd >= 0) + return m->llmnr_ipv4_tcp_fd; + + m->llmnr_ipv4_tcp_fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->llmnr_ipv4_tcp_fd < 0) + return -errno; + + /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ + r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* Disable Don't-Fragment bit in the IP header */ + r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->llmnr_ipv4_tcp_fd, &sa.sa, sizeof(sa.in)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = listen(m->llmnr_ipv4_tcp_fd, SOMAXCONN); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->llmnr_ipv4_tcp_event_source, m->llmnr_ipv4_tcp_fd, EPOLLIN, on_llmnr_stream, 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: + m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); + return r; +} + +int manager_llmnr_ipv6_tcp_fd(Manager *m) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(LLMNR_PORT), + }; + static const int one = 1; + int r; + + assert(m); + + if (m->llmnr_ipv6_tcp_fd >= 0) + return m->llmnr_ipv6_tcp_fd; + + m->llmnr_ipv6_tcp_fd = socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->llmnr_ipv6_tcp_fd < 0) + return -errno; + + /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ + r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->llmnr_ipv6_tcp_fd, &sa.sa, sizeof(sa.in6)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = listen(m->llmnr_ipv6_tcp_fd, SOMAXCONN); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->llmnr_ipv6_tcp_event_source, m->llmnr_ipv6_tcp_fd, EPOLLIN, on_llmnr_stream, 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: + m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); + return r; +} diff --git a/src/resolve/src/resolved-llmnr.h b/src/resolve/src/resolved-llmnr.h new file mode 100644 index 0000000000..8133582fa7 --- /dev/null +++ b/src/resolve/src/resolved-llmnr.h @@ -0,0 +1,32 @@ +#pragma once + +/*** + 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 . +***/ + +#include "resolved-manager.h" + +#define LLMNR_PORT 5355 + +int manager_llmnr_ipv4_udp_fd(Manager *m); +int manager_llmnr_ipv6_udp_fd(Manager *m); +int manager_llmnr_ipv4_tcp_fd(Manager *m); +int manager_llmnr_ipv6_tcp_fd(Manager *m); + +void manager_llmnr_stop(Manager *m); +int manager_llmnr_start(Manager *m); diff --git a/src/resolve/src/resolved-manager.c b/src/resolve/src/resolved-manager.c new file mode 100644 index 0000000000..e82c6ec563 --- /dev/null +++ b/src/resolve/src/resolved-manager.c @@ -0,0 +1,1239 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . + ***/ + +#include +#include +#include + +#include "af-list.h" +#include "alloc-util.h" +#include "dns-domain.h" +#include "fd-util.h" +#include "fileio-label.h" +#include "hostname-util.h" +#include "io-util.h" +#include "netlink-util.h" +#include "network-internal.h" +#include "ordered-set.h" +#include "parse-util.h" +#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-mdns.h" +#include "resolved-resolv-conf.h" +#include "socket-util.h" +#include "string-table.h" +#include "string-util.h" +#include "utf8.h" + +#define SEND_TIMEOUT_USEC (200 * USEC_PER_MSEC) + +static int manager_process_link(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { + Manager *m = userdata; + uint16_t type; + Link *l; + int ifindex, r; + + assert(rtnl); + assert(m); + assert(mm); + + r = sd_netlink_message_get_type(mm, &type); + if (r < 0) + goto fail; + + r = sd_rtnl_message_link_get_ifindex(mm, &ifindex); + if (r < 0) + goto fail; + + l = hashmap_get(m->links, INT_TO_PTR(ifindex)); + + switch (type) { + + case RTM_NEWLINK:{ + bool is_new = !l; + + if (!l) { + r = link_new(m, &l, ifindex); + if (r < 0) + goto fail; + } + + r = link_update_rtnl(l, mm); + if (r < 0) + goto fail; + + r = link_update_monitor(l); + if (r < 0) + goto fail; + + if (is_new) + log_debug("Found new link %i/%s", ifindex, l->name); + + break; + } + + case RTM_DELLINK: + if (l) { + log_debug("Removing link %i/%s", l->ifindex, l->name); + link_free(l); + } + + break; + } + + return 0; + +fail: + log_warning_errno(r, "Failed to process RTNL link message: %m"); + return 0; +} + +static int manager_process_address(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { + Manager *m = userdata; + union in_addr_union address; + uint16_t type; + int r, ifindex, family; + LinkAddress *a; + Link *l; + + assert(rtnl); + assert(mm); + assert(m); + + r = sd_netlink_message_get_type(mm, &type); + if (r < 0) + goto fail; + + r = sd_rtnl_message_addr_get_ifindex(mm, &ifindex); + if (r < 0) + goto fail; + + l = hashmap_get(m->links, INT_TO_PTR(ifindex)); + if (!l) + return 0; + + r = sd_rtnl_message_addr_get_family(mm, &family); + if (r < 0) + goto fail; + + switch (family) { + + case AF_INET: + r = sd_netlink_message_read_in_addr(mm, IFA_LOCAL, &address.in); + if (r < 0) { + r = sd_netlink_message_read_in_addr(mm, IFA_ADDRESS, &address.in); + if (r < 0) + goto fail; + } + + break; + + case AF_INET6: + r = sd_netlink_message_read_in6_addr(mm, IFA_LOCAL, &address.in6); + if (r < 0) { + r = sd_netlink_message_read_in6_addr(mm, IFA_ADDRESS, &address.in6); + if (r < 0) + goto fail; + } + + break; + + default: + return 0; + } + + a = link_find_address(l, family, &address); + + switch (type) { + + case RTM_NEWADDR: + + if (!a) { + r = link_address_new(l, &a, family, &address); + if (r < 0) + return r; + } + + r = link_address_update_rtnl(a, mm); + if (r < 0) + return r; + + break; + + case RTM_DELADDR: + link_address_free(a); + break; + } + + return 0; + +fail: + log_warning_errno(r, "Failed to process RTNL address message: %m"); + return 0; +} + +static int manager_rtnl_listen(Manager *m) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + sd_netlink_message *i; + int r; + + assert(m); + + /* First, subscribe to interfaces coming and going */ + r = sd_netlink_open(&m->rtnl); + if (r < 0) + return r; + + r = sd_netlink_attach_event(m->rtnl, m->event, SD_EVENT_PRIORITY_IMPORTANT); + if (r < 0) + return r; + + r = sd_netlink_add_match(m->rtnl, RTM_NEWLINK, manager_process_link, m); + if (r < 0) + return r; + + r = sd_netlink_add_match(m->rtnl, RTM_DELLINK, manager_process_link, m); + if (r < 0) + return r; + + r = sd_netlink_add_match(m->rtnl, RTM_NEWADDR, manager_process_address, m); + if (r < 0) + return r; + + r = sd_netlink_add_match(m->rtnl, RTM_DELADDR, manager_process_address, m); + if (r < 0) + return r; + + /* Then, enumerate all links */ + r = sd_rtnl_message_new_link(m->rtnl, &req, RTM_GETLINK, 0); + if (r < 0) + return r; + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_netlink_call(m->rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (i = reply; i; i = sd_netlink_message_next(i)) { + r = manager_process_link(m->rtnl, i, m); + if (r < 0) + return r; + } + + req = sd_netlink_message_unref(req); + reply = sd_netlink_message_unref(reply); + + /* Finally, enumerate all addresses, too */ + r = sd_rtnl_message_new_addr(m->rtnl, &req, RTM_GETADDR, 0, AF_UNSPEC); + if (r < 0) + return r; + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_netlink_call(m->rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (i = reply; i; i = sd_netlink_message_next(i)) { + r = manager_process_address(m->rtnl, i, m); + if (r < 0) + return r; + } + + return r; +} + +static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + Iterator i; + Link *l; + int r; + + assert(m); + + sd_network_monitor_flush(m->network_monitor); + + HASHMAP_FOREACH(l, m->links, i) { + r = link_update_monitor(l); + if (r < 0) + log_warning_errno(r, "Failed to update monitor information for %i: %m", l->ifindex); + } + + r = manager_write_resolv_conf(m); + if (r < 0) + log_warning_errno(r, "Could not update "PRIVATE_RESOLV_CONF": %m"); + + return 0; +} + +static int manager_network_monitor_listen(Manager *m) { + int r, fd, events; + + assert(m); + + r = sd_network_monitor_new(&m->network_monitor, NULL); + if (r < 0) + return r; + + fd = sd_network_monitor_get_fd(m->network_monitor); + if (fd < 0) + return fd; + + events = sd_network_monitor_get_events(m->network_monitor); + if (events < 0) + return events; + + r = sd_event_add_io(m->event, &m->network_event_source, fd, events, &on_network_event, 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; +} + +static int determine_hostname(char **llmnr_hostname, char **mdns_hostname) { + _cleanup_free_ char *h = NULL, *n = NULL; + char label[DNS_LABEL_MAX]; + const char *p; + int r, k; + + assert(llmnr_hostname); + assert(mdns_hostname); + + /* Extract and normalize the first label of the locally + * configured hostname, and check it's not "localhost". */ + + h = gethostname_malloc(); + if (!h) + return log_oom(); + + p = h; + r = dns_label_unescape(&p, label, sizeof(label)); + if (r < 0) + return log_error_errno(r, "Failed to unescape host name: %m"); + if (r == 0) { + log_error("Couldn't find a single label in hosntame."); + return -EINVAL; + } + + k = dns_label_undo_idna(label, r, label, sizeof(label)); + if (k < 0) + return log_error_errno(k, "Failed to undo IDNA: %m"); + if (k > 0) + r = k; + + if (!utf8_is_valid(label)) { + log_error("System hostname is not UTF-8 clean."); + return -EINVAL; + } + + r = dns_label_escape_new(label, r, &n); + if (r < 0) + return log_error_errno(r, "Failed to escape host name: %m"); + + if (is_localhost(n)) { + log_debug("System hostname is 'localhost', ignoring."); + return -EINVAL; + } + + r = dns_name_concat(n, "local", mdns_hostname); + if (r < 0) + return log_error_errno(r, "Failed to determine mDNS hostname: %m"); + + *llmnr_hostname = n; + n = NULL; + + return 0; +} + +static int on_hostname_change(sd_event_source *es, int fd, uint32_t revents, void *userdata) { + _cleanup_free_ char *llmnr_hostname = NULL, *mdns_hostname = NULL; + Manager *m = userdata; + int r; + + assert(m); + + r = determine_hostname(&llmnr_hostname, &mdns_hostname); + if (r < 0) + return 0; /* ignore invalid hostnames */ + + if (streq(llmnr_hostname, m->llmnr_hostname) && streq(mdns_hostname, m->mdns_hostname)) + return 0; + + log_info("System hostname changed to '%s'.", llmnr_hostname); + + free(m->llmnr_hostname); + free(m->mdns_hostname); + + m->llmnr_hostname = llmnr_hostname; + m->mdns_hostname = mdns_hostname; + + llmnr_hostname = mdns_hostname = NULL; + + manager_refresh_rrs(m); + + return 0; +} + +static int manager_watch_hostname(Manager *m) { + int r; + + assert(m); + + m->hostname_fd = open("/proc/sys/kernel/hostname", O_RDONLY|O_CLOEXEC|O_NDELAY|O_NOCTTY); + if (m->hostname_fd < 0) { + log_warning_errno(errno, "Failed to watch hostname: %m"); + return 0; + } + + r = sd_event_add_io(m->event, &m->hostname_event_source, m->hostname_fd, 0, on_hostname_change, m); + if (r < 0) { + if (r == -EPERM) + /* kernels prior to 3.2 don't support polling this file. Ignore the failure. */ + m->hostname_fd = safe_close(m->hostname_fd); + else + 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 'gnu-linux'."); + m->llmnr_hostname = strdup("gnu-linux"); + if (!m->llmnr_hostname) + return log_oom(); + + m->mdns_hostname = strdup("gnu-linux.local"); + if (!m->mdns_hostname) + return log_oom(); + } else + log_info("Using system hostname '%s'.", m->llmnr_hostname); + + return 0; +} + +static int manager_sigusr1(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + _cleanup_free_ char *buffer = NULL; + _cleanup_fclose_ FILE *f = NULL; + Manager *m = userdata; + size_t size = 0; + DnsScope *scope; + + assert(s); + assert(si); + assert(m); + + f = open_memstream(&buffer, &size); + if (!f) + return log_oom(); + + LIST_FOREACH(scopes, scope, m->dns_scopes) + dns_scope_dump(scope, f); + + if (fflush_and_check(f) < 0) + return log_oom(); + + log_dump(LOG_INFO, buffer); + return 0; +} + +int manager_new(Manager **ret) { + _cleanup_(manager_freep) Manager *m = NULL; + int r; + + assert(ret); + + m = new0(Manager, 1); + if (!m) + return -ENOMEM; + + m->llmnr_ipv4_udp_fd = m->llmnr_ipv6_udp_fd = -1; + m->llmnr_ipv4_tcp_fd = m->llmnr_ipv6_tcp_fd = -1; + m->mdns_ipv4_fd = m->mdns_ipv6_fd = -1; + m->hostname_fd = -1; + + 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; + + sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); + sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); + + sd_event_set_watchdog(m->event, true); + + r = manager_watch_hostname(m); + if (r < 0) + return r; + + r = dns_scope_new(m, &m->unicast_scope, NULL, DNS_PROTOCOL_DNS, AF_UNSPEC); + if (r < 0) + return r; + + r = manager_network_monitor_listen(m); + if (r < 0) + return r; + + r = manager_rtnl_listen(m); + if (r < 0) + return r; + + r = manager_connect_bus(m); + if (r < 0) + return r; + + (void) sd_event_add_signal(m->event, &m->sigusr1_event_source, SIGUSR1, manager_sigusr1, m); + + *ret = m; + m = NULL; + + return 0; +} + +int manager_start(Manager *m) { + int r; + + assert(m); + + r = manager_llmnr_start(m); + if (r < 0) + return r; + + r = manager_mdns_start(m); + if (r < 0) + return r; + + return 0; +} + +Manager *manager_free(Manager *m) { + Link *l; + + if (!m) + return NULL; + + dns_server_unlink_all(m->dns_servers); + dns_server_unlink_all(m->fallback_dns_servers); + dns_search_domain_unlink_all(m->search_domains); + + while ((l = hashmap_first(m->links))) + link_free(l); + + while (m->dns_queries) + dns_query_free(m->dns_queries); + + dns_scope_free(m->unicast_scope); + + hashmap_free(m->links); + hashmap_free(m->dns_transactions); + + sd_event_source_unref(m->network_event_source); + sd_network_monitor_unref(m->network_monitor); + + sd_netlink_unref(m->rtnl); + sd_event_source_unref(m->rtnl_event_source); + + manager_llmnr_stop(m); + manager_mdns_stop(m); + + sd_bus_slot_unref(m->prepare_for_sleep_slot); + sd_event_source_unref(m->bus_retry_event_source); + sd_bus_unref(m->bus); + + sd_event_source_unref(m->sigusr1_event_source); + + sd_event_unref(m->event); + + dns_resource_key_unref(m->llmnr_host_ipv4_key); + dns_resource_key_unref(m->llmnr_host_ipv6_key); + + sd_event_source_unref(m->hostname_event_source); + safe_close(m->hostname_fd); + free(m->llmnr_hostname); + free(m->mdns_hostname); + + dns_trust_anchor_flush(&m->trust_anchor); + manager_etc_hosts_flush(m); + + free(m); + + return NULL; +} + +int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + union { + struct cmsghdr header; /* For alignment */ + uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo)) + + CMSG_SPACE(int) /* ttl/hoplimit */ + + EXTRA_CMSG_SPACE /* kernel appears to require extra buffer space */]; + } control; + union sockaddr_union sa; + struct msghdr mh = {}; + struct cmsghdr *cmsg; + struct iovec iov; + ssize_t ms, l; + int r; + + assert(m); + assert(fd >= 0); + assert(ret); + + ms = next_datagram_size_fd(fd); + if (ms < 0) + return ms; + + r = dns_packet_new(&p, protocol, ms); + if (r < 0) + return r; + + iov.iov_base = DNS_PACKET_DATA(p); + iov.iov_len = p->allocated; + + mh.msg_name = &sa.sa; + mh.msg_namelen = sizeof(sa); + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_control = &control; + mh.msg_controllen = sizeof(control); + + l = recvmsg(fd, &mh, 0); + if (l < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return -errno; + } + + if (l <= 0) + return -EIO; + + assert(!(mh.msg_flags & MSG_CTRUNC)); + assert(!(mh.msg_flags & MSG_TRUNC)); + + p->size = (size_t) l; + + p->family = sa.sa.sa_family; + p->ipproto = IPPROTO_UDP; + if (p->family == AF_INET) { + p->sender.in = sa.in.sin_addr; + p->sender_port = be16toh(sa.in.sin_port); + } else if (p->family == AF_INET6) { + p->sender.in6 = sa.in6.sin6_addr; + p->sender_port = be16toh(sa.in6.sin6_port); + p->ifindex = sa.in6.sin6_scope_id; + } else + return -EAFNOSUPPORT; + + CMSG_FOREACH(cmsg, &mh) { + + if (cmsg->cmsg_level == IPPROTO_IPV6) { + assert(p->family == AF_INET6); + + switch (cmsg->cmsg_type) { + + case IPV6_PKTINFO: { + struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); + + if (p->ifindex <= 0) + p->ifindex = i->ipi6_ifindex; + + p->destination.in6 = i->ipi6_addr; + break; + } + + case IPV6_HOPLIMIT: + p->ttl = *(int *) CMSG_DATA(cmsg); + break; + + } + } else if (cmsg->cmsg_level == IPPROTO_IP) { + assert(p->family == AF_INET); + + switch (cmsg->cmsg_type) { + + case IP_PKTINFO: { + struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); + + if (p->ifindex <= 0) + p->ifindex = i->ipi_ifindex; + + p->destination.in = i->ipi_addr; + break; + } + + case IP_TTL: + p->ttl = *(int *) CMSG_DATA(cmsg); + break; + } + } + } + + /* The Linux kernel sets the interface index to the loopback + * device if the packet came from the local host since it + * avoids the routing table in such a case. Let's unset the + * interface index in such a case. */ + if (p->ifindex == LOOPBACK_IFINDEX) + p->ifindex = 0; + + if (protocol != DNS_PROTOCOL_DNS) { + /* If we don't know the interface index still, we look for the + * first local interface with a matching address. Yuck! */ + if (p->ifindex <= 0) + p->ifindex = manager_find_ifindex(m, p->family, &p->destination); + } + + *ret = p; + p = NULL; + + return 1; +} + +static int sendmsg_loop(int fd, struct msghdr *mh, int flags) { + int r; + + assert(fd >= 0); + assert(mh); + + for (;;) { + if (sendmsg(fd, mh, flags) >= 0) + return 0; + + if (errno == EINTR) + continue; + + if (errno != EAGAIN) + return -errno; + + r = fd_wait_for_event(fd, POLLOUT, SEND_TIMEOUT_USEC); + if (r < 0) + return r; + if (r == 0) + return -ETIMEDOUT; + } +} + +static int write_loop(int fd, void *message, size_t length) { + int r; + + assert(fd >= 0); + assert(message); + + for (;;) { + if (write(fd, message, length) >= 0) + return 0; + + if (errno == EINTR) + continue; + + if (errno != EAGAIN) + return -errno; + + r = fd_wait_for_event(fd, POLLOUT, SEND_TIMEOUT_USEC); + if (r < 0) + return r; + if (r == 0) + return -ETIMEDOUT; + } +} + +int manager_write(Manager *m, int fd, DnsPacket *p) { + int r; + + 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) + return r; + + return 0; +} + +static int manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_addr *addr, uint16_t port, DnsPacket *p) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + }; + union { + struct cmsghdr header; /* For alignment */ + uint8_t buffer[CMSG_SPACE(sizeof(struct in_pktinfo))]; + } control; + struct msghdr mh = {}; + struct iovec iov; + + assert(m); + assert(fd >= 0); + assert(addr); + assert(port > 0); + assert(p); + + iov.iov_base = DNS_PACKET_DATA(p); + iov.iov_len = p->size; + + sa.in.sin_addr = *addr; + sa.in.sin_port = htobe16(port), + + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_name = &sa.sa; + mh.msg_namelen = sizeof(sa.in); + + if (ifindex > 0) { + struct cmsghdr *cmsg; + struct in_pktinfo *pi; + + zero(control); + + mh.msg_control = &control; + mh.msg_controllen = CMSG_LEN(sizeof(struct in_pktinfo)); + + cmsg = CMSG_FIRSTHDR(&mh); + cmsg->cmsg_len = mh.msg_controllen; + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + + pi = (struct in_pktinfo*) CMSG_DATA(cmsg); + pi->ipi_ifindex = ifindex; + } + + return sendmsg_loop(fd, &mh, 0); +} + +static int manager_ipv6_send(Manager *m, int fd, int ifindex, const struct in6_addr *addr, uint16_t port, DnsPacket *p) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + }; + union { + struct cmsghdr header; /* For alignment */ + uint8_t buffer[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } control; + struct msghdr mh = {}; + struct iovec iov; + + assert(m); + assert(fd >= 0); + assert(addr); + assert(port > 0); + assert(p); + + iov.iov_base = DNS_PACKET_DATA(p); + iov.iov_len = p->size; + + sa.in6.sin6_addr = *addr; + sa.in6.sin6_port = htobe16(port), + sa.in6.sin6_scope_id = ifindex; + + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_name = &sa.sa; + mh.msg_namelen = sizeof(sa.in6); + + if (ifindex > 0) { + struct cmsghdr *cmsg; + struct in6_pktinfo *pi; + + zero(control); + + mh.msg_control = &control; + mh.msg_controllen = CMSG_LEN(sizeof(struct in6_pktinfo)); + + cmsg = CMSG_FIRSTHDR(&mh); + cmsg->cmsg_len = mh.msg_controllen; + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + + pi = (struct in6_pktinfo*) CMSG_DATA(cmsg); + pi->ipi6_ifindex = ifindex; + } + + return sendmsg_loop(fd, &mh, 0); +} + +int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p) { + assert(m); + assert(fd >= 0); + assert(addr); + assert(port > 0); + assert(p); + + 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); + else if (family == AF_INET6) + return manager_ipv6_send(m, fd, ifindex, &addr->in6, port, p); + + return -EAFNOSUPPORT; +} + +uint32_t manager_find_mtu(Manager *m) { + uint32_t mtu = 0; + Link *l; + Iterator i; + + /* If we don't know on which link a DNS packet would be + * delivered, let's find the largest MTU that works on all + * interfaces we know of */ + + HASHMAP_FOREACH(l, m->links, i) { + if (l->mtu <= 0) + continue; + + if (mtu <= 0 || l->mtu < mtu) + mtu = l->mtu; + } + + return mtu; +} + +int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr) { + LinkAddress *a; + + assert(m); + + a = manager_find_link_address(m, family, in_addr); + if (a) + return a->link->ifindex; + + return 0; +} + +void manager_refresh_rrs(Manager *m) { + Iterator i; + Link *l; + + assert(m); + + m->llmnr_host_ipv4_key = dns_resource_key_unref(m->llmnr_host_ipv4_key); + m->llmnr_host_ipv6_key = dns_resource_key_unref(m->llmnr_host_ipv6_key); + + HASHMAP_FOREACH(l, m->links, i) { + link_add_rrs(l, true); + link_add_rrs(l, false); + } +} + +int manager_next_hostname(Manager *m) { + const char *p; + uint64_t u, a; + char *h, *k; + int r; + + assert(m); + + p = strchr(m->llmnr_hostname, 0); + assert(p); + + while (p > m->llmnr_hostname) { + if (!strchr("0123456789", p[-1])) + break; + + p--; + } + + if (*p == 0 || safe_atou64(p, &u) < 0 || u <= 0) + u = 1; + + /* Add a random number to the old value. This way we can avoid + * that two hosts pick the same hostname, win on IPv4 and lose + * on IPv6 (or vice versa), and pick the same hostname + * replacement hostname, ad infinitum. We still want the + * numbers to go up monotonically, hence we just add a random + * value 1..10 */ + + random_bytes(&a, sizeof(a)); + u += 1 + a % 10; + + if (asprintf(&h, "%.*s%" PRIu64, (int) (p - m->llmnr_hostname), m->llmnr_hostname, u) < 0) + return -ENOMEM; + + r = dns_name_concat(h, "local", &k); + if (r < 0) { + free(h); + return r; + } + + log_info("Hostname conflict, changing published hostname from '%s' to '%s'.", m->llmnr_hostname, h); + + free(m->llmnr_hostname); + m->llmnr_hostname = h; + + free(m->mdns_hostname); + m->mdns_hostname = k; + + manager_refresh_rrs(m); + + return 0; +} + +LinkAddress* manager_find_link_address(Manager *m, int family, const union in_addr_union *in_addr) { + Iterator i; + Link *l; + + assert(m); + + HASHMAP_FOREACH(l, m->links, i) { + LinkAddress *a; + + a = link_find_address(l, family, in_addr); + if (a) + return a; + } + + return NULL; +} + +bool manager_our_packet(Manager *m, DnsPacket *p) { + assert(m); + assert(p); + + return !!manager_find_link_address(m, p->family, &p->sender); +} + +DnsScope* manager_find_scope(Manager *m, DnsPacket *p) { + Link *l; + + assert(m); + assert(p); + + l = hashmap_get(m->links, INT_TO_PTR(p->ifindex)); + if (!l) + return NULL; + + switch (p->protocol) { + case DNS_PROTOCOL_LLMNR: + if (p->family == AF_INET) + return l->llmnr_ipv4_scope; + else if (p->family == AF_INET6) + return l->llmnr_ipv6_scope; + + break; + + case DNS_PROTOCOL_MDNS: + if (p->family == AF_INET) + return l->mdns_ipv4_scope; + else if (p->family == AF_INET6) + return l->mdns_ipv6_scope; + + break; + + default: + break; + } + + return NULL; +} + +void manager_verify_all(Manager *m) { + DnsScope *s; + + assert(m); + + LIST_FOREACH(scopes, s, m->dns_scopes) + dns_zone_verify_all(&s->zone); +} + +int manager_is_own_hostname(Manager *m, const char *name) { + int r; + + assert(m); + assert(name); + + if (m->llmnr_hostname) { + r = dns_name_equal(name, m->llmnr_hostname); + if (r != 0) + return r; + } + + if (m->mdns_hostname) + return dns_name_equal(name, m->mdns_hostname); + + return 0; +} + +int manager_compile_dns_servers(Manager *m, OrderedSet **dns) { + DnsServer *s; + Iterator i; + Link *l; + int r; + + assert(m); + assert(dns); + + r = ordered_set_ensure_allocated(dns, &dns_server_hash_ops); + if (r < 0) + return r; + + /* First add the system-wide servers and domains */ + LIST_FOREACH(servers, s, m->dns_servers) { + r = ordered_set_put(*dns, s); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + + /* Then, add the per-link servers */ + HASHMAP_FOREACH(l, m->links, i) { + LIST_FOREACH(servers, s, l->dns_servers) { + r = ordered_set_put(*dns, s); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + } + + /* If we found nothing, add the fallback servers */ + if (ordered_set_isempty(*dns)) { + LIST_FOREACH(servers, s, m->fallback_dns_servers) { + r = ordered_set_put(*dns, s); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + } + + return 0; +} + +int manager_compile_search_domains(Manager *m, OrderedSet **domains) { + DnsSearchDomain *d; + Iterator i; + Link *l; + int r; + + assert(m); + assert(domains); + + r = ordered_set_ensure_allocated(domains, &dns_name_hash_ops); + if (r < 0) + return r; + + LIST_FOREACH(domains, d, m->search_domains) { + r = ordered_set_put(*domains, d->name); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + + HASHMAP_FOREACH(l, m->links, i) { + + LIST_FOREACH(domains, d, l->search_domains) { + r = ordered_set_put(*domains, d->name); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + } + + return 0; +} + +DnssecMode manager_get_dnssec_mode(Manager *m) { + assert(m); + + if (m->dnssec_mode != _DNSSEC_MODE_INVALID) + return m->dnssec_mode; + + return DNSSEC_NO; +} + +bool manager_dnssec_supported(Manager *m) { + DnsServer *server; + Iterator i; + Link *l; + + assert(m); + + if (manager_get_dnssec_mode(m) == DNSSEC_NO) + return false; + + server = manager_get_dns_server(m); + if (server && !dns_server_dnssec_supported(server)) + return false; + + HASHMAP_FOREACH(l, m->links, i) + if (!link_dnssec_supported(l)) + return false; + + return true; +} + +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/src/resolved-manager.h b/src/resolve/src/resolved-manager.h new file mode 100644 index 0000000000..e82a824f29 --- /dev/null +++ b/src/resolve/src/resolved-manager.h @@ -0,0 +1,171 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include "sd-event.h" +#include "sd-netlink.h" +#include "sd-network.h" + +#include "hashmap.h" +#include "list.h" +#include "ordered-set.h" +#include "resolve-util.h" + +typedef struct Manager Manager; + +#include "resolved-dns-query.h" +#include "resolved-dns-search-domain.h" +#include "resolved-dns-server.h" +#include "resolved-dns-stream.h" +#include "resolved-dns-trust-anchor.h" +#include "resolved-link.h" + +#define MANAGER_SEARCH_DOMAINS_MAX 32 +#define MANAGER_DNS_SERVERS_MAX 32 + +struct Manager { + sd_event *event; + + ResolveSupport llmnr_support; + ResolveSupport mdns_support; + DnssecMode dnssec_mode; + + /* Network */ + Hashmap *links; + + sd_netlink *rtnl; + sd_event_source *rtnl_event_source; + + sd_network_monitor *network_monitor; + sd_event_source *network_event_source; + + /* DNS query management */ + Hashmap *dns_transactions; + LIST_HEAD(DnsQuery, dns_queries); + unsigned n_dns_queries; + + LIST_HEAD(DnsStream, dns_streams); + unsigned n_dns_streams; + + /* Unicast dns */ + LIST_HEAD(DnsServer, dns_servers); + LIST_HEAD(DnsServer, fallback_dns_servers); + unsigned n_dns_servers; /* counts both main and fallback */ + DnsServer *current_dns_server; + + LIST_HEAD(DnsSearchDomain, search_domains); + unsigned n_search_domains; + bool permit_domain_search; + + bool need_builtin_fallbacks:1; + + bool read_resolv_conf:1; + usec_t resolv_conf_mtime; + + DnsTrustAnchor trust_anchor; + + LIST_HEAD(DnsScope, dns_scopes); + DnsScope *unicast_scope; + + /* LLMNR */ + int llmnr_ipv4_udp_fd; + int llmnr_ipv6_udp_fd; + int llmnr_ipv4_tcp_fd; + int llmnr_ipv6_tcp_fd; + + sd_event_source *llmnr_ipv4_udp_event_source; + sd_event_source *llmnr_ipv6_udp_event_source; + sd_event_source *llmnr_ipv4_tcp_event_source; + sd_event_source *llmnr_ipv6_tcp_event_source; + + /* mDNS */ + int mdns_ipv4_fd; + int mdns_ipv6_fd; + + sd_event_source *mdns_ipv4_event_source; + sd_event_source *mdns_ipv6_event_source; + + /* dbus */ + sd_bus *bus; + sd_event_source *bus_retry_event_source; + + /* The hostname we publish on LLMNR and mDNS */ + char *llmnr_hostname; + char *mdns_hostname; + DnsResourceKey *llmnr_host_ipv4_key; + DnsResourceKey *llmnr_host_ipv6_key; + + /* Watch the system hostname */ + int hostname_fd; + sd_event_source *hostname_event_source; + + /* Watch for system suspends */ + 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 */ + +int manager_new(Manager **ret); +Manager* manager_free(Manager *m); + +int manager_start(Manager *m); + +uint32_t manager_find_mtu(Manager *m); + +int manager_write(Manager *m, int fd, DnsPacket *p); +int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p); +int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret); + +int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr); +LinkAddress* manager_find_link_address(Manager *m, int family, const union in_addr_union *in_addr); + +void manager_refresh_rrs(Manager *m); +int manager_next_hostname(Manager *m); + +bool manager_our_packet(Manager *m, DnsPacket *p); +DnsScope* manager_find_scope(Manager *m, DnsPacket *p); + +void manager_verify_all(Manager *m); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); + +#define EXTRA_CMSG_SPACE 1024 + +int manager_is_own_hostname(Manager *m, const char *name); + +int manager_compile_dns_servers(Manager *m, OrderedSet **servers); +int manager_compile_search_domains(Manager *m, OrderedSet **domains); + +DnssecMode manager_get_dnssec_mode(Manager *m); +bool manager_dnssec_supported(Manager *m); + +void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key); + +bool manager_routable(Manager *m, int family); diff --git a/src/resolve/src/resolved-mdns.c b/src/resolve/src/resolved-mdns.c new file mode 100644 index 0000000000..bc8b8b809b --- /dev/null +++ b/src/resolve/src/resolved-mdns.c @@ -0,0 +1,287 @@ +/*** + This file is part of systemd. + + Copyright 2015 Daniel Mack + + 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 . + ***/ + +#include +#include +#include + +#include "fd-util.h" +#include "resolved-manager.h" +#include "resolved-mdns.h" + +void manager_mdns_stop(Manager *m) { + assert(m); + + m->mdns_ipv4_event_source = sd_event_source_unref(m->mdns_ipv4_event_source); + m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); + + m->mdns_ipv6_event_source = sd_event_source_unref(m->mdns_ipv6_event_source); + m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); +} + +int manager_mdns_start(Manager *m) { + int r; + + assert(m); + + if (m->mdns_support == RESOLVE_SUPPORT_NO) + return 0; + + r = manager_mdns_ipv4_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + if (socket_ipv6_is_supported()) { + r = manager_mdns_ipv6_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + } + + return 0; + +eaddrinuse: + log_warning("There appears to be another mDNS responder running. Turning off mDNS support."); + m->mdns_support = RESOLVE_SUPPORT_NO; + manager_mdns_stop(m); + + return 0; +} + +static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + Manager *m = userdata; + DnsScope *scope; + int r; + + r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p); + if (r <= 0) + return r; + + scope = manager_find_scope(m, p); + if (!scope) { + log_warning("Got mDNS UDP packet on unknown scope. Ignoring."); + return 0; + } + + if (dns_packet_validate_reply(p) > 0) { + DnsResourceRecord *rr; + + log_debug("Got mDNS reply packet"); + + /* + * mDNS is different from regular DNS and LLMNR with regard to handling responses. + * While on other protocols, we can ignore every answer that doesn't match a question + * we broadcast earlier, RFC6762, section 18.1 recommends looking at and caching all + * incoming information, regardless of the DNS packet ID. + * + * Hence, extract the packet here, and try to find a transaction for answer the we got + * and complete it. Also store the new information in scope's cache. + */ + r = dns_packet_extract(p); + if (r < 0) { + log_debug("mDNS packet extraction failed."); + return 0; + } + + dns_scope_check_conflicts(scope, p); + + DNS_ANSWER_FOREACH(rr, p->answer) { + const char *name = DNS_RESOURCE_KEY_NAME(rr->key); + DnsTransaction *t; + + /* If the received reply packet contains ANY record that is not .local or .in-addr.arpa, + * we assume someone's playing tricks on us and discard the packet completely. */ + if (!(dns_name_endswith(name, "in-addr.arpa") > 0 || + dns_name_endswith(name, "local") > 0)) + return 0; + + t = dns_scope_find_transaction(scope, rr->key, false); + if (t) + dns_transaction_process_reply(t, p); + } + + 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)); + + dns_scope_process_query(scope, NULL, p); + } else + log_debug("Invalid mDNS UDP packet."); + + return 0; +} + +int manager_mdns_ipv4_fd(Manager *m) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(MDNS_PORT), + }; + static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; + int r; + + assert(m); + + if (m->mdns_ipv4_fd >= 0) + return m->mdns_ipv4_fd; + + m->mdns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->mdns_ipv4_fd < 0) + return -errno; + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* Disable Don't-Fragment bit in the IP header */ + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->mdns_ipv4_fd, &sa.sa, sizeof(sa.in)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->mdns_ipv4_event_source, m->mdns_ipv4_fd, EPOLLIN, on_mdns_packet, m); + if (r < 0) + goto fail; + + return m->mdns_ipv4_fd; + +fail: + m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); + return r; +} + +int manager_mdns_ipv6_fd(Manager *m) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(MDNS_PORT), + }; + static const int one = 1, ttl = 255; + int r; + + assert(m); + + if (m->mdns_ipv6_fd >= 0) + return m->mdns_ipv6_fd; + + m->mdns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->mdns_ipv6_fd < 0) + return -errno; + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->mdns_ipv6_fd, &sa.sa, sizeof(sa.in6)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->mdns_ipv6_event_source, m->mdns_ipv6_fd, EPOLLIN, on_mdns_packet, m); + if (r < 0) + goto fail; + + return m->mdns_ipv6_fd; + +fail: + m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); + return r; +} diff --git a/src/resolve/src/resolved-mdns.h b/src/resolve/src/resolved-mdns.h new file mode 100644 index 0000000000..5d274648f4 --- /dev/null +++ b/src/resolve/src/resolved-mdns.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Daniel Mack + + 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 . +***/ + +#include "resolved-manager.h" + +#define MDNS_PORT 5353 + +int manager_mdns_ipv4_fd(Manager *m); +int manager_mdns_ipv6_fd(Manager *m); + +void manager_mdns_stop(Manager *m); +int manager_mdns_start(Manager *m); diff --git a/src/resolve/src/resolved-resolv-conf.c b/src/resolve/src/resolved-resolv-conf.c new file mode 100644 index 0000000000..065427b690 --- /dev/null +++ b/src/resolve/src/resolved-resolv-conf.c @@ -0,0 +1,267 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . + ***/ + +#include + +#include "alloc-util.h" +#include "dns-domain.h" +#include "fd-util.h" +#include "fileio-label.h" +#include "fileio.h" +#include "ordered-set.h" +#include "resolved-conf.h" +#include "resolved-resolv-conf.h" +#include "string-util.h" +#include "strv.h" + +int manager_read_resolv_conf(Manager *m) { + _cleanup_fclose_ FILE *f = NULL; + struct stat st, own; + char line[LINE_MAX]; + usec_t t; + int r; + + assert(m); + + /* Reads the system /etc/resolv.conf, if it exists and is not + * symlinked to our own resolv.conf instance */ + + if (!m->read_resolv_conf) + return 0; + + r = stat("/etc/resolv.conf", &st); + if (r < 0) { + if (errno == ENOENT) + return 0; + + r = log_warning_errno(errno, "Failed to stat /etc/resolv.conf: %m"); + goto clear; + } + + /* Have we already seen the file? */ + t = timespec_load(&st.st_mtim); + if (t == m->resolv_conf_mtime) + return 0; + + /* Is it symlinked to our own file? */ + if (stat("/run/systemd/resolve/resolv.conf", &own) >= 0 && + st.st_dev == own.st_dev && + st.st_ino == own.st_ino) + return 0; + + f = fopen("/etc/resolv.conf", "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + r = log_warning_errno(errno, "Failed to open /etc/resolv.conf: %m"); + goto clear; + } + + if (fstat(fileno(f), &st) < 0) { + r = log_error_errno(errno, "Failed to stat open file: %m"); + goto clear; + } + + dns_server_mark_all(m->dns_servers); + dns_search_domain_mark_all(m->search_domains); + + FOREACH_LINE(line, f, r = -errno; goto clear) { + const char *a; + char *l; + + l = strstrip(line); + if (*l == '#' || *l == ';') + continue; + + a = first_word(l, "nameserver"); + if (a) { + r = manager_add_dns_server_by_string(m, DNS_SERVER_SYSTEM, a); + if (r < 0) + log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a); + + continue; + } + + a = first_word(l, "domain"); + if (!a) /* We treat "domain" lines, and "search" lines as equivalent, and add both to our list. */ + a = first_word(l, "search"); + if (a) { + r = manager_parse_search_domains_and_warn(m, a); + if (r < 0) + log_warning_errno(r, "Failed to parse search domain string '%s', ignoring.", a); + } + } + + m->resolv_conf_mtime = t; + + /* Flush out all servers and search domains that are still + * marked. Those are then ones that didn't appear in the new + * /etc/resolv.conf */ + dns_server_unlink_marked(m->dns_servers); + dns_search_domain_unlink_marked(m->search_domains); + + /* Whenever /etc/resolv.conf changes, start using the first + * DNS server of it. This is useful to deal with broken + * network managing implementations (like NetworkManager), + * that when connecting to a VPN place both the VPN DNS + * servers and the local ones in /etc/resolv.conf. Without + * resetting the DNS server to use back to the first entry we + * will continue to use the local one thus being unable to + * resolve VPN domains. */ + manager_set_dns_server(m, m->dns_servers); + + /* Unconditionally flush the cache when /etc/resolv.conf is + * modified, even if the data it contained was completely + * identical to the previous version we used. We do this + * because altering /etc/resolv.conf is typically done when + * the network configuration changes, and that should be + * enough to flush the global unicast DNS cache. */ + if (m->unicast_scope) + dns_cache_flush(&m->unicast_scope->cache); + + return 0; + +clear: + dns_server_unlink_all(m->dns_servers); + dns_search_domain_unlink_all(m->search_domains); + return r; +} + +static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { + assert(s); + assert(f); + assert(count); + + (void) dns_server_string(s); + + if (!s->server_string) { + log_warning("Our of memory, or invalid DNS address. Ignoring server."); + return; + } + + if (*count == MAXNS) + fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f); + (*count) ++; + + fprintf(f, "nameserver %s\n", s->server_string); +} + +static void write_resolv_conf_search( + const char *domain, + FILE *f, + unsigned *count, + unsigned *length) { + + assert(domain); + assert(f); + assert(length); + + if (*count >= MAXDNSRCH || + *length + strlen(domain) > 256) { + if (*count == MAXDNSRCH) + fputs(" # Too many search domains configured, remaining ones ignored.", f); + if (*length <= 256) + fputs(" # Total length of all search domains is too long, remaining ones ignored.", f); + + return; + } + + (*length) += strlen(domain); + (*count) ++; + + fputc(' ', f); + fputs(domain, f); +} + +static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) { + Iterator i; + + fputs("# This file is managed by systemd-resolved(8). Do not edit.\n#\n" + "# Third party programs must not access this file directly, but\n" + "# only through the symlink at /etc/resolv.conf. To manage\n" + "# resolv.conf(5) in a different way, replace the symlink by a\n" + "# static file or a different symlink.\n\n", f); + + if (ordered_set_isempty(dns)) + fputs("# No DNS servers known.\n", f); + else { + unsigned count = 0; + DnsServer *s; + + ORDERED_SET_FOREACH(s, dns, i) + write_resolv_conf_server(s, f, &count); + } + + if (!ordered_set_isempty(domains)) { + unsigned length = 0, count = 0; + char *domain; + + fputs("search", f); + ORDERED_SET_FOREACH(domain, domains, i) + write_resolv_conf_search(domain, f, &count, &length); + fputs("\n", f); + } + + return fflush_and_check(f); +} + +int manager_write_resolv_conf(Manager *m) { + + _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL; + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(m); + + /* Read the system /etc/resolv.conf first */ + manager_read_resolv_conf(m); + + /* Add the full list to a set, to filter out duplicates */ + r = manager_compile_dns_servers(m, &dns); + if (r < 0) + return r; + + r = manager_compile_search_domains(m, &domains); + if (r < 0) + return r; + + r = fopen_temporary_label(PRIVATE_RESOLV_CONF, PRIVATE_RESOLV_CONF, &f, &temp_path); + if (r < 0) + return r; + + fchmod(fileno(f), 0644); + + r = write_resolv_conf_contents(f, dns, domains); + if (r < 0) + goto fail; + + if (rename(temp_path, PRIVATE_RESOLV_CONF) < 0) { + r = -errno; + goto fail; + } + + return 0; + +fail: + (void) unlink(PRIVATE_RESOLV_CONF); + (void) unlink(temp_path); + return r; +} diff --git a/src/resolve/src/resolved-resolv-conf.h b/src/resolve/src/resolved-resolv-conf.h new file mode 100644 index 0000000000..75fa080e4c --- /dev/null +++ b/src/resolve/src/resolved-resolv-conf.h @@ -0,0 +1,27 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#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/src/resolved.c b/src/resolve/src/resolved.c new file mode 100644 index 0000000000..c7e2ab14d6 --- /dev/null +++ b/src/resolve/src/resolved.c @@ -0,0 +1,112 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include "sd-daemon.h" +#include "sd-event.h" + +#include "capability-util.h" +#include "mkdir.h" +#include "resolved-conf.h" +#include "resolved-manager.h" +#include "resolved-resolv-conf.h" +#include "selinux-util.h" +#include "signal-util.h" +#include "user-util.h" + +int main(int argc, char *argv[]) { + _cleanup_(manager_freep) Manager *m = NULL; + const char *user = "systemd-resolve"; + uid_t uid; + gid_t gid; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto finish; + } + + umask(0022); + + r = mac_selinux_init(NULL); + if (r < 0) { + log_error_errno(r, "SELinux setup failed: %m"); + goto finish; + } + + r = get_user_creds(&user, &uid, &gid, NULL, NULL); + if (r < 0) { + log_error_errno(r, "Cannot resolve user name %s: %m", user); + goto finish; + } + + /* Always create the directory where resolv.conf will live */ + r = mkdir_safe_label("/run/systemd/resolve", 0755, uid, gid); + if (r < 0) { + log_error_errno(r, "Could not create runtime directory: %m"); + goto finish; + } + + r = drop_privileges(uid, gid, 0); + if (r < 0) + goto finish; + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGUSR1, -1) >= 0); + + r = manager_new(&m); + if (r < 0) { + log_error_errno(r, "Could not create manager: %m"); + goto finish; + } + + r = manager_start(m); + if (r < 0) { + log_error_errno(r, "Failed to start manager: %m"); + goto finish; + } + + /* Write finish default resolv.conf to avoid a dangling + * symlink */ + r = manager_write_resolv_conf(m); + if (r < 0) + log_warning_errno(r, "Could not create "PRIVATE_RESOLV_CONF": %m"); + + sd_notify(false, + "READY=1\n" + "STATUS=Processing requests..."); + + r = sd_event_loop(m->event); + if (r < 0) { + log_error_errno(r, "Event loop failed: %m"); + goto finish; + } + + sd_event_get_exit_code(m->event, &r); + +finish: + sd_notify(false, + "STOPPING=1\n" + "STATUS=Shutting down..."); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/resolve/src/resolved.conf.in b/src/resolve/src/resolved.conf.in new file mode 100644 index 0000000000..efc9c6733a --- /dev/null +++ b/src/resolve/src/resolved.conf.in @@ -0,0 +1,19 @@ +# This file is part of systemd. +# +# 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. +# +# Entries in this file show the compile time defaults. +# You can change settings by editing this file. +# Defaults can be restored by simply deleting this file. +# +# See resolved.conf(5) for details + +[Resolve] +#DNS= +#FallbackDNS=@DNS_SERVERS@ +#Domains= +#LLMNR=yes +#DNSSEC=no diff --git a/src/resolve/src/test-dnssec-complex.c b/src/resolve/src/test-dnssec-complex.c new file mode 100644 index 0000000000..58c089eb40 --- /dev/null +++ b/src/resolve/src/test-dnssec-complex.c @@ -0,0 +1,236 @@ +/*** + 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 . +***/ + +#include + +#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/src/test-dnssec.c b/src/resolve/src/test-dnssec.c new file mode 100644 index 0000000000..a093d86a91 --- /dev/null +++ b/src/resolve/src/test-dnssec.c @@ -0,0 +1,336 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include +#include + +#include "alloc-util.h" +#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) { + + static const uint8_t signature_blob[] = { + 0x7f, 0x79, 0xdd, 0x5e, 0x89, 0x79, 0x18, 0xd0, 0x34, 0x86, 0x8c, 0x72, 0x77, 0x75, 0x48, 0x4d, + 0xc3, 0x7d, 0x38, 0x04, 0xab, 0xcd, 0x9e, 0x4c, 0x82, 0xb0, 0x92, 0xca, 0xe9, 0x66, 0xe9, 0x6e, + 0x47, 0xc7, 0x68, 0x8c, 0x94, 0xf6, 0x69, 0xcb, 0x75, 0x94, 0xe6, 0x30, 0xa6, 0xfb, 0x68, 0x64, + 0x96, 0x1a, 0x84, 0xe1, 0xdc, 0x16, 0x4c, 0x83, 0x6c, 0x44, 0xf2, 0x74, 0x4d, 0x74, 0x79, 0x8f, + 0xf3, 0xf4, 0x63, 0x0d, 0xef, 0x5a, 0xe7, 0xe2, 0xfd, 0xf2, 0x2b, 0x38, 0x7c, 0x28, 0x96, 0x9d, + 0xb6, 0xcd, 0x5c, 0x3b, 0x57, 0xe2, 0x24, 0x78, 0x65, 0xd0, 0x9e, 0x77, 0x83, 0x09, 0x6c, 0xff, + 0x3d, 0x52, 0x3f, 0x6e, 0xd1, 0xed, 0x2e, 0xf9, 0xee, 0x8e, 0xa6, 0xbe, 0x9a, 0xa8, 0x87, 0x76, + 0xd8, 0x77, 0xcc, 0x96, 0xa0, 0x98, 0xa1, 0xd1, 0x68, 0x09, 0x43, 0xcf, 0x56, 0xd9, 0xd1, 0x66, + }; + + static const uint8_t dnskey_blob[] = { + 0x03, 0x01, 0x00, 0x01, 0x9b, 0x49, 0x9b, 0xc1, 0xf9, 0x9a, 0xe0, 0x4e, 0xcf, 0xcb, 0x14, 0x45, + 0x2e, 0xc9, 0xf9, 0x74, 0xa7, 0x18, 0xb5, 0xf3, 0xde, 0x39, 0x49, 0xdf, 0x63, 0x33, 0x97, 0x52, + 0xe0, 0x8e, 0xac, 0x50, 0x30, 0x8e, 0x09, 0xd5, 0x24, 0x3d, 0x26, 0xa4, 0x49, 0x37, 0x2b, 0xb0, + 0x6b, 0x1b, 0xdf, 0xde, 0x85, 0x83, 0xcb, 0x22, 0x4e, 0x60, 0x0a, 0x91, 0x1a, 0x1f, 0xc5, 0x40, + 0xb1, 0xc3, 0x15, 0xc1, 0x54, 0x77, 0x86, 0x65, 0x53, 0xec, 0x10, 0x90, 0x0c, 0x91, 0x00, 0x5e, + 0x15, 0xdc, 0x08, 0x02, 0x4c, 0x8c, 0x0d, 0xc0, 0xac, 0x6e, 0xc4, 0x3e, 0x1b, 0x80, 0x19, 0xe4, + 0xf7, 0x5f, 0x77, 0x51, 0x06, 0x87, 0x61, 0xde, 0xa2, 0x18, 0x0f, 0x40, 0x8b, 0x79, 0x72, 0xfa, + 0x8d, 0x1a, 0x44, 0x47, 0x0d, 0x8e, 0x3a, 0x2d, 0xc7, 0x39, 0xbf, 0x56, 0x28, 0x97, 0xd9, 0x20, + 0x4f, 0x00, 0x51, 0x3b, + }; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *a = NULL, *rrsig = NULL, *dnskey = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + DnssecResult result; + + a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "nAsA.gov"); + assert_se(a); + + a->a.in_addr.s_addr = inet_addr("52.0.14.116"); + + 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); + + rrsig->rrsig.type_covered = DNS_TYPE_A; + rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; + rrsig->rrsig.labels = 2; + rrsig->rrsig.original_ttl = 600; + rrsig->rrsig.expiration = 0x5683135c; + rrsig->rrsig.inception = 0x565b7da8; + rrsig->rrsig.key_tag = 63876; + 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(a->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, 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); + assert_se(result == DNSSEC_VALIDATED); +} + +static void test_dnssec_verify_dns_key(void) { + + static const uint8_t ds1_fprint[] = { + 0x46, 0x8B, 0xC8, 0xDD, 0xC7, 0xE8, 0x27, 0x03, 0x40, 0xBB, 0x8A, 0x1F, 0x3B, 0x2E, 0x45, 0x9D, + 0x80, 0x67, 0x14, 0x01, + }; + static const uint8_t ds2_fprint[] = { + 0x8A, 0xEE, 0x80, 0x47, 0x05, 0x5F, 0x83, 0xD1, 0x48, 0xBA, 0x8F, 0xF6, 0xDD, 0xA7, 0x60, 0xCE, + 0x94, 0xF7, 0xC7, 0x5E, 0x52, 0x4C, 0xF2, 0xE9, 0x50, 0xB9, 0x2E, 0xCB, 0xEF, 0x96, 0xB9, 0x98, + }; + static const uint8_t dnskey_blob[] = { + 0x03, 0x01, 0x00, 0x01, 0xa8, 0x12, 0xda, 0x4f, 0xd2, 0x7d, 0x54, 0x14, 0x0e, 0xcc, 0x5b, 0x5e, + 0x45, 0x9c, 0x96, 0x98, 0xc0, 0xc0, 0x85, 0x81, 0xb1, 0x47, 0x8c, 0x7d, 0xe8, 0x39, 0x50, 0xcc, + 0xc5, 0xd0, 0xf2, 0x00, 0x81, 0x67, 0x79, 0xf6, 0xcc, 0x9d, 0xad, 0x6c, 0xbb, 0x7b, 0x6f, 0x48, + 0x97, 0x15, 0x1c, 0xfd, 0x0b, 0xfe, 0xd3, 0xd7, 0x7d, 0x9f, 0x81, 0x26, 0xd3, 0xc5, 0x65, 0x49, + 0xcf, 0x46, 0x62, 0xb0, 0x55, 0x6e, 0x47, 0xc7, 0x30, 0xef, 0x51, 0xfb, 0x3e, 0xc6, 0xef, 0xde, + 0x27, 0x3f, 0xfa, 0x57, 0x2d, 0xa7, 0x1d, 0x80, 0x46, 0x9a, 0x5f, 0x14, 0xb3, 0xb0, 0x2c, 0xbe, + 0x72, 0xca, 0xdf, 0xb2, 0xff, 0x36, 0x5b, 0x4f, 0xec, 0x58, 0x8e, 0x8d, 0x01, 0xe9, 0xa9, 0xdf, + 0xb5, 0x60, 0xad, 0x52, 0x4d, 0xfc, 0xa9, 0x3e, 0x8d, 0x35, 0x95, 0xb3, 0x4e, 0x0f, 0xca, 0x45, + 0x1b, 0xf7, 0xef, 0x3a, 0x88, 0x25, 0x08, 0xc7, 0x4e, 0x06, 0xc1, 0x62, 0x1a, 0xce, 0xd8, 0x77, + 0xbd, 0x02, 0x65, 0xf8, 0x49, 0xfb, 0xce, 0xf6, 0xa8, 0x09, 0xfc, 0xde, 0xb2, 0x09, 0x9d, 0x39, + 0xf8, 0x63, 0x9c, 0x32, 0x42, 0x7c, 0xa0, 0x30, 0x86, 0x72, 0x7a, 0x4a, 0xc6, 0xd4, 0xb3, 0x2d, + 0x24, 0xef, 0x96, 0x3f, 0xc2, 0xda, 0xd3, 0xf2, 0x15, 0x6f, 0xda, 0x65, 0x4b, 0x81, 0x28, 0x68, + 0xf4, 0xfe, 0x3e, 0x71, 0x4f, 0x50, 0x96, 0x72, 0x58, 0xa1, 0x89, 0xdd, 0x01, 0x61, 0x39, 0x39, + 0xc6, 0x76, 0xa4, 0xda, 0x02, 0x70, 0x3d, 0xc0, 0xdc, 0x8d, 0x70, 0x72, 0x04, 0x90, 0x79, 0xd4, + 0xec, 0x65, 0xcf, 0x49, 0x35, 0x25, 0x3a, 0x14, 0x1a, 0x45, 0x20, 0xeb, 0x31, 0xaf, 0x92, 0xba, + 0x20, 0xd3, 0xcd, 0xa7, 0x13, 0x44, 0xdc, 0xcf, 0xf0, 0x27, 0x34, 0xb9, 0xe7, 0x24, 0x6f, 0x73, + 0xe7, 0xea, 0x77, 0x03, + }; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds1 = NULL, *ds2 = 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"); + assert_se(ds1); + + ds1->ds.key_tag = 47857; + ds1->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; + ds1->ds.digest_type = DNSSEC_DIGEST_SHA1; + ds1->ds.digest_size = sizeof(ds1_fprint); + ds1->ds.digest = memdup(ds1_fprint, ds1->ds.digest_size); + assert_se(ds1->ds.digest); + + 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); + + ds2->ds.key_tag = 47857; + ds2->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; + ds2->ds.digest_type = DNSSEC_DIGEST_SHA256; + ds2->ds.digest_size = sizeof(ds2_fprint); + ds2->ds.digest = memdup(ds2_fprint, ds2->ds.digest_size); + assert_se(ds2->ds.digest); + + 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); + + dnskey->dnskey.flags = 257; + 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_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) { + char canonicalized[DNSSEC_CANONICAL_HOSTNAME_MAX]; + + assert_se(dnssec_canonicalize(original, canonicalized, sizeof(canonicalized)) == r); + if (r < 0) + return; + + assert_se(streq(canonicalized, canonical)); +} + +static void test_dnssec_canonicalize(void) { + test_dnssec_canonicalize_one("", ".", 1); + test_dnssec_canonicalize_one(".", ".", 1); + test_dnssec_canonicalize_one("foo", "foo.", 4); + test_dnssec_canonicalize_one("foo.", "foo.", 4); + test_dnssec_canonicalize_one("FOO.", "foo.", 4); + test_dnssec_canonicalize_one("FOO.bar.", "foo.bar.", 8); + 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/src/test-resolve-tables.c b/src/resolve/src/test-resolve-tables.c new file mode 100644 index 0000000000..63660afc87 --- /dev/null +++ b/src/resolve/src/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 . +***/ + +#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/resolve/test-dnssec-complex.c b/src/resolve/test-dnssec-complex.c deleted file mode 100644 index 58c089eb40..0000000000 --- a/src/resolve/test-dnssec-complex.c +++ /dev/null @@ -1,236 +0,0 @@ -/*** - 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 . -***/ - -#include - -#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 deleted file mode 100644 index a093d86a91..0000000000 --- a/src/resolve/test-dnssec.c +++ /dev/null @@ -1,336 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include -#include - -#include "alloc-util.h" -#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) { - - static const uint8_t signature_blob[] = { - 0x7f, 0x79, 0xdd, 0x5e, 0x89, 0x79, 0x18, 0xd0, 0x34, 0x86, 0x8c, 0x72, 0x77, 0x75, 0x48, 0x4d, - 0xc3, 0x7d, 0x38, 0x04, 0xab, 0xcd, 0x9e, 0x4c, 0x82, 0xb0, 0x92, 0xca, 0xe9, 0x66, 0xe9, 0x6e, - 0x47, 0xc7, 0x68, 0x8c, 0x94, 0xf6, 0x69, 0xcb, 0x75, 0x94, 0xe6, 0x30, 0xa6, 0xfb, 0x68, 0x64, - 0x96, 0x1a, 0x84, 0xe1, 0xdc, 0x16, 0x4c, 0x83, 0x6c, 0x44, 0xf2, 0x74, 0x4d, 0x74, 0x79, 0x8f, - 0xf3, 0xf4, 0x63, 0x0d, 0xef, 0x5a, 0xe7, 0xe2, 0xfd, 0xf2, 0x2b, 0x38, 0x7c, 0x28, 0x96, 0x9d, - 0xb6, 0xcd, 0x5c, 0x3b, 0x57, 0xe2, 0x24, 0x78, 0x65, 0xd0, 0x9e, 0x77, 0x83, 0x09, 0x6c, 0xff, - 0x3d, 0x52, 0x3f, 0x6e, 0xd1, 0xed, 0x2e, 0xf9, 0xee, 0x8e, 0xa6, 0xbe, 0x9a, 0xa8, 0x87, 0x76, - 0xd8, 0x77, 0xcc, 0x96, 0xa0, 0x98, 0xa1, 0xd1, 0x68, 0x09, 0x43, 0xcf, 0x56, 0xd9, 0xd1, 0x66, - }; - - static const uint8_t dnskey_blob[] = { - 0x03, 0x01, 0x00, 0x01, 0x9b, 0x49, 0x9b, 0xc1, 0xf9, 0x9a, 0xe0, 0x4e, 0xcf, 0xcb, 0x14, 0x45, - 0x2e, 0xc9, 0xf9, 0x74, 0xa7, 0x18, 0xb5, 0xf3, 0xde, 0x39, 0x49, 0xdf, 0x63, 0x33, 0x97, 0x52, - 0xe0, 0x8e, 0xac, 0x50, 0x30, 0x8e, 0x09, 0xd5, 0x24, 0x3d, 0x26, 0xa4, 0x49, 0x37, 0x2b, 0xb0, - 0x6b, 0x1b, 0xdf, 0xde, 0x85, 0x83, 0xcb, 0x22, 0x4e, 0x60, 0x0a, 0x91, 0x1a, 0x1f, 0xc5, 0x40, - 0xb1, 0xc3, 0x15, 0xc1, 0x54, 0x77, 0x86, 0x65, 0x53, 0xec, 0x10, 0x90, 0x0c, 0x91, 0x00, 0x5e, - 0x15, 0xdc, 0x08, 0x02, 0x4c, 0x8c, 0x0d, 0xc0, 0xac, 0x6e, 0xc4, 0x3e, 0x1b, 0x80, 0x19, 0xe4, - 0xf7, 0x5f, 0x77, 0x51, 0x06, 0x87, 0x61, 0xde, 0xa2, 0x18, 0x0f, 0x40, 0x8b, 0x79, 0x72, 0xfa, - 0x8d, 0x1a, 0x44, 0x47, 0x0d, 0x8e, 0x3a, 0x2d, 0xc7, 0x39, 0xbf, 0x56, 0x28, 0x97, 0xd9, 0x20, - 0x4f, 0x00, 0x51, 0x3b, - }; - - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *a = NULL, *rrsig = NULL, *dnskey = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - DnssecResult result; - - a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "nAsA.gov"); - assert_se(a); - - a->a.in_addr.s_addr = inet_addr("52.0.14.116"); - - 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); - - rrsig->rrsig.type_covered = DNS_TYPE_A; - rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; - rrsig->rrsig.labels = 2; - rrsig->rrsig.original_ttl = 600; - rrsig->rrsig.expiration = 0x5683135c; - rrsig->rrsig.inception = 0x565b7da8; - rrsig->rrsig.key_tag = 63876; - 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(a->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, 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); - assert_se(result == DNSSEC_VALIDATED); -} - -static void test_dnssec_verify_dns_key(void) { - - static const uint8_t ds1_fprint[] = { - 0x46, 0x8B, 0xC8, 0xDD, 0xC7, 0xE8, 0x27, 0x03, 0x40, 0xBB, 0x8A, 0x1F, 0x3B, 0x2E, 0x45, 0x9D, - 0x80, 0x67, 0x14, 0x01, - }; - static const uint8_t ds2_fprint[] = { - 0x8A, 0xEE, 0x80, 0x47, 0x05, 0x5F, 0x83, 0xD1, 0x48, 0xBA, 0x8F, 0xF6, 0xDD, 0xA7, 0x60, 0xCE, - 0x94, 0xF7, 0xC7, 0x5E, 0x52, 0x4C, 0xF2, 0xE9, 0x50, 0xB9, 0x2E, 0xCB, 0xEF, 0x96, 0xB9, 0x98, - }; - static const uint8_t dnskey_blob[] = { - 0x03, 0x01, 0x00, 0x01, 0xa8, 0x12, 0xda, 0x4f, 0xd2, 0x7d, 0x54, 0x14, 0x0e, 0xcc, 0x5b, 0x5e, - 0x45, 0x9c, 0x96, 0x98, 0xc0, 0xc0, 0x85, 0x81, 0xb1, 0x47, 0x8c, 0x7d, 0xe8, 0x39, 0x50, 0xcc, - 0xc5, 0xd0, 0xf2, 0x00, 0x81, 0x67, 0x79, 0xf6, 0xcc, 0x9d, 0xad, 0x6c, 0xbb, 0x7b, 0x6f, 0x48, - 0x97, 0x15, 0x1c, 0xfd, 0x0b, 0xfe, 0xd3, 0xd7, 0x7d, 0x9f, 0x81, 0x26, 0xd3, 0xc5, 0x65, 0x49, - 0xcf, 0x46, 0x62, 0xb0, 0x55, 0x6e, 0x47, 0xc7, 0x30, 0xef, 0x51, 0xfb, 0x3e, 0xc6, 0xef, 0xde, - 0x27, 0x3f, 0xfa, 0x57, 0x2d, 0xa7, 0x1d, 0x80, 0x46, 0x9a, 0x5f, 0x14, 0xb3, 0xb0, 0x2c, 0xbe, - 0x72, 0xca, 0xdf, 0xb2, 0xff, 0x36, 0x5b, 0x4f, 0xec, 0x58, 0x8e, 0x8d, 0x01, 0xe9, 0xa9, 0xdf, - 0xb5, 0x60, 0xad, 0x52, 0x4d, 0xfc, 0xa9, 0x3e, 0x8d, 0x35, 0x95, 0xb3, 0x4e, 0x0f, 0xca, 0x45, - 0x1b, 0xf7, 0xef, 0x3a, 0x88, 0x25, 0x08, 0xc7, 0x4e, 0x06, 0xc1, 0x62, 0x1a, 0xce, 0xd8, 0x77, - 0xbd, 0x02, 0x65, 0xf8, 0x49, 0xfb, 0xce, 0xf6, 0xa8, 0x09, 0xfc, 0xde, 0xb2, 0x09, 0x9d, 0x39, - 0xf8, 0x63, 0x9c, 0x32, 0x42, 0x7c, 0xa0, 0x30, 0x86, 0x72, 0x7a, 0x4a, 0xc6, 0xd4, 0xb3, 0x2d, - 0x24, 0xef, 0x96, 0x3f, 0xc2, 0xda, 0xd3, 0xf2, 0x15, 0x6f, 0xda, 0x65, 0x4b, 0x81, 0x28, 0x68, - 0xf4, 0xfe, 0x3e, 0x71, 0x4f, 0x50, 0x96, 0x72, 0x58, 0xa1, 0x89, 0xdd, 0x01, 0x61, 0x39, 0x39, - 0xc6, 0x76, 0xa4, 0xda, 0x02, 0x70, 0x3d, 0xc0, 0xdc, 0x8d, 0x70, 0x72, 0x04, 0x90, 0x79, 0xd4, - 0xec, 0x65, 0xcf, 0x49, 0x35, 0x25, 0x3a, 0x14, 0x1a, 0x45, 0x20, 0xeb, 0x31, 0xaf, 0x92, 0xba, - 0x20, 0xd3, 0xcd, 0xa7, 0x13, 0x44, 0xdc, 0xcf, 0xf0, 0x27, 0x34, 0xb9, 0xe7, 0x24, 0x6f, 0x73, - 0xe7, 0xea, 0x77, 0x03, - }; - - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds1 = NULL, *ds2 = 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"); - assert_se(ds1); - - ds1->ds.key_tag = 47857; - ds1->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; - ds1->ds.digest_type = DNSSEC_DIGEST_SHA1; - ds1->ds.digest_size = sizeof(ds1_fprint); - ds1->ds.digest = memdup(ds1_fprint, ds1->ds.digest_size); - assert_se(ds1->ds.digest); - - 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); - - ds2->ds.key_tag = 47857; - ds2->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; - ds2->ds.digest_type = DNSSEC_DIGEST_SHA256; - ds2->ds.digest_size = sizeof(ds2_fprint); - ds2->ds.digest = memdup(ds2_fprint, ds2->ds.digest_size); - assert_se(ds2->ds.digest); - - 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); - - dnskey->dnskey.flags = 257; - 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_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) { - char canonicalized[DNSSEC_CANONICAL_HOSTNAME_MAX]; - - assert_se(dnssec_canonicalize(original, canonicalized, sizeof(canonicalized)) == r); - if (r < 0) - return; - - assert_se(streq(canonicalized, canonical)); -} - -static void test_dnssec_canonicalize(void) { - test_dnssec_canonicalize_one("", ".", 1); - test_dnssec_canonicalize_one(".", ".", 1); - test_dnssec_canonicalize_one("foo", "foo.", 4); - test_dnssec_canonicalize_one("foo.", "foo.", 4); - test_dnssec_canonicalize_one("FOO.", "foo.", 4); - test_dnssec_canonicalize_one("FOO.bar.", "foo.bar.", 8); - 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 deleted file mode 100644 index 63660afc87..0000000000 --- a/src/resolve/test-resolve-tables.c +++ /dev/null @@ -1,27 +0,0 @@ -/*** - 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 . -***/ - -#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/system/systemd/Makefile b/src/system/systemd/Makefile new file mode 100644 index 0000000000..e873ba3f94 --- /dev/null +++ b/src/system/systemd/Makefile @@ -0,0 +1,63 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . + +systemd_SOURCES = \ + src/core/main.c + +systemd_CFLAGS = \ + $(AM_CFLAGS) \ + $(SECCOMP_CFLAGS) \ + $(MOUNT_CFLAGS) + +systemd_LDADD = \ + libcore.la + +dist_pkgsysconf_DATA += \ + src/core/system.conf \ + src/core/user.conf + +dist_dbuspolicy_DATA += \ + src/core/org.freedesktop.systemd1.conf + +dist_dbussystemservice_DATA += \ + src/core/org.freedesktop.systemd1.service + +polkitpolicy_in_in_files += \ + src/core/org.freedesktop.systemd1.policy.in.in + +pkgconfigdata_DATA += \ + src/core/systemd.pc + +nodist_rpmmacros_DATA = \ + src/core/macros.systemd + +EXTRA_DIST += \ + src/core/systemd.pc.in \ + src/core/macros.systemd.in \ + src/core/triggers.systemd.in + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.systemd1.busname + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.systemd1.busname diff --git a/src/system/systemd/macros.systemd.in b/src/system/systemd/macros.systemd.in new file mode 100644 index 0000000000..2cace3d3ba --- /dev/null +++ b/src/system/systemd/macros.systemd.in @@ -0,0 +1,105 @@ +# -*- Mode: rpm-spec; indent-tabs-mode: nil -*- */ +# +# This file is part of systemd. +# +# Copyright 2012 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 . + +# RPM macros for packages installing systemd unit files + +%_unitdir @systemunitdir@ +%_userunitdir @userunitdir@ +%_presetdir @systempresetdir@ +%_udevhwdbdir @udevhwdbdir@ +%_udevrulesdir @udevrulesdir@ +%_journalcatalogdir @catalogdir@ +%_tmpfilesdir @tmpfilesdir@ +%_sysusersdir @sysusersdir@ +%_sysctldir @sysctldir@ +%_binfmtdir @binfmtdir@ + +%systemd_requires \ +Requires(post): systemd \ +Requires(preun): systemd \ +Requires(postun): systemd \ +%{nil} + +%systemd_post() \ +if [ $1 -eq 1 ] ; then \ + # Initial installation \ + systemctl --no-reload preset %{?*} >/dev/null 2>&1 || : \ +fi \ +%{nil} + +%systemd_user_post() %{expand:%systemd_post \\--user \\--global %%{?*}} + +%systemd_preun() \ +if [ $1 -eq 0 ] ; then \ + # Package removal, not upgrade \ + systemctl --no-reload disable --now %{?*} > /dev/null 2>&1 || : \ +fi \ +%{nil} + +%systemd_user_preun() \ +if [ $1 -eq 0 ] ; then \ + # Package removal, not upgrade \ + systemctl --no-reload --user --global disable %{?*} > /dev/null 2>&1 || : \ +fi \ +%{nil} + +%systemd_postun() %{nil} + +%systemd_user_postun() %{nil} + +%systemd_postun_with_restart() \ +if [ $1 -ge 1 ] ; then \ + # Package upgrade, not uninstall \ + systemctl try-restart %{?*} >/dev/null 2>&1 || : \ +fi \ +%{nil} + +%systemd_user_postun_with_restart() %{nil} + +%udev_hwdb_update() \ +udevadm hwdb --update >/dev/null 2>&1 || : \ +%{nil} + +%udev_rules_update() \ +udevadm control --reload >/dev/null 2>&1 || : \ +%{nil} + +%journal_catalog_update() \ +journalctl --update-catalog >/dev/null 2>&1 || : \ +%{nil} + +%tmpfiles_create() \ +systemd-tmpfiles --create %{?*} >/dev/null 2>&1 || : \ +%{nil} + +%sysusers_create() \ +systemd-sysusers %{?*} >/dev/null 2>&1 || : \ +%{nil} + +%sysusers_create_inline() \ +echo %{?*} | systemd-sysusers - >/dev/null 2>&1 || : \ +%{nil} + +%sysctl_apply() \ +@rootlibexecdir@/systemd-sysctl %{?*} >/dev/null 2>&1 || : \ +%{nil} + +%binfmt_apply() \ +@rootlibexecdir@/systemd-binfmt %{?*} >/dev/null 2>&1 || : \ +%{nil} diff --git a/src/system/systemd/main.c b/src/system/systemd/main.c new file mode 100644 index 0000000000..5346392d73 --- /dev/null +++ b/src/system/systemd/main.c @@ -0,0 +1,2142 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SECCOMP +#include +#endif +#ifdef HAVE_VALGRIND_VALGRIND_H +#include +#endif + +#include "sd-bus.h" +#include "sd-daemon.h" + +#include "alloc-util.h" +#include "architecture.h" +#include "build.h" +#include "bus-error.h" +#include "bus-util.h" +#include "capability-util.h" +#include "clock-util.h" +#include "conf-parser.h" +#include "cpu-set-util.h" +#include "dbus-manager.h" +#include "def.h" +#include "env-util.h" +#include "fd-util.h" +#include "fdset.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "hostname-setup.h" +#include "ima-setup.h" +#include "killall.h" +#include "kmod-setup.h" +#include "load-fragment.h" +#include "log.h" +#include "loopback-setup.h" +#include "machine-id-setup.h" +#include "manager.h" +#include "missing.h" +#include "mount-setup.h" +#include "pager.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "process-util.h" +#include "rlimit-util.h" +#include "selinux-setup.h" +#include "selinux-util.h" +#include "signal-util.h" +#include "smack-setup.h" +#include "special.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "strv.h" +#include "switch-root.h" +#include "terminal-util.h" +#include "user-util.h" +#include "virt.h" +#include "watchdog.h" + +static enum { + ACTION_RUN, + ACTION_HELP, + ACTION_VERSION, + ACTION_TEST, + ACTION_DUMP_CONFIGURATION_ITEMS, + ACTION_DONE +} arg_action = ACTION_RUN; +static char *arg_default_unit = NULL; +static ManagerRunningAs arg_running_as = _MANAGER_RUNNING_AS_INVALID; +static bool arg_dump_core = true; +static int arg_crash_chvt = -1; +static bool arg_crash_shell = false; +static bool arg_crash_reboot = false; +static bool arg_confirm_spawn = false; +static ShowStatus arg_show_status = _SHOW_STATUS_UNSET; +static bool arg_switched_root = false; +static int arg_no_pager = -1; +static char ***arg_join_controllers = NULL; +static ExecOutput arg_default_std_output = EXEC_OUTPUT_JOURNAL; +static ExecOutput arg_default_std_error = EXEC_OUTPUT_INHERIT; +static usec_t arg_default_restart_usec = DEFAULT_RESTART_USEC; +static usec_t arg_default_timeout_start_usec = DEFAULT_TIMEOUT_USEC; +static usec_t arg_default_timeout_stop_usec = DEFAULT_TIMEOUT_USEC; +static usec_t arg_default_start_limit_interval = DEFAULT_START_LIMIT_INTERVAL; +static unsigned arg_default_start_limit_burst = DEFAULT_START_LIMIT_BURST; +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 = 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; +static FILE* arg_serialization = NULL; +static bool arg_default_cpu_accounting = false; +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) { + + if (arg_no_pager <= 0) + return; + + pager_open(false); +} + +noreturn static void freeze_or_reboot(void) { + + if (arg_crash_reboot) { + log_notice("Rebooting in 10s..."); + (void) sleep(10); + + log_notice("Rebooting now..."); + (void) reboot(RB_AUTOBOOT); + log_emergency_errno(errno, "Failed to reboot: %m"); + } + + log_emergency("Freezing execution."); + freeze(); +} + +noreturn static void crash(int sig) { + struct sigaction sa; + pid_t pid; + + if (getpid() != 1) + /* Pass this on immediately, if this is not PID 1 */ + (void) raise(sig); + else if (!arg_dump_core) + log_emergency("Caught <%s>, not dumping core.", signal_to_string(sig)); + else { + sa = (struct sigaction) { + .sa_handler = nop_signal_handler, + .sa_flags = SA_NOCLDSTOP|SA_RESTART, + }; + + /* We want to wait for the core process, hence let's enable SIGCHLD */ + (void) sigaction(SIGCHLD, &sa, NULL); + + pid = raw_clone(SIGCHLD, NULL); + if (pid < 0) + log_emergency_errno(errno, "Caught <%s>, cannot fork for core dump: %m", signal_to_string(sig)); + else if (pid == 0) { + /* Enable default signal handler for core dump */ + + sa = (struct sigaction) { + .sa_handler = SIG_DFL, + }; + (void) sigaction(sig, &sa, NULL); + + /* Don't limit the coredump size */ + (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY)); + + /* Just to be sure... */ + (void) chdir("/"); + + /* Raise the signal again */ + pid = raw_getpid(); + (void) kill(pid, sig); /* raise() would kill the parent */ + + assert_not_reached("We shouldn't be here..."); + _exit(EXIT_FAILURE); + } else { + siginfo_t status; + int r; + + /* Order things nicely. */ + r = wait_for_terminate(pid, &status); + if (r < 0) + log_emergency_errno(r, "Caught <%s>, waitpid() failed: %m", signal_to_string(sig)); + else if (status.si_code != CLD_DUMPED) + log_emergency("Caught <%s>, core dump failed (child "PID_FMT", code=%s, status=%i/%s).", + signal_to_string(sig), + pid, sigchld_code_to_string(status.si_code), + status.si_status, + strna(status.si_code == CLD_EXITED + ? exit_status_to_string(status.si_status, EXIT_STATUS_FULL) + : signal_to_string(status.si_status))); + else + log_emergency("Caught <%s>, dumped core as pid "PID_FMT".", signal_to_string(sig), pid); + } + } + + if (arg_crash_chvt >= 0) + (void) chvt(arg_crash_chvt); + + sa = (struct sigaction) { + .sa_handler = SIG_IGN, + .sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT|SA_RESTART, + }; + + /* Let the kernel reap children for us */ + (void) sigaction(SIGCHLD, &sa, NULL); + + if (arg_crash_shell) { + log_notice("Executing crash shell in 10s..."); + (void) sleep(10); + + pid = raw_clone(SIGCHLD, NULL); + if (pid < 0) + log_emergency_errno(errno, "Failed to fork off crash shell: %m"); + else if (pid == 0) { + (void) setsid(); + (void) make_console_stdio(); + (void) execle("/bin/sh", "/bin/sh", NULL, environ); + + log_emergency_errno(errno, "execle() failed: %m"); + _exit(EXIT_FAILURE); + } else { + log_info("Spawned crash shell as PID "PID_FMT".", pid); + (void) wait_for_terminate(pid, NULL); + } + } + + freeze_or_reboot(); +} + +static void install_crash_handler(void) { + static const struct sigaction sa = { + .sa_handler = crash, + .sa_flags = SA_NODEFER, /* So that we can raise the signal again from the signal handler */ + }; + int r; + + /* We ignore the return value here, since, we don't mind if we + * cannot set up a crash handler */ + r = sigaction_many(&sa, SIGNALS_CRASH_HANDLER, -1); + if (r < 0) + log_debug_errno(r, "I had trouble setting up the crash handler, ignoring: %m"); +} + +static int console_setup(void) { + _cleanup_close_ int tty_fd = -1; + int r; + + tty_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (tty_fd < 0) + return log_error_errno(tty_fd, "Failed to open /dev/console: %m"); + + /* We don't want to force text mode. plymouth may be showing + * pictures already from initrd. */ + r = reset_terminal_fd(tty_fd, false); + if (r < 0) + return log_error_errno(r, "Failed to reset /dev/console: %m"); + + return 0; +} + +static int parse_crash_chvt(const char *value) { + int b; + + if (safe_atoi(value, &arg_crash_chvt) >= 0) + return 0; + + b = parse_boolean(value); + if (b < 0) + return b; + + if (b > 0) + arg_crash_chvt = 0; /* switch to where kmsg goes */ + else + arg_crash_chvt = -1; /* turn off switching */ + + 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; + + assert(key); + + if (streq(key, "systemd.unit") && value) { + + if (!in_initrd()) + return free_and_strdup(&arg_default_unit, value); + + } else if (streq(key, "rd.systemd.unit") && value) { + + if (in_initrd()) + return free_and_strdup(&arg_default_unit, value); + + } else if (streq(key, "systemd.dump_core") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse dump core switch %s. Ignoring.", value); + else + arg_dump_core = r; + + } else if (streq(key, "systemd.crash_chvt") && value) { + + if (parse_crash_chvt(value) < 0) + log_warning("Failed to parse crash chvt switch %s. Ignoring.", value); + + } else if (streq(key, "systemd.crash_shell") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse crash shell switch %s. Ignoring.", value); + else + arg_crash_shell = r; + + } else if (streq(key, "systemd.crash_reboot") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse crash reboot switch %s. Ignoring.", value); + else + arg_crash_reboot = r; + + } else if (streq(key, "systemd.confirm_spawn") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse confirm spawn switch %s. Ignoring.", value); + else + arg_confirm_spawn = r; + + } else if (streq(key, "systemd.show_status") && value) { + + r = parse_show_status(value, &arg_show_status); + if (r < 0) + log_warning("Failed to parse show status switch %s. Ignoring.", value); + + } else if (streq(key, "systemd.default_standard_output") && value) { + + r = exec_output_from_string(value); + if (r < 0) + log_warning("Failed to parse default standard output switch %s. Ignoring.", value); + else + arg_default_std_output = r; + + } else if (streq(key, "systemd.default_standard_error") && value) { + + r = exec_output_from_string(value); + if (r < 0) + log_warning("Failed to parse default standard error switch %s. Ignoring.", value); + else + arg_default_std_error = r; + + } else if (streq(key, "systemd.setenv") && value) { + + if (env_assignment_is_valid(value)) { + char **env; + + env = strv_env_set(arg_default_environment, value); + if (env) + arg_default_environment = env; + else + log_warning_errno(ENOMEM, "Setting environment variable '%s' failed, ignoring: %m", 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) + arg_show_status = SHOW_STATUS_AUTO; + + } else if (streq(key, "debug") && !value) { + + /* Note that log_parse_environment() handles 'debug' + * too, and sets the log level to LOG_DEBUG. */ + + if (detect_container() > 0) + log_set_target(LOG_TARGET_CONSOLE); + + } else if (!in_initrd() && !value) { + const char *target; + + /* SysV compatibility */ + target = runlevel_to_target(key); + if (target) + return free_and_strdup(&arg_default_unit, target); + } + + return 0; +} + +#define DEFINE_SETTER(name, func, descr) \ + static int name(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 r; \ + \ + assert(filename); \ + assert(lvalue); \ + assert(rvalue); \ + \ + r = func(rvalue); \ + if (r < 0) \ + log_syntax(unit, LOG_ERR, filename, line, r, \ + "Invalid " descr "'%s': %m", \ + rvalue); \ + \ + return 0; \ + } + +DEFINE_SETTER(config_parse_level2, log_set_max_level_from_string, "log level") +DEFINE_SETTER(config_parse_target, log_set_target_from_string, "target") +DEFINE_SETTER(config_parse_color, log_show_color_from_string, "color" ) +DEFINE_SETTER(config_parse_location, log_show_location_from_string, "location") + +static int config_parse_cpu_affinity2( + 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_cpu_free_ cpu_set_t *c = NULL; + int ncpus; + + ncpus = parse_cpu_set_and_warn(rvalue, &c, unit, filename, line, lvalue); + if (ncpus < 0) + return ncpus; + + if (sched_setaffinity(0, CPU_ALLOC_SIZE(ncpus), c) < 0) + log_warning("Failed to set CPU affinity: %m"); + + return 0; +} + +static int config_parse_show_status( + 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 k; + ShowStatus *b = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + k = parse_show_status(rvalue, b); + if (k < 0) { + log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse show status setting, ignoring: %s", rvalue); + return 0; + } + + return 0; +} + +static int config_parse_crash_chvt( + 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 r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = parse_crash_chvt(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CrashChangeVT= setting, ignoring: %s", rvalue); + return 0; + } + + return 0; +} + +static int config_parse_join_controllers(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 *whole_rvalue = rvalue; + unsigned n = 0; + + assert(filename); + assert(lvalue); + assert(rvalue); + + arg_join_controllers = strv_free_free(arg_join_controllers); + + for (;;) { + _cleanup_free_ char *word = NULL; + char **l; + int r; + + r = extract_first_word(&rvalue, &word, WHITESPACE, EXTRACT_QUOTES); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Invalid value for %s: %s", lvalue, whole_rvalue); + return r; + } + if (r == 0) + break; + + l = strv_split(word, ","); + if (!l) + return log_oom(); + strv_uniq(l); + + if (strv_length(l) <= 1) { + strv_free(l); + continue; + } + + if (!arg_join_controllers) { + arg_join_controllers = new(char**, 2); + if (!arg_join_controllers) { + strv_free(l); + return log_oom(); + } + + arg_join_controllers[0] = l; + arg_join_controllers[1] = NULL; + + n = 1; + } else { + char ***a; + char ***t; + + t = new0(char**, n+2); + if (!t) { + strv_free(l); + return log_oom(); + } + + n = 0; + + for (a = arg_join_controllers; *a; a++) { + + if (strv_overlap(*a, l)) { + if (strv_extend_strv(&l, *a, false) < 0) { + strv_free(l); + strv_free_free(t); + return log_oom(); + } + + } else { + char **c; + + c = strv_copy(*a); + if (!c) { + strv_free(l); + strv_free_free(t); + return log_oom(); + } + + t[n++] = c; + } + } + + t[n++] = strv_uniq(l); + + strv_free_free(arg_join_controllers); + arg_join_controllers = t; + } + } + if (!isempty(rvalue)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); + + return 0; +} + +static int parse_config_file(void) { + + const ConfigTableItem items[] = { + { "Manager", "LogLevel", config_parse_level2, 0, NULL }, + { "Manager", "LogTarget", config_parse_target, 0, NULL }, + { "Manager", "LogColor", config_parse_color, 0, NULL }, + { "Manager", "LogLocation", config_parse_location, 0, NULL }, + { "Manager", "DumpCore", config_parse_bool, 0, &arg_dump_core }, + { "Manager", "CrashChVT", /* legacy */ config_parse_crash_chvt, 0, NULL }, + { "Manager", "CrashChangeVT", config_parse_crash_chvt, 0, NULL }, + { "Manager", "CrashShell", config_parse_bool, 0, &arg_crash_shell }, + { "Manager", "CrashReboot", config_parse_bool, 0, &arg_crash_reboot }, + { "Manager", "ShowStatus", config_parse_show_status, 0, &arg_show_status }, + { "Manager", "CPUAffinity", config_parse_cpu_affinity2, 0, NULL }, + { "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_capability_set, 0, &arg_capability_bounding_set }, +#ifdef HAVE_SECCOMP + { "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs }, +#endif + { "Manager", "TimerSlackNSec", config_parse_nsec, 0, &arg_timer_slack_nsec }, + { "Manager", "DefaultTimerAccuracySec", config_parse_sec, 0, &arg_default_timer_accuracy_usec }, + { "Manager", "DefaultStandardOutput", config_parse_output, 0, &arg_default_std_output }, + { "Manager", "DefaultStandardError", config_parse_output, 0, &arg_default_std_error }, + { "Manager", "DefaultTimeoutStartSec", config_parse_sec, 0, &arg_default_timeout_start_usec }, + { "Manager", "DefaultTimeoutStopSec", config_parse_sec, 0, &arg_default_timeout_stop_usec }, + { "Manager", "DefaultRestartSec", config_parse_sec, 0, &arg_default_restart_usec }, + { "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_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 }, + { "Manager", "DefaultTasksAccounting", config_parse_bool, 0, &arg_default_tasks_accounting }, + { "Manager", "DefaultTasksMax", config_parse_tasks_max, 0, &arg_default_tasks_max }, + {} + }; + + const char *fn, *conf_dirs_nulstr; + + fn = arg_running_as == MANAGER_SYSTEM ? + PKGSYSCONFDIR "/system.conf" : + PKGSYSCONFDIR "/user.conf"; + + conf_dirs_nulstr = arg_running_as == MANAGER_SYSTEM ? + 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); + + /* 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; +} + +static void manager_set_defaults(Manager *m) { + + assert(m); + + m->default_timer_accuracy_usec = arg_default_timer_accuracy_usec; + m->default_std_output = arg_default_std_output; + m->default_std_error = arg_default_std_error; + m->default_timeout_start_usec = arg_default_timeout_start_usec; + m->default_timeout_stop_usec = arg_default_timeout_stop_usec; + m->default_restart_usec = arg_default_restart_usec; + m->default_start_limit_interval = arg_default_start_limit_interval; + m->default_start_limit_burst = arg_default_start_limit_burst; + m->default_cpu_accounting = arg_default_cpu_accounting; + m->default_blockio_accounting = arg_default_blockio_accounting; + m->default_memory_accounting = arg_default_memory_accounting; + m->default_tasks_accounting = arg_default_tasks_accounting; + m->default_tasks_max = arg_default_tasks_max; + + manager_set_default_rlimits(m, arg_default_rlimit); + manager_environment_add(m, NULL, arg_default_environment); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_LOG_LEVEL = 0x100, + ARG_LOG_TARGET, + ARG_LOG_COLOR, + ARG_LOG_LOCATION, + ARG_UNIT, + ARG_SYSTEM, + ARG_USER, + ARG_TEST, + ARG_NO_PAGER, + ARG_VERSION, + ARG_DUMP_CONFIGURATION_ITEMS, + ARG_DUMP_CORE, + ARG_CRASH_CHVT, + ARG_CRASH_SHELL, + ARG_CRASH_REBOOT, + ARG_CONFIRM_SPAWN, + ARG_SHOW_STATUS, + ARG_DESERIALIZE, + ARG_SWITCHED_ROOT, + ARG_DEFAULT_STD_OUTPUT, + ARG_DEFAULT_STD_ERROR, + ARG_MACHINE_ID + }; + + static const struct option options[] = { + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "log-target", required_argument, NULL, ARG_LOG_TARGET }, + { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, + { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, + { "unit", required_argument, NULL, ARG_UNIT }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, + { "test", no_argument, NULL, ARG_TEST }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS }, + { "dump-core", optional_argument, NULL, ARG_DUMP_CORE }, + { "crash-chvt", required_argument, NULL, ARG_CRASH_CHVT }, + { "crash-shell", optional_argument, NULL, ARG_CRASH_SHELL }, + { "crash-reboot", optional_argument, NULL, ARG_CRASH_REBOOT }, + { "confirm-spawn", optional_argument, NULL, ARG_CONFIRM_SPAWN }, + { "show-status", optional_argument, NULL, ARG_SHOW_STATUS }, + { "deserialize", required_argument, NULL, ARG_DESERIALIZE }, + { "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 }, + {} + }; + + int c, r; + + assert(argc >= 1); + assert(argv); + + if (getpid() == 1) + opterr = 0; + + while ((c = getopt_long(argc, argv, "hDbsz:", options, NULL)) >= 0) + + switch (c) { + + case ARG_LOG_LEVEL: + r = log_set_max_level_from_string(optarg); + if (r < 0) { + log_error("Failed to parse log level %s.", optarg); + return r; + } + + break; + + case ARG_LOG_TARGET: + r = log_set_target_from_string(optarg); + if (r < 0) { + log_error("Failed to parse log target %s.", optarg); + return r; + } + + break; + + case ARG_LOG_COLOR: + + if (optarg) { + r = log_show_color_from_string(optarg); + if (r < 0) { + log_error("Failed to parse log color setting %s.", optarg); + return r; + } + } else + log_show_color(true); + + break; + + case ARG_LOG_LOCATION: + if (optarg) { + r = log_show_location_from_string(optarg); + if (r < 0) { + log_error("Failed to parse log location setting %s.", optarg); + return r; + } + } else + log_show_location(true); + + break; + + case ARG_DEFAULT_STD_OUTPUT: + r = exec_output_from_string(optarg); + if (r < 0) { + log_error("Failed to parse default standard output setting %s.", optarg); + return r; + } else + arg_default_std_output = r; + break; + + case ARG_DEFAULT_STD_ERROR: + r = exec_output_from_string(optarg); + if (r < 0) { + log_error("Failed to parse default standard error output setting %s.", optarg); + return r; + } else + arg_default_std_error = r; + break; + + case ARG_UNIT: + + r = free_and_strdup(&arg_default_unit, optarg); + if (r < 0) + return log_error_errno(r, "Failed to set default unit %s: %m", optarg); + + break; + + case ARG_SYSTEM: + arg_running_as = MANAGER_SYSTEM; + break; + + case ARG_USER: + arg_running_as = MANAGER_USER; + break; + + case ARG_TEST: + arg_action = ACTION_TEST; + if (arg_no_pager < 0) + arg_no_pager = true; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_VERSION: + arg_action = ACTION_VERSION; + break; + + case ARG_DUMP_CONFIGURATION_ITEMS: + arg_action = ACTION_DUMP_CONFIGURATION_ITEMS; + break; + + case ARG_DUMP_CORE: + if (!optarg) + arg_dump_core = true; + else { + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse dump core boolean: %s", optarg); + arg_dump_core = r; + } + break; + + case ARG_CRASH_CHVT: + r = parse_crash_chvt(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse crash virtual terminal index: %s", optarg); + break; + + case ARG_CRASH_SHELL: + if (!optarg) + arg_crash_shell = true; + else { + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse crash shell boolean: %s", optarg); + arg_crash_shell = r; + } + break; + + case ARG_CRASH_REBOOT: + if (!optarg) + arg_crash_reboot = true; + else { + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse crash shell boolean: %s", optarg); + arg_crash_reboot = r; + } + break; + + case ARG_CONFIRM_SPAWN: + r = optarg ? parse_boolean(optarg) : 1; + if (r < 0) { + log_error("Failed to parse confirm spawn boolean %s.", optarg); + return r; + } + arg_confirm_spawn = r; + break; + + case ARG_SHOW_STATUS: + if (optarg) { + r = parse_show_status(optarg, &arg_show_status); + if (r < 0) { + log_error("Failed to parse show status boolean %s.", optarg); + return r; + } + } else + arg_show_status = SHOW_STATUS_YES; + break; + + case ARG_DESERIALIZE: { + int fd; + FILE *f; + + r = safe_atoi(optarg, &fd); + if (r < 0 || fd < 0) { + log_error("Failed to parse deserialize option %s.", optarg); + return -EINVAL; + } + + (void) fd_cloexec(fd, true); + + f = fdopen(fd, "r"); + if (!f) + return log_error_errno(errno, "Failed to open serialization fd: %m"); + + safe_fclose(arg_serialization); + arg_serialization = f; + + break; + } + + case ARG_SWITCHED_ROOT: + 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) + arg_no_pager = true; + break; + + case 'D': + log_set_max_level(LOG_DEBUG); + break; + + case 'b': + case 's': + case 'z': + /* Just to eat away the sysvinit kernel + * cmdline args without getopt() error + * messages that we'll parse in + * parse_proc_cmdline_word() or ignore. */ + + case '?': + if (getpid() != 1) + return -EINVAL; + else + return 0; + + default: + assert_not_reached("Unhandled option code."); + } + + if (optind < argc && getpid() != 1) { + /* Hmm, when we aren't run as init system + * let's complain about excess arguments */ + + log_error("Excess arguments."); + return -EINVAL; + } + + return 0; +} + +static int help(void) { + + printf("%s [OPTIONS...]\n\n" + "Starts up and maintains the system or user services.\n\n" + " -h --help Show this help\n" + " --test Determine startup sequence, dump it and exit\n" + " --no-pager Do not pipe output into a pager\n" + " --dump-configuration-items Dump understood unit configuration items\n" + " --unit=UNIT Set default unit\n" + " --system Run a system instance, even if PID != 1\n" + " --user Run a user instance\n" + " --dump-core[=BOOL] Dump core on crash\n" + " --crash-vt=NR Change to specified VT on crash\n" + " --crash-reboot[=BOOL] Reboot on crash\n" + " --crash-shell[=BOOL] Run shell on crash\n" + " --confirm-spawn[=BOOL] Ask for confirmation when spawning processes\n" + " --show-status[=BOOL] Show status updates on the console during bootup\n" + " --log-target=TARGET Set log target (console, journal, kmsg, journal-or-kmsg, null)\n" + " --log-level=LEVEL Set log level (debug, info, notice, warning, err, crit, alert, emerg)\n" + " --log-color[=BOOL] Highlight important log messages\n" + " --log-location[=BOOL] Include code location in log messages\n" + " --default-standard-output= Set default standard output for services\n" + " --default-standard-error= Set default standard error output for services\n", + program_invocation_short_name); + + return 0; +} + +static int prepare_reexecute(Manager *m, FILE **_f, FDSet **_fds, bool switching_root) { + _cleanup_fdset_free_ FDSet *fds = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(m); + assert(_f); + assert(_fds); + + r = manager_open_serialization(m, &f); + if (r < 0) + return log_error_errno(r, "Failed to create serialization file: %m"); + + /* Make sure nothing is really destructed when we shut down */ + m->n_reloading ++; + bus_manager_send_reloading(m, true); + + fds = fdset_new(); + if (!fds) + return log_oom(); + + r = manager_serialize(m, f, fds, switching_root); + if (r < 0) + return log_error_errno(r, "Failed to serialize state: %m"); + + if (fseeko(f, 0, SEEK_SET) == (off_t) -1) + return log_error_errno(errno, "Failed to rewind serialization fd: %m"); + + r = fd_cloexec(fileno(f), false); + if (r < 0) + return log_error_errno(r, "Failed to disable O_CLOEXEC for serialization: %m"); + + r = fdset_cloexec(fds, false); + if (r < 0) + return log_error_errno(r, "Failed to disable O_CLOEXEC for serialization fds: %m"); + + *_f = f; + *_fds = fds; + + f = NULL; + fds = NULL; + + return 0; +} + +static int bump_rlimit_nofile(struct rlimit *saved_rlimit) { + struct rlimit nl; + int r; + + assert(saved_rlimit); + + /* Save the original RLIMIT_NOFILE so that we can reset it + * later when transitioning from the initrd to the main + * systemd or suchlike. */ + if (getrlimit(RLIMIT_NOFILE, saved_rlimit) < 0) + return log_error_errno(errno, "Reading RLIMIT_NOFILE failed: %m"); + + /* Make sure forked processes get the default kernel setting */ + if (!arg_default_rlimit[RLIMIT_NOFILE]) { + struct rlimit *rl; + + rl = newdup(struct rlimit, saved_rlimit, 1); + if (!rl) + return log_oom(); + + arg_default_rlimit[RLIMIT_NOFILE] = rl; + } + + /* Bump up the resource limit for ourselves substantially */ + nl.rlim_cur = nl.rlim_max = 64*1024; + r = setrlimit_closest(RLIMIT_NOFILE, &nl); + if (r < 0) + return log_error_errno(r, "Setting RLIMIT_NOFILE failed: %m"); + + return 0; +} + +static void test_usr(void) { + + /* Check that /usr is not a separate fs */ + + if (dir_is_empty("/usr") <= 0) + return; + + log_warning("/usr appears to be on its own filesystem and is not already mounted. This is not a supported setup. " + "Some things will probably break (sometimes even silently) in mysterious ways. " + "Consult http://freedesktop.org/wiki/Software/systemd/separate-usr-is-broken for more information."); +} + +static int initialize_join_controllers(void) { + /* By default, mount "cpu" + "cpuacct" together, and "net_cls" + * + "net_prio". We'd like to add "cpuset" to the mix, but + * "cpuset" doesn't really work for groups with no initialized + * attributes. */ + + arg_join_controllers = new(char**, 3); + if (!arg_join_controllers) + return -ENOMEM; + + arg_join_controllers[0] = strv_new("cpu", "cpuacct", NULL); + if (!arg_join_controllers[0]) + goto oom; + + arg_join_controllers[1] = strv_new("net_cls", "net_prio", NULL); + if (!arg_join_controllers[1]) + goto oom; + + arg_join_controllers[2] = NULL; + return 0; + +oom: + arg_join_controllers = strv_free_free(arg_join_controllers); + return -ENOMEM; +} + +static int enforce_syscall_archs(Set *archs) { +#ifdef HAVE_SECCOMP + scmp_filter_ctx *seccomp; + Iterator i; + void *id; + int r; + + seccomp = seccomp_init(SCMP_ACT_ALLOW); + if (!seccomp) + return log_oom(); + + SET_FOREACH(id, arg_syscall_archs, i) { + r = seccomp_arch_add(seccomp, PTR_TO_UINT32(id) - 1); + if (r == -EEXIST) + continue; + if (r < 0) { + log_error_errno(r, "Failed to add architecture to seccomp: %m"); + goto finish; + } + } + + r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); + if (r < 0) { + log_error_errno(r, "Failed to unset NO_NEW_PRIVS: %m"); + goto finish; + } + + r = seccomp_load(seccomp); + if (r < 0) + log_error_errno(r, "Failed to add install architecture seccomp: %m"); + +finish: + seccomp_release(seccomp); + return r; +#else + return 0; +#endif +} + +static int status_welcome(void) { + _cleanup_free_ char *pretty_name = NULL, *ansi_color = NULL; + int r; + + r = parse_env_file("/etc/os-release", NEWLINE, + "PRETTY_NAME", &pretty_name, + "ANSI_COLOR", &ansi_color, + NULL); + if (r == -ENOENT) + r = parse_env_file("/usr/lib/os-release", NEWLINE, + "PRETTY_NAME", &pretty_name, + "ANSI_COLOR", &ansi_color, + NULL); + + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read os-release file: %m"); + + return status_printf(NULL, false, false, + "\nWelcome to \x1B[%sm%s\x1B[0m!\n", + isempty(ansi_color) ? "1" : ansi_color, + isempty(pretty_name) ? "GNU/Linux" : pretty_name); +} + +static int write_container_id(void) { + const char *c; + int r; + + c = getenv("container"); + if (isempty(c)) + return 0; + + r = write_string_file("/run/systemd/container", c, WRITE_STRING_FILE_CREATE); + if (r < 0) + return log_warning_errno(r, "Failed to write /run/systemd/container, ignoring: %m"); + + return 1; +} + +static int bump_unix_max_dgram_qlen(void) { + _cleanup_free_ char *qlen = NULL; + unsigned long v; + int r; + + /* Let's bump the net.unix.max_dgram_qlen sysctl. The kernel + * default of 16 is simply too low. We set the value really + * really early during boot, so that it is actually applied to + * all our sockets, including the $NOTIFY_SOCKET one. */ + + r = read_one_line_file("/proc/sys/net/unix/max_dgram_qlen", &qlen); + if (r < 0) + return log_warning_errno(r, "Failed to read AF_UNIX datagram queue length, ignoring: %m"); + + r = safe_atolu(qlen, &v); + if (r < 0) + return log_warning_errno(r, "Failed to parse AF_UNIX datagram queue length, ignoring: %m"); + + if (v >= DEFAULT_UNIX_MAX_DGRAM_QLEN) + return 0; + + qlen = mfree(qlen); + if (asprintf(&qlen, "%lu\n", DEFAULT_UNIX_MAX_DGRAM_QLEN) < 0) + return log_oom(); + + r = write_string_file("/proc/sys/net/unix/max_dgram_qlen", qlen, 0); + if (r < 0) + return log_full_errno(IN_SET(r, -EROFS, -EPERM, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to bump AF_UNIX datagram queue length, ignoring: %m"); + + return 1; +} + +int main(int argc, char *argv[]) { + Manager *m = NULL; + int r, retval = EXIT_FAILURE; + usec_t before_startup, after_startup; + char timespan[FORMAT_TIMESPAN_MAX]; + FDSet *fds = NULL; + bool reexecute = false; + const char *shutdown_verb = NULL; + dual_timestamp initrd_timestamp = DUAL_TIMESTAMP_NULL; + dual_timestamp userspace_timestamp = DUAL_TIMESTAMP_NULL; + dual_timestamp kernel_timestamp = DUAL_TIMESTAMP_NULL; + dual_timestamp security_start_timestamp = DUAL_TIMESTAMP_NULL; + dual_timestamp security_finish_timestamp = DUAL_TIMESTAMP_NULL; + static char systemd[] = "systemd"; + bool skip_setup = false; + unsigned j; + bool loaded_policy = false; + bool arm_reboot_watchdog = false; + bool queue_default_job = false; + bool empty_etc = false; + char *switch_root_dir = NULL, *switch_root_init = NULL; + struct rlimit saved_rlimit_nofile = RLIMIT_MAKE_CONST(0); + const char *error_message = NULL; + +#ifdef HAVE_SYSV_COMPAT + if (getpid() != 1 && strstr(program_invocation_short_name, "init")) { + /* This is compatibility support for SysV, where + * calling init as a user is identical to telinit. */ + + errno = -ENOENT; + execv(SYSTEMCTL_BINARY_PATH, argv); + log_error_errno(errno, "Failed to exec " SYSTEMCTL_BINARY_PATH ": %m"); + return 1; + } +#endif + + dual_timestamp_from_monotonic(&kernel_timestamp, 0); + dual_timestamp_get(&userspace_timestamp); + + /* Determine if this is a reexecution or normal bootup. We do + * the full command line parsing much later, so let's just + * have a quick peek here. */ + if (strv_find(argv+1, "--deserialize")) + skip_setup = true; + + /* If we have switched root, do all the special setup + * things */ + if (strv_find(argv+1, "--switched-root")) + skip_setup = false; + + /* If we get started via the /sbin/init symlink then we are + called 'init'. After a subsequent reexecution we are then + called 'systemd'. That is confusing, hence let's call us + systemd right-away. */ + program_invocation_short_name = systemd; + prctl(PR_SET_NAME, systemd); + + saved_argv = argv; + saved_argc = argc; + + log_show_color(isatty(STDERR_FILENO) > 0); + log_set_upgrade_syslog_to_journal(true); + + /* Disable the umask logic */ + if (getpid() == 1) + umask(0); + + if (getpid() == 1 && detect_container() <= 0) { + + /* Running outside of a container as PID 1 */ + arg_running_as = MANAGER_SYSTEM; + make_null_stdio(); + log_set_target(LOG_TARGET_KMSG); + log_open(); + + if (in_initrd()) + initrd_timestamp = userspace_timestamp; + + if (!skip_setup) { + 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"; + goto finish; + } else if (ima_setup() < 0) { + error_message = "Failed to load IMA policy"; + goto finish; + } else if (mac_smack_setup(&loaded_policy) < 0) { + error_message = "Failed to load SMACK policy"; + goto finish; + } + dual_timestamp_get(&security_finish_timestamp); + } + + if (mac_selinux_init(NULL) < 0) { + error_message = "Failed to initialize SELinux policy"; + goto finish; + } + + if (!skip_setup) { + if (clock_is_localtime() > 0) { + int min; + + /* + * The very first call of settimeofday() also does a time warp in the kernel. + * + * In the rtc-in-local time mode, we set the kernel's timezone, and rely on + * external tools to take care of maintaining the RTC and do all adjustments. + * This matches the behavior of Windows, which leaves the RTC alone if the + * registry tells that the RTC runs in UTC. + */ + r = clock_set_timezone(&min); + if (r < 0) + log_error_errno(r, "Failed to apply local time delta, ignoring: %m"); + else + log_info("RTC configured in localtime, applying delta of %i minutes to system time.", min); + } else if (!in_initrd()) { + /* + * Do a dummy very first call to seal the kernel's time warp magic. + * + * Do not call this this from inside the initrd. The initrd might not + * carry /etc/adjtime with LOCAL, but the real system could be set up + * that way. In such case, we need to delay the time-warp or the sealing + * until we reach the real system. + * + * Do no set the kernel's timezone. The concept of local time cannot + * be supported reliably, the time will jump or be incorrect at every daylight + * saving time change. All kernel local time concepts will be treated + * as UTC that way. + */ + (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 + * open the logs like this for now. Note that if we + * are transitioning from the initrd there might still + * be journal fd open, and we shouldn't attempt + * opening that before we parsed /proc/cmdline which + * might redirect output elsewhere. */ + log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); + + } else if (getpid() == 1) { + /* Running inside a container, as PID 1 */ + arg_running_as = MANAGER_SYSTEM; + log_set_target(LOG_TARGET_CONSOLE); + log_close_console(); /* force reopen of /dev/console */ + log_open(); + + /* For the later on, see above... */ + log_set_target(LOG_TARGET_JOURNAL); + + /* clear the kernel timestamp, + * because we are in a container */ + kernel_timestamp.monotonic = 0ULL; + kernel_timestamp.realtime = 0ULL; + + } else { + /* Running as user instance */ + arg_running_as = MANAGER_USER; + log_set_target(LOG_TARGET_AUTO); + log_open(); + + /* clear the kernel timestamp, + * because we are not PID 1 */ + kernel_timestamp = DUAL_TIMESTAMP_NULL; + } + + if (getpid() == 1) { + /* Don't limit the core dump size, so that coredump handlers such as systemd-coredump (which honour the limit) + * will process core dumps for system services by default. */ + (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY)); + + /* But at the same time, turn off the core_pattern logic by default, so that no coredumps are stored + * until the systemd-coredump tool is enabled via sysctl. */ + if (!skip_setup) + (void) write_string_file("/proc/sys/kernel/core_pattern", "|/bin/false", 0); + } + + /* Initialize default unit */ + r = free_and_strdup(&arg_default_unit, SPECIAL_DEFAULT_TARGET); + if (r < 0) { + log_emergency_errno(r, "Failed to set default unit %s: %m", SPECIAL_DEFAULT_TARGET); + error_message = "Failed to set default unit"; + goto finish; + } + + r = initialize_join_controllers(); + if (r < 0) { + error_message = "Failed to initialize cgroup controllers"; + goto finish; + } + + /* Mount /proc, /sys and friends, so that /proc/cmdline and + * /proc/$PID/fd is available. */ + if (getpid() == 1) { + + /* Load the kernel modules early, so that we kdbus.ko is loaded before kdbusfs shall be mounted */ + if (!skip_setup) + kmod_setup(); + + r = mount_setup(loaded_policy); + if (r < 0) { + error_message = "Failed to mount API filesystems"; + goto finish; + } + } + + /* Reset all signal handlers. */ + (void) reset_all_signal_handlers(); + (void) ignore_signals(SIGNALS_IGNORE, -1); + + if (parse_config_file() < 0) { + error_message = "Failed to parse config file"; + goto finish; + } + + if (arg_running_as == MANAGER_SYSTEM) { + r = parse_proc_cmdline(parse_proc_cmdline_item); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + } + + /* Note that this also parses bits from the kernel command + * line, including "debug". */ + log_parse_environment(); + + if (parse_argv(argc, argv) < 0) { + error_message = "Failed to parse commandline arguments"; + goto finish; + } + + if (arg_action == ACTION_TEST && + geteuid() == 0) { + log_error("Don't run test mode as root."); + goto finish; + } + + if (arg_running_as == MANAGER_USER && + arg_action == ACTION_RUN && + sd_booted() <= 0) { + log_error("Trying to run as user instance, but the system has not been booted with systemd."); + goto finish; + } + + if (arg_running_as == MANAGER_SYSTEM && + arg_action == ACTION_RUN && + running_in_chroot() > 0) { + log_error("Cannot be run in a chroot() environment."); + goto finish; + } + + if (arg_action == ACTION_TEST) + skip_setup = true; + + pager_open_if_enabled(); + + if (arg_action == ACTION_HELP) { + retval = help(); + goto finish; + } else if (arg_action == ACTION_VERSION) { + retval = version(); + goto finish; + } else if (arg_action == ACTION_DUMP_CONFIGURATION_ITEMS) { + unit_dump_config_items(stdout); + retval = EXIT_SUCCESS; + goto finish; + } else if (arg_action == ACTION_DONE) { + retval = EXIT_SUCCESS; + goto finish; + } + + if (arg_running_as == MANAGER_USER && + !getenv("XDG_RUNTIME_DIR")) { + log_error("Trying to run as user instance, but $XDG_RUNTIME_DIR is not set."); + goto finish; + } + + assert_se(arg_action == ACTION_RUN || arg_action == ACTION_TEST); + + /* Close logging fds, in order not to confuse fdset below */ + log_close(); + + /* Remember open file descriptors for later deserialization */ + r = fdset_new_fill(&fds); + if (r < 0) { + log_emergency_errno(r, "Failed to allocate fd set: %m"); + error_message = "Failed to allocate fd set"; + goto finish; + } else + fdset_cloexec(fds, true); + + if (arg_serialization) + assert_se(fdset_remove(fds, fileno(arg_serialization)) >= 0); + + if (arg_running_as == MANAGER_SYSTEM) + /* Become a session leader if we aren't one yet. */ + setsid(); + + /* Move out of the way, so that we won't block unmounts */ + assert_se(chdir("/") == 0); + + /* Reset the console, but only if this is really init and we + * are freshly booted */ + if (arg_running_as == MANAGER_SYSTEM && arg_action == ACTION_RUN) { + + /* If we are init, we connect stdin/stdout/stderr to + * /dev/null and make sure we don't have a controlling + * tty. */ + release_terminal(); + + if (getpid() == 1 && !skip_setup) + console_setup(); + } + + /* Open the logging devices, if possible and necessary */ + log_open(); + + if (arg_show_status == _SHOW_STATUS_UNSET) + arg_show_status = SHOW_STATUS_YES; + + /* Make sure we leave a core dump without panicing the + * kernel. */ + if (getpid() == 1) { + install_crash_handler(); + + r = mount_cgroup_controllers(arg_join_controllers); + if (r < 0) + goto finish; + } + + if (arg_running_as == MANAGER_SYSTEM) { + int v; + + log_info(PACKAGE_STRING " running in %ssystem mode. (" SYSTEMD_FEATURES ")", + arg_action == ACTION_TEST ? "test " : "" ); + + v = detect_virtualization(); + if (v > 0) + log_info("Detected virtualization %s.", virtualization_to_string(v)); + + write_container_id(); + + log_info("Detected architecture %s.", architecture_to_string(uname_architecture())); + + if (in_initrd()) + log_info("Running in initial RAM disk."); + + /* Let's check whether /etc is already populated. We + * don't actually really check for that, but use + * /etc/machine-id as flag file. This allows container + * managers and installers to provision a couple of + * files already. If the container manager wants to + * provision the machine ID itself it should pass + * $container_uuid to PID 1. */ + + empty_etc = access("/etc/machine-id", F_OK) < 0; + if (empty_etc) + log_info("Running with unpopulated /etc."); + } else { + _cleanup_free_ char *t; + + t = uid_to_name(getuid()); + log_debug(PACKAGE_STRING " running in %suser mode for user "UID_FMT"/%s. (" SYSTEMD_FEATURES ")", + arg_action == ACTION_TEST ? " test" : "", getuid(), t); + } + + if (arg_running_as == MANAGER_SYSTEM && !skip_setup) { + if (arg_show_status > 0) + status_welcome(); + + hostname_setup(); + machine_id_setup(NULL, arg_machine_id); + loopback_setup(); + bump_unix_max_dgram_qlen(); + + test_usr(); + } + + if (arg_running_as == MANAGER_SYSTEM && arg_runtime_watchdog > 0) + watchdog_set_timeout(&arg_runtime_watchdog); + + if (arg_timer_slack_nsec != NSEC_INFINITY) + if (prctl(PR_SET_TIMERSLACK, arg_timer_slack_nsec) < 0) + log_error_errno(errno, "Failed to adjust timer slack: %m"); + + 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, true); + if (r < 0) { + log_emergency_errno(r, "Failed to drop capability bounding set: %m"); + error_message = "Failed to drop capability bounding set"; + goto finish; + } + } + + if (arg_syscall_archs) { + r = enforce_syscall_archs(arg_syscall_archs); + if (r < 0) { + error_message = "Failed to set syscall architectures"; + goto finish; + } + } + + if (arg_running_as == MANAGER_USER) + /* Become reaper of our children */ + if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) + log_warning_errno(errno, "Failed to make us a subreaper: %m"); + + if (arg_running_as == MANAGER_SYSTEM) { + bump_rlimit_nofile(&saved_rlimit_nofile); + + if (empty_etc) { + r = unit_file_preset_all(UNIT_FILE_SYSTEM, false, NULL, UNIT_FILE_PRESET_ENABLE_ONLY, false, NULL, 0); + if (r < 0) + log_full_errno(r == -EEXIST ? LOG_NOTICE : LOG_WARNING, r, "Failed to populate /etc with preset unit settings, ignoring: %m"); + else + log_info("Populated /etc with preset unit settings."); + } + } + + r = manager_new(arg_running_as, arg_action == ACTION_TEST, &m); + if (r < 0) { + log_emergency_errno(r, "Failed to allocate manager object: %m"); + error_message = "Failed to allocate manager object"; + goto finish; + } + + m->confirm_spawn = arg_confirm_spawn; + m->runtime_watchdog = arg_runtime_watchdog; + m->shutdown_watchdog = arg_shutdown_watchdog; + m->userspace_timestamp = userspace_timestamp; + m->kernel_timestamp = kernel_timestamp; + m->initrd_timestamp = initrd_timestamp; + m->security_start_timestamp = security_start_timestamp; + m->security_finish_timestamp = security_finish_timestamp; + + manager_set_defaults(m); + manager_set_show_status(m, arg_show_status); + manager_set_first_boot(m, empty_etc); + + /* Remember whether we should queue the default job */ + queue_default_job = !arg_serialization || arg_switched_root; + + before_startup = now(CLOCK_MONOTONIC); + + r = manager_startup(m, arg_serialization, fds); + if (r < 0) + log_error_errno(r, "Failed to fully start up daemon: %m"); + + /* This will close all file descriptors that were opened, but + * not claimed by any unit. */ + fds = fdset_free(fds); + + arg_serialization = safe_fclose(arg_serialization); + + if (queue_default_job) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + Unit *target = NULL; + Job *default_unit_job; + + log_debug("Activating default unit: %s", arg_default_unit); + + r = manager_load_unit(m, arg_default_unit, NULL, &error, &target); + if (r < 0) + log_error("Failed to load default target: %s", bus_error_message(&error, r)); + else if (target->load_state == UNIT_ERROR || target->load_state == UNIT_NOT_FOUND) + log_error_errno(target->load_error, "Failed to load default target: %m"); + else if (target->load_state == UNIT_MASKED) + log_error("Default target masked."); + + if (!target || target->load_state != UNIT_LOADED) { + log_info("Trying to load rescue target..."); + + r = manager_load_unit(m, SPECIAL_RESCUE_TARGET, NULL, &error, &target); + if (r < 0) { + log_emergency("Failed to load rescue target: %s", bus_error_message(&error, r)); + error_message = "Failed to load rescue target"; + goto finish; + } else if (target->load_state == UNIT_ERROR || target->load_state == UNIT_NOT_FOUND) { + log_emergency_errno(target->load_error, "Failed to load rescue target: %m"); + error_message = "Failed to load rescue target"; + goto finish; + } else if (target->load_state == UNIT_MASKED) { + log_emergency("Rescue target masked."); + error_message = "Rescue target masked"; + goto finish; + } + } + + assert(target->load_state == UNIT_LOADED); + + if (arg_action == ACTION_TEST) { + printf("-> By units:\n"); + manager_dump_units(m, stdout, "\t"); + } + + r = manager_add_job(m, JOB_START, target, JOB_ISOLATE, &error, &default_unit_job); + if (r == -EPERM) { + log_debug("Default target could not be isolated, starting instead: %s", bus_error_message(&error, r)); + + sd_bus_error_free(&error); + + r = manager_add_job(m, JOB_START, target, JOB_REPLACE, &error, &default_unit_job); + if (r < 0) { + log_emergency("Failed to start default target: %s", bus_error_message(&error, r)); + error_message = "Failed to start default target"; + goto finish; + } + } else if (r < 0) { + log_emergency("Failed to isolate default target: %s", bus_error_message(&error, r)); + error_message = "Failed to isolate default target"; + goto finish; + } + + m->default_unit_job_id = default_unit_job->id; + + after_startup = now(CLOCK_MONOTONIC); + log_full(arg_action == ACTION_TEST ? LOG_INFO : LOG_DEBUG, + "Loaded units and determined initial transaction in %s.", + format_timespan(timespan, sizeof(timespan), after_startup - before_startup, 100 * USEC_PER_MSEC)); + + if (arg_action == ACTION_TEST) { + printf("-> By jobs:\n"); + manager_dump_jobs(m, stdout, "\t"); + retval = EXIT_SUCCESS; + goto finish; + } + } + + for (;;) { + r = manager_loop(m); + if (r < 0) { + log_emergency_errno(r, "Failed to run main loop: %m"); + error_message = "Failed to run main loop"; + goto finish; + } + + switch (m->exit_code) { + + case MANAGER_RELOAD: + log_info("Reloading."); + + r = parse_config_file(); + if (r < 0) + log_error("Failed to parse config file."); + + manager_set_defaults(m); + + r = manager_reload(m); + if (r < 0) + log_error_errno(r, "Failed to reload: %m"); + break; + + case MANAGER_REEXECUTE: + + if (prepare_reexecute(m, &arg_serialization, &fds, false) < 0) { + error_message = "Failed to prepare for reexecution"; + goto finish; + } + + reexecute = true; + log_notice("Reexecuting."); + goto finish; + + case MANAGER_SWITCH_ROOT: + /* Steal the switch root parameters */ + switch_root_dir = m->switch_root; + switch_root_init = m->switch_root_init; + m->switch_root = m->switch_root_init = NULL; + + if (!switch_root_init) + if (prepare_reexecute(m, &arg_serialization, &fds, true) < 0) { + error_message = "Failed to prepare for reexecution"; + goto finish; + } + + reexecute = true; + log_notice("Switching root."); + goto finish; + + case MANAGER_EXIT: + retval = m->return_value; + + if (m->running_as == MANAGER_USER) { + log_debug("Exit."); + goto finish; + } + + /* fallthrough */ + case MANAGER_REBOOT: + case MANAGER_POWEROFF: + case MANAGER_HALT: + case MANAGER_KEXEC: { + static const char * const table[_MANAGER_EXIT_CODE_MAX] = { + [MANAGER_EXIT] = "exit", + [MANAGER_REBOOT] = "reboot", + [MANAGER_POWEROFF] = "poweroff", + [MANAGER_HALT] = "halt", + [MANAGER_KEXEC] = "kexec" + }; + + assert_se(shutdown_verb = table[m->exit_code]); + arm_reboot_watchdog = m->exit_code == MANAGER_REBOOT; + + log_notice("Shutting down."); + goto finish; + } + + default: + assert_not_reached("Unknown exit code."); + } + } + +finish: + pager_close(); + + if (m) + arg_shutdown_watchdog = m->shutdown_watchdog; + + m = manager_free(m); + + for (j = 0; j < ELEMENTSOF(arg_default_rlimit); j++) + arg_default_rlimit[j] = mfree(arg_default_rlimit[j]); + + arg_default_unit = mfree(arg_default_unit); + arg_join_controllers = strv_free_free(arg_join_controllers); + arg_default_environment = strv_free(arg_default_environment); + arg_syscall_archs = set_free(arg_syscall_archs); + + mac_selinux_finish(); + + if (reexecute) { + const char **args; + unsigned i, args_size; + + /* Close and disarm the watchdog, so that the new + * instance can reinitialize it, but doesn't get + * rebooted while we do that */ + watchdog_close(true); + + /* Reset the RLIMIT_NOFILE to the kernel default, so + * that the new systemd can pass the kernel default to + * its child processes */ + if (saved_rlimit_nofile.rlim_cur > 0) + (void) setrlimit(RLIMIT_NOFILE, &saved_rlimit_nofile); + + if (switch_root_dir) { + /* Kill all remaining processes from the + * initrd, but don't wait for them, so that we + * can handle the SIGCHLD for them after + * deserializing. */ + broadcast_signal(SIGTERM, false, true); + + /* And switch root with MS_MOVE, because we remove the old directory afterwards and detach it. */ + r = switch_root(switch_root_dir, "/mnt", true, MS_MOVE); + if (r < 0) + log_error_errno(r, "Failed to switch root, trying to continue: %m"); + } + + args_size = MAX(6, argc+1); + args = newa(const char*, args_size); + + if (!switch_root_init) { + char sfd[DECIMAL_STR_MAX(int) + 1]; + + /* First try to spawn ourselves with the right + * path, and with full serialization. We do + * this only if the user didn't specify an + * explicit init to spawn. */ + + assert(arg_serialization); + assert(fds); + + xsprintf(sfd, "%i", fileno(arg_serialization)); + + i = 0; + args[i++] = SYSTEMD_BINARY_PATH; + if (switch_root_dir) + args[i++] = "--switched-root"; + args[i++] = arg_running_as == MANAGER_SYSTEM ? "--system" : "--user"; + args[i++] = "--deserialize"; + args[i++] = sfd; + args[i++] = NULL; + + /* do not pass along the environment we inherit from the kernel or initrd */ + if (switch_root_dir) + (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); + } + + /* Try the fallback, if there is any, without any + * serialization. We pass the original argv[] and + * envp[]. (Well, modulo the ordering changes due to + * getopt() in argv[], and some cleanups in envp[], + * but let's hope that doesn't matter.) */ + + arg_serialization = safe_fclose(arg_serialization); + fds = fdset_free(fds); + + /* Reopen the console */ + (void) make_console_stdio(); + + for (j = 1, i = 1; j < (unsigned) argc; j++) + args[i++] = argv[j]; + args[i++] = NULL; + assert(i <= args_size); + + /* Reenable any blocked signals, especially important + * if we switch from initial ramdisk to init=... */ + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + if (switch_root_init) { + args[0] = switch_root_init; + (void) execv(args[0], (char* const*) args); + log_warning_errno(errno, "Failed to execute configured init, trying fallback: %m"); + } + + args[0] = "/sbin/init"; + (void) execv(args[0], (char* const*) args); + + if (errno == ENOENT) { + log_warning("No /sbin/init, trying fallback"); + + args[0] = "/bin/sh"; + args[1] = NULL; + (void) execv(args[0], (char* const*) args); + log_error_errno(errno, "Failed to execute /bin/sh, giving up: %m"); + } else + log_warning_errno(errno, "Failed to execute /sbin/init, giving up: %m"); + } + + arg_serialization = safe_fclose(arg_serialization); + fds = fdset_free(fds); + +#ifdef HAVE_VALGRIND_VALGRIND_H + /* If we are PID 1 and running under valgrind, then let's exit + * here explicitly. valgrind will only generate nice output on + * exit(), not on exec(), hence let's do the former not the + * latter here. */ + if (getpid() == 1 && RUNNING_ON_VALGRIND) + return 0; +#endif + + if (shutdown_verb) { + char log_level[DECIMAL_STR_MAX(int) + 1]; + char exit_code[DECIMAL_STR_MAX(uint8_t) + 1]; + const char* command_line[11] = { + SYSTEMD_SHUTDOWN_BINARY_PATH, + shutdown_verb, + "--log-level", log_level, + "--log-target", + }; + unsigned pos = 5; + _cleanup_strv_free_ char **env_block = NULL; + + assert(command_line[pos] == NULL); + env_block = strv_copy(environ); + + xsprintf(log_level, "%d", log_get_max_level()); + + switch (log_get_target()) { + + case LOG_TARGET_KMSG: + case LOG_TARGET_JOURNAL_OR_KMSG: + case LOG_TARGET_SYSLOG_OR_KMSG: + command_line[pos++] = "kmsg"; + break; + + case LOG_TARGET_NULL: + command_line[pos++] = "null"; + break; + + case LOG_TARGET_CONSOLE: + default: + command_line[pos++] = "console"; + break; + }; + + if (log_get_show_color()) + command_line[pos++] = "--log-color"; + + if (log_get_show_location()) + command_line[pos++] = "--log-location"; + + if (streq(shutdown_verb, "exit")) { + command_line[pos++] = "--exit-code"; + command_line[pos++] = exit_code; + xsprintf(exit_code, "%d", retval); + } + + assert(pos < ELEMENTSOF(command_line)); + + if (arm_reboot_watchdog && arg_shutdown_watchdog > 0) { + char *e; + + /* If we reboot let's set the shutdown + * watchdog and tell the shutdown binary to + * repeatedly ping it */ + r = watchdog_set_timeout(&arg_shutdown_watchdog); + watchdog_close(r < 0); + + /* Tell the binary how often to ping, ignore failure */ + if (asprintf(&e, "WATCHDOG_USEC="USEC_FMT, arg_shutdown_watchdog) > 0) + (void) strv_push(&env_block, e); + } else + watchdog_close(true); + + /* Avoid the creation of new processes forked by the + * kernel; at this point, we will not listen to the + * signals anyway */ + if (detect_container() <= 0) + (void) cg_uninstall_release_agent(SYSTEMD_CGROUP_CONTROLLER); + + execve(SYSTEMD_SHUTDOWN_BINARY_PATH, (char **) command_line, env_block); + log_error_errno(errno, "Failed to execute shutdown binary, %s: %m", + getpid() == 1 ? "freezing" : "quitting"); + } + + if (getpid() == 1) { + if (error_message) + manager_status_printf(NULL, STATUS_TYPE_EMERGENCY, + ANSI_HIGHLIGHT_RED "!!!!!!" ANSI_NORMAL, + "%s, freezing.", error_message); + freeze_or_reboot(); + } + + return retval; +} diff --git a/src/system/systemd/org.freedesktop.systemd1.conf b/src/system/systemd/org.freedesktop.systemd1.conf new file mode 100644 index 0000000000..6a7a37ee92 --- /dev/null +++ b/src/system/systemd/org.freedesktop.systemd1.conf @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/system/systemd/org.freedesktop.systemd1.policy.in.in b/src/system/systemd/org.freedesktop.systemd1.policy.in.in new file mode 100644 index 0000000000..cc39a9e1c3 --- /dev/null +++ b/src/system/systemd/org.freedesktop.systemd1.policy.in.in @@ -0,0 +1,70 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + <_description>Send passphrase back to system + <_message>Authentication is required to send the entered passphrase back to the system. + + no + no + auth_admin_keep + + @rootlibexecdir@/systemd-reply-password + + + + <_description>Manage system services or other units + <_message>Authentication is required to manage system services or other units. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Manage system service or unit files + <_message>Authentication is required to manage system service or unit files. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Set or unset system and service manager environment variables + <_message>Authentication is required to set or unset system and service manager environment variables. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Reload the systemd state + <_message>Authentication is required to reload the systemd state. + + auth_admin + auth_admin + auth_admin_keep + + + + diff --git a/src/system/systemd/org.freedesktop.systemd1.service b/src/system/systemd/org.freedesktop.systemd1.service new file mode 100644 index 0000000000..d4df3e93a2 --- /dev/null +++ b/src/system/systemd/org.freedesktop.systemd1.service @@ -0,0 +1,11 @@ +# This file is part of systemd. +# +# 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. + +[D-BUS Service] +Name=org.freedesktop.systemd1 +Exec=/bin/false +User=root diff --git a/src/system/systemd/system.conf b/src/system/systemd/system.conf new file mode 100644 index 0000000000..e2ded27333 --- /dev/null +++ b/src/system/systemd/system.conf @@ -0,0 +1,60 @@ +# This file is part of systemd. +# +# 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. +# +# Entries in this file show the compile time defaults. +# You can change settings by editing this file. +# Defaults can be restored by simply deleting this file. +# +# See systemd-system.conf(5) for details. + +[Manager] +#LogLevel=info +#LogTarget=journal-or-kmsg +#LogColor=yes +#LogLocation=no +#DumpCore=yes +#ShowStatus=yes +#CrashChangeVT=no +#CrashShell=no +#CrashReboot=no +#CPUAffinity=1 2 +#JoinControllers=cpu,cpuacct net_cls,net_prio +#RuntimeWatchdogSec=0 +#ShutdownWatchdogSec=10min +#CapabilityBoundingSet= +#SystemCallArchitectures= +#TimerSlackNSec= +#DefaultTimerAccuracySec=1min +#DefaultStandardOutput=journal +#DefaultStandardError=inherit +#DefaultTimeoutStartSec=90s +#DefaultTimeoutStopSec=90s +#DefaultRestartSec=100ms +#DefaultStartLimitInterval=10s +#DefaultStartLimitBurst=5 +#DefaultEnvironment= +#DefaultCPUAccounting=no +#DefaultBlockIOAccounting=no +#DefaultMemoryAccounting=no +#DefaultTasksAccounting=yes +#DefaultTasksMax=512 +#DefaultLimitCPU= +#DefaultLimitFSIZE= +#DefaultLimitDATA= +#DefaultLimitSTACK= +#DefaultLimitCORE= +#DefaultLimitRSS= +#DefaultLimitNOFILE= +#DefaultLimitAS= +#DefaultLimitNPROC= +#DefaultLimitMEMLOCK= +#DefaultLimitLOCKS= +#DefaultLimitSIGPENDING= +#DefaultLimitMSGQUEUE= +#DefaultLimitNICE= +#DefaultLimitRTPRIO= +#DefaultLimitRTTIME= diff --git a/src/system/systemd/systemd.pc.in b/src/system/systemd/systemd.pc.in new file mode 100644 index 0000000000..ac52b30dd3 --- /dev/null +++ b/src/system/systemd/systemd.pc.in @@ -0,0 +1,34 @@ +# This file is part of systemd. +# +# 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. + +prefix=@prefix@ +systemdutildir=@rootlibexecdir@ +systemdsystemunitdir=@systemunitdir@ +systemdsystempresetdir=@systempresetdir@ +systemduserunitdir=@userunitdir@ +systemduserpresetdir=@userpresetdir@ +systemdsystemconfdir=@pkgsysconfdir@/system +systemduserconfdir=@pkgsysconfdir@/user +systemdsystemunitpath=${systemdsystemconfdir}:/etc/systemd/system:/run/systemd/system:/usr/local/lib/systemd/system:${systemdsystemunitdir}:/usr/lib/systemd/system:/lib/systemd/system +systemduserunitpath=${systemduserconfdir}:/etc/systemd/user:/run/systemd/user:/usr/local/lib/systemd/user:/usr/local/share/systemd/user:${systemduserunitdir}:/usr/lib/systemd/user:/usr/share/systemd/user +systemdsystemgeneratordir=@systemgeneratordir@ +systemdusergeneratordir=@usergeneratordir@ +systemdsleepdir=@systemsleepdir@ +systemdshutdowndir=@systemshutdowndir@ +tmpfilesdir=@tmpfilesdir@ +sysusersdir=@sysusersdir@ +sysctldir=@sysctldir@ +binfmtdir=@binfmtdir@ +modulesloaddir=@modulesloaddir@ +catalogdir=@catalogdir@ +systemuidmax=@systemuidmax@ +systemgidmax=@systemgidmax@ + +Name: systemd +Description: systemd System and Service Manager +URL: @PACKAGE_URL@ +Version: @PACKAGE_VERSION@ diff --git a/src/system/systemd/triggers.systemd.in b/src/system/systemd/triggers.systemd.in new file mode 100644 index 0000000000..9e18a39a67 --- /dev/null +++ b/src/system/systemd/triggers.systemd.in @@ -0,0 +1,64 @@ +# -*- Mode: rpm-spec; indent-tabs-mode: nil -*- */ +# +# This file is part of systemd. +# +# Copyright 2015 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 . + +# The contents of this are an example to be copied into systemd.spec. + +%transfiletriggerin -P 900900 -p -- @systemunitdir@ /etc/systemd/system +-- This script will run after any package is initially installed or +-- upgraded. We care about the case where a package is initially +-- installed, because other cases are covered by the *un scriptlets, +-- so sometimes we will reload needlessly. + +pid = posix.fork() +if pid == 0 then + assert(posix.exec("%{_bindir}/systemctl", "daemon-reload")) +elseif pid > 0 then + posix.wait(pid) +end + +%transfiletriggerun -p -- @systemunitdir@ /etc/systemd/system +-- On removal, we need to run daemon-reload after any units have been +-- removed. %transfiletriggerpostun would be ideal, but it does not get +-- executed for some reason. +-- On upgrade, we need to run daemon-reload after any new unit files +-- have been installed, but before %postun scripts in packages get +-- executed. %transfiletriggerun gets the right list of files +-- but it is invoked too early (before changes happen). +-- %filetriggerpostun happens at the right time, but it fires for +-- every package. +-- To execute the reload at the right time, we create a state +-- file in %transfiletriggerun and execute the daemon-reload in +-- the first %filetriggerpostun. + +posix.mkdir("%{_localstatedir}/lib") +posix.mkdir("%{_localstatedir}/lib/rpm-state") +posix.mkdir("%{_localstatedir}/lib/rpm-state/systemd") +io.open("%{_localstatedir}/lib/rpm-state/systemd/needs-reload", "w") + +%filetriggerpostun -P 1000100 -p -- @systemunitdir@ /etc/systemd/system +if posix.access("%{_localstatedir}/lib/rpm-state/systemd/needs-reload") then + posix.unlink("%{_localstatedir}/lib/rpm-state/systemd/needs-reload") + posix.rmdir("%{_localstatedir}/lib/rpm-state/systemd") + pid = posix.fork() + if pid == 0 then + assert(posix.exec("%{_bindir}/systemctl", "daemon-reload")) + elseif pid > 0 then + posix.wait(pid) + end +end diff --git a/src/system/systemd/user.conf b/src/system/systemd/user.conf new file mode 100644 index 0000000000..87c8164378 --- /dev/null +++ b/src/system/systemd/user.conf @@ -0,0 +1,44 @@ +# This file is part of systemd. +# +# 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. +# +# You can override the directives in this file by creating files in +# /etc/systemd/user.conf.d/*.conf. +# +# See systemd-user.conf(5) for details + +[Manager] +#LogLevel=info +#LogTarget=console +#LogColor=yes +#LogLocation=no +#SystemCallArchitectures= +#TimerSlackNSec= +#DefaultTimerAccuracySec=1min +#DefaultStandardOutput=inherit +#DefaultStandardError=inherit +#DefaultTimeoutStartSec=90s +#DefaultTimeoutStopSec=90s +#DefaultRestartSec=100ms +#DefaultStartLimitInterval=10s +#DefaultStartLimitBurst=5 +#DefaultEnvironment= +#DefaultLimitCPU= +#DefaultLimitFSIZE= +#DefaultLimitDATA= +#DefaultLimitSTACK= +#DefaultLimitCORE= +#DefaultLimitRSS= +#DefaultLimitNOFILE= +#DefaultLimitAS= +#DefaultLimitNPROC= +#DefaultLimitMEMLOCK= +#DefaultLimitLOCKS= +#DefaultLimitSIGPENDING= +#DefaultLimitMSGQUEUE= +#DefaultLimitNICE= +#DefaultLimitRTPRIO= +#DefaultLimitRTTIME= diff --git a/src/systemd-analyze/.gitignore b/src/systemd-analyze/.gitignore new file mode 100644 index 0000000000..752ea236c8 --- /dev/null +++ b/src/systemd-analyze/.gitignore @@ -0,0 +1 @@ +/systemd-analyze diff --git a/src/systemd-analyze/Makefile b/src/systemd-analyze/Makefile new file mode 100644 index 0000000000..5b2f58d7e4 --- /dev/null +++ b/src/systemd-analyze/Makefile @@ -0,0 +1,35 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +systemd_analyze_SOURCES = \ + src/analyze/analyze.c \ + src/analyze/analyze-verify.c \ + src/analyze/analyze-verify.h + +systemd_analyze_CFLAGS = \ + $(AM_CFLAGS) \ + $(SECCOMP_CFLAGS) \ + $(MOUNT_CFLAGS) + +systemd_analyze_LDADD = \ + libcore.la + diff --git a/src/systemd-analyze/analyze-verify.c b/src/systemd-analyze/analyze-verify.c new file mode 100644 index 0000000000..d36c8db3d4 --- /dev/null +++ b/src/systemd-analyze/analyze-verify.c @@ -0,0 +1,306 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include "alloc-util.h" +#include "analyze-verify.h" +#include "bus-error.h" +#include "bus-util.h" +#include "log.h" +#include "manager.h" +#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; + + _cleanup_strv_free_ char **ans = NULL; + int r; + + STRV_FOREACH(filename, filenames) { + char *t; + + t = dirname_malloc(*filename); + if (!t) + return -ENOMEM; + + r = strv_consume(&ans, t); + if (r < 0) + return r; + } + + assert_se(strv_uniq(ans)); + + r = strv_extend(&ans, ""); + if (r < 0) + return r; + + *var = strv_join(ans, ":"); + if (!*var) + return -ENOMEM; + + return 0; +} + +static int verify_socket(Unit *u) { + int r; + + assert(u); + + if (u->type != UNIT_SOCKET) + return 0; + + /* Cannot run this without the service being around */ + + /* This makes sure instance is created if necessary. */ + r = socket_instantiate_service(SOCKET(u)); + if (r < 0) { + log_unit_error_errno(u, r, "Socket cannot be started, failed to create instance: %m"); + return r; + } + + /* This checks both type of sockets */ + if (UNIT_ISSET(SOCKET(u)->service)) { + Service *service; + + service = SERVICE(UNIT_DEREF(SOCKET(u)->service)); + log_unit_debug(u, "Using %s", UNIT(service)->id); + + if (UNIT(service)->load_state != UNIT_LOADED) { + log_unit_error(u, "Service %s not loaded, %s cannot be started.", UNIT(service)->id, u->id); + return -ENOENT; + } + } + + return 0; +} + +static int verify_executable(Unit *u, ExecCommand *exec) { + if (exec == NULL) + return 0; + + if (access(exec->path, X_OK) < 0) + return log_unit_error_errno(u, errno, "Command %s is not executable: %m", exec->path); + + return 0; +} + +static int verify_executables(Unit *u) { + ExecCommand *exec; + int r = 0, k; + unsigned i; + + assert(u); + + exec = u->type == UNIT_SOCKET ? SOCKET(u)->control_command : + u->type == UNIT_MOUNT ? MOUNT(u)->control_command : + u->type == UNIT_SWAP ? SWAP(u)->control_command : NULL; + k = verify_executable(u, exec); + if (k < 0 && r == 0) + r = k; + + if (u->type == UNIT_SERVICE) + for (i = 0; i < ELEMENTSOF(SERVICE(u)->exec_command); i++) { + k = verify_executable(u, SERVICE(u)->exec_command[i]); + if (k < 0 && r == 0) + r = k; + } + + if (u->type == UNIT_SOCKET) + for (i = 0; i < ELEMENTSOF(SOCKET(u)->exec_command); i++) { + k = verify_executable(u, SOCKET(u)->exec_command[i]); + if (k < 0 && r == 0) + r = k; + } + + return r; +} + +static int verify_documentation(Unit *u, bool check_man) { + char **p; + int r = 0, k; + + STRV_FOREACH(p, u->documentation) { + log_unit_debug(u, "Found documentation item: %s", *p); + + if (check_man && startswith(*p, "man:")) { + k = show_man_page(*p + 4, true); + if (k != 0) { + if (k < 0) + log_unit_error_errno(u, r, "Can't show %s: %m", *p); + else { + log_unit_error_errno(u, r, "man %s command failed with code %d", *p + 4, k); + k = -ENOEXEC; + } + if (r == 0) + r = k; + } + } + } + + /* Check remote URLs? */ + + return r; +} + +static int verify_unit(Unit *u, bool check_man) { + _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; + int r, k; + + assert(u); + + if (log_get_max_level() >= LOG_DEBUG) + unit_dump(u, stdout, "\t"); + + log_unit_debug(u, "Creating %s/start job", u->id); + r = manager_add_job(u->manager, JOB_START, u, JOB_REPLACE, &err, NULL); + if (r < 0) + log_unit_error_errno(u, r, "Failed to create %s/start: %s", u->id, bus_error_message(&err, r)); + + k = verify_socket(u); + if (k < 0 && r == 0) + r = k; + + k = verify_executables(u); + if (k < 0 && r == 0) + r = k; + + k = verify_documentation(u, check_man); + if (k < 0 && r == 0) + r = k; + + return r; +} + +int verify_units(char **filenames, ManagerRunningAs running_as, bool check_man) { + _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; + Manager *m = NULL; + FILE *serial = NULL; + FDSet *fdset = NULL; + + _cleanup_free_ char *var = NULL; + + char **filename; + int r = 0, k; + + Unit *units[strv_length(filenames)]; + int i, count = 0; + + if (strv_isempty(filenames)) + return 0; + + /* set the path */ + r = generate_path(&var, filenames); + if (r < 0) + return log_error_errno(r, "Failed to generate unit load path: %m"); + + assert_se(set_unit_path(var) >= 0); + + r = manager_new(running_as, true, &m); + if (r < 0) + return log_error_errno(r, "Failed to initialize manager: %m"); + + log_debug("Starting manager..."); + + r = manager_startup(m, serial, fdset); + if (r < 0) { + log_error_errno(r, "Failed to start manager: %m"); + goto finish; + } + + manager_clear_jobs(m); + + log_debug("Loading remaining units from the command line..."); + + STRV_FOREACH(filename, filenames) { + _cleanup_free_ char *prepared = NULL; + + log_debug("Handling %s...", *filename); + + 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) + r = k; + } else + count ++; + } + + for (i = 0; i < count; i++) { + k = verify_unit(units[i], check_man); + if (k < 0 && r == 0) + r = k; + } + +finish: + manager_free(m); + + return r; +} diff --git a/src/systemd-analyze/analyze-verify.h b/src/systemd-analyze/analyze-verify.h new file mode 100644 index 0000000000..54adad93e1 --- /dev/null +++ b/src/systemd-analyze/analyze-verify.h @@ -0,0 +1,26 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#pragma once + +#include + +#include "path-lookup.h" + +int verify_units(char **filenames, ManagerRunningAs running_as, bool check_man); diff --git a/src/systemd-analyze/analyze.c b/src/systemd-analyze/analyze.c new file mode 100644 index 0000000000..1a28093b07 --- /dev/null +++ b/src/systemd-analyze/analyze.c @@ -0,0 +1,1492 @@ +/*** + This file is part of systemd. + + Copyright 2010-2013 Lennart Poettering + Copyright 2013 Simon Peeters + + 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 . +***/ + +#include +#include +#include +#include + +#include "sd-bus.h" + +#include "alloc-util.h" +#include "analyze-verify.h" +#include "bus-error.h" +#include "bus-util.h" +#include "glob-util.h" +#include "hashmap.h" +#include "locale-util.h" +#include "log.h" +#include "pager.h" +#include "parse-util.h" +#include "special.h" +#include "strv.h" +#include "strxcpyx.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "util.h" + +#define SCALE_X (0.1 / 1000.0) /* pixels per us */ +#define SCALE_Y (20.0) + +#define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0)) + +#define svg(...) printf(__VA_ARGS__) + +#define svg_bar(class, x1, x2, y) \ + svg(" \n", \ + (class), \ + SCALE_X * (x1), SCALE_Y * (y), \ + SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0) + +#define svg_text(b, x, y, format, ...) \ + do { \ + svg(" ", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \ + svg(format, ## __VA_ARGS__); \ + svg("\n"); \ + } while(false) + +static enum dot { + DEP_ALL, + DEP_ORDER, + DEP_REQUIRE +} arg_dot = DEP_ALL; +static char** arg_dot_from_patterns = NULL; +static char** arg_dot_to_patterns = NULL; +static usec_t arg_fuzz = 0; +static bool arg_no_pager = false; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static char *arg_host = NULL; +static bool arg_user = false; +static bool arg_man = true; + +struct boot_times { + usec_t firmware_time; + usec_t loader_time; + usec_t kernel_time; + usec_t kernel_done_time; + usec_t initrd_time; + usec_t userspace_time; + usec_t finish_time; + usec_t security_start_time; + usec_t security_finish_time; + usec_t generators_start_time; + usec_t generators_finish_time; + usec_t unitsload_start_time; + usec_t unitsload_finish_time; + + /* + * If we're analyzing the user instance, all timestamps will be offset + * by its own start-up timestamp, which may be arbitrarily big. + * With "plot", this causes arbitrarily wide output SVG files which almost + * completely consist of empty space. Thus we cancel out this offset. + * + * This offset is subtracted from times above by acquire_boot_times(), + * but it still needs to be subtracted from unit-specific timestamps + * (so it is stored here for reference). + */ + usec_t reverse_offset; +}; + +struct unit_times { + char *name; + usec_t activating; + usec_t activated; + usec_t deactivated; + usec_t deactivating; + usec_t time; +}; + +struct host_info { + char *hostname; + char *kernel_name; + char *kernel_release; + char *kernel_version; + char *os_pretty_name; + char *virtualization; + char *architecture; +}; + +static void pager_open_if_enabled(void) { + + if (arg_no_pager) + return; + + pager_open(false); +} + +static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *interface, const char *property, uint64_t *val) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(property); + assert(val); + + r = sd_bus_get_property_trivial( + bus, + "org.freedesktop.systemd1", + path, + interface, + property, + &error, + 't', val); + + if (r < 0) { + log_error("Failed to parse reply: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char *property, char ***strv) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(path); + assert(property); + assert(strv); + + r = sd_bus_get_property_strv( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + property, + &error, + strv); + if (r < 0) { + log_error("Failed to get unit property %s: %s", property, bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int compare_unit_time(const void *a, const void *b) { + return compare(((struct unit_times *)b)->time, + ((struct unit_times *)a)->time); +} + +static int compare_unit_start(const void *a, const void *b) { + return compare(((struct unit_times *)a)->activating, + ((struct unit_times *)b)->activating); +} + +static void free_unit_times(struct unit_times *t, unsigned n) { + struct unit_times *p; + + for (p = t; p < t + n; p++) + free(p->name); + + free(t); +} + +static void subtract_timestamp(usec_t *a, usec_t b) { + assert(a); + + if (*a > 0) { + assert(*a >= b); + *a -= b; + } +} + +static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) { + static struct boot_times times; + static bool cached = false; + + if (cached) + goto finish; + + assert_cc(sizeof(usec_t) == sizeof(uint64_t)); + + if (bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "FirmwareTimestampMonotonic", + ×.firmware_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LoaderTimestampMonotonic", + ×.loader_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "KernelTimestamp", + ×.kernel_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "InitRDTimestampMonotonic", + ×.initrd_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UserspaceTimestampMonotonic", + ×.userspace_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "FinishTimestampMonotonic", + ×.finish_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SecurityStartTimestampMonotonic", + ×.security_start_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SecurityFinishTimestampMonotonic", + ×.security_finish_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GeneratorsStartTimestampMonotonic", + ×.generators_start_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GeneratorsFinishTimestampMonotonic", + ×.generators_finish_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UnitsLoadStartTimestampMonotonic", + ×.unitsload_start_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UnitsLoadFinishTimestampMonotonic", + ×.unitsload_finish_time) < 0) + return -EIO; + + if (times.finish_time <= 0) { + log_error("Bootup is not yet finished. Please try again later."); + return -EINPROGRESS; + } + + if (arg_user) { + /* + * User-instance-specific timestamps processing + * (see comment to reverse_offset in struct boot_times). + */ + times.reverse_offset = times.userspace_time; + + times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time = times.userspace_time = 0; + subtract_timestamp(×.finish_time, times.reverse_offset); + + subtract_timestamp(×.security_start_time, times.reverse_offset); + subtract_timestamp(×.security_finish_time, times.reverse_offset); + + subtract_timestamp(×.generators_start_time, times.reverse_offset); + subtract_timestamp(×.generators_finish_time, times.reverse_offset); + + subtract_timestamp(×.unitsload_start_time, times.reverse_offset); + subtract_timestamp(×.unitsload_finish_time, times.reverse_offset); + } else { + if (times.initrd_time) + times.kernel_done_time = times.initrd_time; + else + times.kernel_done_time = times.userspace_time; + } + + cached = true; + +finish: + *bt = × + return 0; +} + +static void free_host_info(struct host_info *hi) { + + if (!hi) + return; + + free(hi->hostname); + free(hi->kernel_name); + free(hi->kernel_release); + free(hi->kernel_version); + free(hi->os_pretty_name); + free(hi->virtualization); + free(hi->architecture); + free(hi); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info*, free_host_info); + +static int acquire_time_data(sd_bus *bus, struct unit_times **out) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r, c = 0; + struct boot_times *boot_times = NULL; + struct unit_times *unit_times = NULL; + size_t size = 0; + UnitInfo u; + + r = acquire_boot_times(bus, &boot_times); + if (r < 0) + goto fail; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnits", + &error, &reply, + NULL); + if (r < 0) { + log_error("Failed to list units: %s", bus_error_message(&error, -r)); + goto fail; + } + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); + if (r < 0) { + bus_log_parse_error(r); + goto fail; + } + + while ((r = bus_parse_unit_info(reply, &u)) > 0) { + struct unit_times *t; + + if (!GREEDY_REALLOC(unit_times, size, c+1)) { + r = log_oom(); + goto fail; + } + + t = unit_times+c; + t->name = NULL; + + assert_cc(sizeof(usec_t) == sizeof(uint64_t)); + + if (bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "InactiveExitTimestampMonotonic", + &t->activating) < 0 || + bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "ActiveEnterTimestampMonotonic", + &t->activated) < 0 || + bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "ActiveExitTimestampMonotonic", + &t->deactivating) < 0 || + bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "InactiveEnterTimestampMonotonic", + &t->deactivated) < 0) { + r = -EIO; + goto fail; + } + + subtract_timestamp(&t->activating, boot_times->reverse_offset); + subtract_timestamp(&t->activated, boot_times->reverse_offset); + subtract_timestamp(&t->deactivating, boot_times->reverse_offset); + subtract_timestamp(&t->deactivated, boot_times->reverse_offset); + + if (t->activated >= t->activating) + t->time = t->activated - t->activating; + else if (t->deactivated >= t->activating) + t->time = t->deactivated - t->activating; + else + t->time = 0; + + if (t->activating == 0) + continue; + + t->name = strdup(u.id); + if (t->name == NULL) { + r = log_oom(); + goto fail; + } + c++; + } + if (r < 0) { + bus_log_parse_error(r); + goto fail; + } + + *out = unit_times; + return c; + +fail: + if (unit_times) + free_unit_times(unit_times, (unsigned) c); + return r; +} + +static int acquire_host_info(sd_bus *bus, struct host_info **hi) { + static const struct bus_properties_map hostname_map[] = { + { "Hostname", "s", NULL, offsetof(struct host_info, hostname) }, + { "KernelName", "s", NULL, offsetof(struct host_info, kernel_name) }, + { "KernelRelease", "s", NULL, offsetof(struct host_info, kernel_release) }, + { "KernelVersion", "s", NULL, offsetof(struct host_info, kernel_version) }, + { "OperatingSystemPrettyName", "s", NULL, offsetof(struct host_info, os_pretty_name) }, + {} + }; + + static const struct bus_properties_map manager_map[] = { + { "Virtualization", "s", NULL, offsetof(struct host_info, virtualization) }, + { "Architecture", "s", NULL, offsetof(struct host_info, architecture) }, + {} + }; + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(free_host_infop) struct host_info *host; + int r; + + host = new0(struct host_info, 1); + if (!host) + return log_oom(); + + r = bus_map_all_properties(bus, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + hostname_map, + host); + if (r < 0) + log_debug_errno(r, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error, r)); + + r = bus_map_all_properties(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + manager_map, + host); + if (r < 0) + return log_error_errno(r, "Failed to get host information from systemd: %s", bus_error_message(&error, r)); + + *hi = host; + host = NULL; + + return 0; +} + +static int pretty_boot_time(sd_bus *bus, char **_buf) { + char ts[FORMAT_TIMESPAN_MAX]; + struct boot_times *t; + static char buf[4096]; + size_t size; + char *ptr; + int r; + + r = acquire_boot_times(bus, &t); + if (r < 0) + return r; + + ptr = buf; + size = sizeof(buf); + + size = strpcpyf(&ptr, size, "Startup finished in "); + if (t->firmware_time) + size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC)); + if (t->loader_time) + size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC)); + if (t->kernel_time) + size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC)); + if (t->initrd_time > 0) + size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC)); + + size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC)); + strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC)); + + ptr = strdup(buf); + if (!ptr) + return log_oom(); + + *_buf = ptr; + return 0; +} + +static void svg_graph_box(double height, double begin, double end) { + long long i; + + /* outside box, fill */ + svg("\n", + SCALE_X * (end - begin), SCALE_Y * height); + + for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) { + /* lines for each second */ + if (i % 5000000 == 0) + svg(" \n" + " %.01fs\n", + SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i); + else if (i % 1000000 == 0) + svg(" \n" + " %.01fs\n", + SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i); + else + svg(" \n", + SCALE_X * i, SCALE_X * i, SCALE_Y * height); + } +} + +static int analyze_plot(sd_bus *bus) { + _cleanup_(free_host_infop) struct host_info *host = NULL; + struct unit_times *times; + struct boot_times *boot; + int n, m = 1, y=0; + double width; + _cleanup_free_ char *pretty_times = NULL; + struct unit_times *u; + + n = acquire_boot_times(bus, &boot); + if (n < 0) + return n; + + n = pretty_boot_time(bus, &pretty_times); + if (n < 0) + return n; + + n = acquire_host_info(bus, &host); + if (n < 0) + return n; + + n = acquire_time_data(bus, ×); + if (n <= 0) + return n; + + qsort(times, n, sizeof(struct unit_times), compare_unit_start); + + width = SCALE_X * (boot->firmware_time + boot->finish_time); + if (width < 800.0) + width = 800.0; + + if (boot->firmware_time > boot->loader_time) + m++; + if (boot->loader_time) { + m++; + if (width < 1000.0) + width = 1000.0; + } + if (boot->initrd_time) + m++; + if (boot->kernel_time) + m++; + + for (u = times; u < times + n; u++) { + double text_start, text_width; + + if (u->activating < boot->userspace_time || + u->activating > boot->finish_time) { + u->name = mfree(u->name); + continue; + } + + /* If the text cannot fit on the left side then + * increase the svg width so it fits on the right. + * TODO: calculate the text width more accurately */ + text_width = 8.0 * strlen(u->name); + text_start = (boot->firmware_time + u->activating) * SCALE_X; + if (text_width > text_start && text_width + text_start > width) + width = text_width + text_start; + + if (u->deactivated > u->activating && u->deactivated <= boot->finish_time + && u->activated == 0 && u->deactivating == 0) + u->activated = u->deactivating = u->deactivated; + if (u->activated < u->activating || u->activated > boot->finish_time) + u->activated = boot->finish_time; + if (u->deactivating < u->activated || u->activated > boot->finish_time) + u->deactivating = boot->finish_time; + if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time) + u->deactivated = boot->finish_time; + m++; + } + + svg("\n" + "\n"); + + svg("\n\n", + 80.0 + width, 150.0 + (m * SCALE_Y) + + 5 * SCALE_Y /* legend */); + + /* write some basic info as a comment, including some help */ + svg("\n" + "\n" + "\n" + "\n" + "\n\n" + "\n\n", VERSION); + + /* style sheet */ + svg("\n \n\n\n"); + + svg("\n"); + svg("%s", pretty_times); + svg("%s %s (%s %s %s) %s %s", + isempty(host->os_pretty_name) ? "GNU/Linux" : host->os_pretty_name, + strempty(host->hostname), + strempty(host->kernel_name), + strempty(host->kernel_release), + strempty(host->kernel_version), + strempty(host->architecture), + strempty(host->virtualization)); + + svg("\n", 20.0 + (SCALE_X * boot->firmware_time)); + svg_graph_box(m, -(double) boot->firmware_time, boot->finish_time); + + if (boot->firmware_time) { + svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y); + svg_text(true, -(double) boot->firmware_time, y, "firmware"); + y++; + } + if (boot->loader_time) { + svg_bar("loader", -(double) boot->loader_time, 0, y); + svg_text(true, -(double) boot->loader_time, y, "loader"); + y++; + } + if (boot->kernel_time) { + svg_bar("kernel", 0, boot->kernel_done_time, y); + svg_text(true, 0, y, "kernel"); + y++; + } + if (boot->initrd_time) { + svg_bar("initrd", boot->initrd_time, boot->userspace_time, y); + svg_text(true, boot->initrd_time, y, "initrd"); + y++; + } + svg_bar("active", boot->userspace_time, boot->finish_time, y); + svg_bar("security", boot->security_start_time, boot->security_finish_time, y); + svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y); + svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y); + svg_text(true, boot->userspace_time, y, "systemd"); + y++; + + for (u = times; u < times + n; u++) { + char ts[FORMAT_TIMESPAN_MAX]; + bool b; + + if (!u->name) + continue; + + svg_bar("activating", u->activating, u->activated, y); + svg_bar("active", u->activated, u->deactivating, y); + svg_bar("deactivating", u->deactivating, u->deactivated, y); + + /* place the text on the left if we have passed the half of the svg width */ + b = u->activating * SCALE_X < width / 2; + if (u->time) + svg_text(b, u->activating, y, "%s (%s)", + u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC)); + else + svg_text(b, u->activating, y, "%s", u->name); + y++; + } + + svg("\n"); + + /* Legend */ + svg("\n"); + y++; + svg_bar("activating", 0, 300000, y); + svg_text(true, 400000, y, "Activating"); + y++; + svg_bar("active", 0, 300000, y); + svg_text(true, 400000, y, "Active"); + y++; + svg_bar("deactivating", 0, 300000, y); + svg_text(true, 400000, y, "Deactivating"); + y++; + svg_bar("security", 0, 300000, y); + svg_text(true, 400000, y, "Setting up security module"); + y++; + svg_bar("generators", 0, 300000, y); + svg_text(true, 400000, y, "Generators"); + y++; + svg_bar("unitsload", 0, 300000, y); + svg_text(true, 400000, y, "Loading unit files"); + y++; + + svg("\n\n"); + + svg("\n"); + + free_unit_times(times, (unsigned) n); + + n = 0; + return n; +} + +static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches, + bool last, struct unit_times *times, struct boot_times *boot) { + unsigned int i; + char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX]; + + for (i = level; i != 0; i--) + printf("%s", draw_special_char(branches & (1 << (i-1)) ? DRAW_TREE_VERTICAL : DRAW_TREE_SPACE)); + + printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH)); + + if (times) { + if (times->time) + printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED, name, + format_timespan(ts, sizeof(ts), times->activating - boot->userspace_time, USEC_PER_MSEC), + format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ANSI_NORMAL); + else if (times->activated > boot->userspace_time) + printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC)); + else + printf("%s", name); + } else + printf("%s", name); + printf("\n"); + + return 0; +} + +static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) { + _cleanup_free_ char *path = NULL; + + assert(bus); + assert(name); + assert(deps); + + path = unit_dbus_path_from_name(name); + if (path == NULL) + return -ENOMEM; + + return bus_get_unit_property_strv(bus, path, "After", deps); +} + +static Hashmap *unit_times_hashmap; + +static int list_dependencies_compare(const void *_a, const void *_b) { + const char **a = (const char**) _a, **b = (const char**) _b; + usec_t usa = 0, usb = 0; + struct unit_times *times; + + times = hashmap_get(unit_times_hashmap, *a); + if (times) + usa = times->activated; + times = hashmap_get(unit_times_hashmap, *b); + if (times) + usb = times->activated; + + return usb - usa; +} + +static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int level, char ***units, + unsigned int branches) { + _cleanup_strv_free_ char **deps = NULL; + char **c; + int r = 0; + usec_t service_longest = 0; + int to_print = 0; + struct unit_times *times; + struct boot_times *boot; + + if (strv_extend(units, name)) + return log_oom(); + + r = list_dependencies_get_dependencies(bus, name, &deps); + if (r < 0) + return r; + + qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare); + + r = acquire_boot_times(bus, &boot); + if (r < 0) + return r; + + STRV_FOREACH(c, deps) { + times = hashmap_get(unit_times_hashmap, *c); + if (times + && times->activated + && times->activated <= boot->finish_time + && (times->activated >= service_longest + || service_longest == 0)) { + service_longest = times->activated; + break; + } + } + + if (service_longest == 0 ) + return r; + + STRV_FOREACH(c, deps) { + times = hashmap_get(unit_times_hashmap, *c); + if (times && times->activated && times->activated <= boot->finish_time && (service_longest - times->activated) <= arg_fuzz) + to_print++; + } + + if (!to_print) + return r; + + STRV_FOREACH(c, deps) { + times = hashmap_get(unit_times_hashmap, *c); + if (!times + || !times->activated + || times->activated > boot->finish_time + || service_longest - times->activated > arg_fuzz) + continue; + + to_print--; + + r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot); + if (r < 0) + return r; + + if (strv_contains(*units, *c)) { + r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0), + true, NULL, boot); + if (r < 0) + return r; + continue; + } + + r = list_dependencies_one(bus, *c, level + 1, units, + (branches << 1) | (to_print ? 1 : 0)); + if (r < 0) + return r; + + if (!to_print) + break; + } + return 0; +} + +static int list_dependencies(sd_bus *bus, const char *name) { + _cleanup_strv_free_ char **units = NULL; + char ts[FORMAT_TIMESPAN_MAX]; + struct unit_times *times; + int r; + const char *id; + _cleanup_free_ char *path = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + struct boot_times *boot; + + assert(bus); + + path = unit_dbus_path_from_name(name); + if (path == NULL) + return -ENOMEM; + + r = sd_bus_get_property( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "Id", + &error, + &reply, + "s"); + if (r < 0) { + log_error("Failed to get ID: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "s", &id); + if (r < 0) + return bus_log_parse_error(r); + + times = hashmap_get(unit_times_hashmap, id); + + r = acquire_boot_times(bus, &boot); + if (r < 0) + return r; + + if (times) { + if (times->time) + printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED, id, + format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ANSI_NORMAL); + else if (times->activated > boot->userspace_time) + printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC)); + else + printf("%s\n", id); + } + + return list_dependencies_one(bus, name, 0, &units, 0); +} + +static int analyze_critical_chain(sd_bus *bus, char *names[]) { + struct unit_times *times; + unsigned int i; + Hashmap *h; + int n, r; + + n = acquire_time_data(bus, ×); + if (n <= 0) + return n; + + h = hashmap_new(&string_hash_ops); + if (!h) + return -ENOMEM; + + for (i = 0; i < (unsigned)n; i++) { + r = hashmap_put(h, times[i].name, ×[i]); + if (r < 0) + return r; + } + unit_times_hashmap = h; + + pager_open_if_enabled(); + + puts("The time after the unit is active or started is printed after the \"@\" character.\n" + "The time the unit takes to start is printed after the \"+\" character.\n"); + + if (!strv_isempty(names)) { + char **name; + STRV_FOREACH(name, names) + list_dependencies(bus, *name); + } else + list_dependencies(bus, SPECIAL_DEFAULT_TARGET); + + hashmap_free(h); + free_unit_times(times, (unsigned) n); + return 0; +} + +static int analyze_blame(sd_bus *bus) { + struct unit_times *times; + unsigned i; + int n; + + n = acquire_time_data(bus, ×); + if (n <= 0) + return n; + + qsort(times, n, sizeof(struct unit_times), compare_unit_time); + + pager_open_if_enabled(); + + for (i = 0; i < (unsigned) n; i++) { + char ts[FORMAT_TIMESPAN_MAX]; + + if (times[i].time > 0) + printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name); + } + + free_unit_times(times, (unsigned) n); + return 0; +} + +static int analyze_time(sd_bus *bus) { + _cleanup_free_ char *buf = NULL; + int r; + + r = pretty_boot_time(bus, &buf); + if (r < 0) + return r; + + puts(buf); + return 0; +} + +static int graph_one_property(sd_bus *bus, const UnitInfo *u, const char* prop, const char *color, char* patterns[], char* from_patterns[], char* to_patterns[]) { + _cleanup_strv_free_ char **units = NULL; + char **unit; + int r; + bool match_patterns; + + assert(u); + assert(prop); + assert(color); + + match_patterns = strv_fnmatch(patterns, u->id, 0); + + if (!strv_isempty(from_patterns) && + !match_patterns && + !strv_fnmatch(from_patterns, u->id, 0)) + return 0; + + r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units); + if (r < 0) + return r; + + STRV_FOREACH(unit, units) { + bool match_patterns2; + + match_patterns2 = strv_fnmatch(patterns, *unit, 0); + + if (!strv_isempty(to_patterns) && + !match_patterns2 && + !strv_fnmatch(to_patterns, *unit, 0)) + continue; + + if (!strv_isempty(patterns) && !match_patterns && !match_patterns2) + continue; + + printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color); + } + + return 0; +} + +static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[], char *from_patterns[], char *to_patterns[]) { + int r; + + assert(bus); + assert(u); + + if (IN_SET(arg_dot, DEP_ORDER, DEP_ALL)) { + r = graph_one_property(bus, u, "After", "green", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + } + + if (IN_SET(arg_dot, DEP_REQUIRE, DEP_ALL)) { + r = graph_one_property(bus, u, "Requires", "black", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + r = graph_one_property(bus, u, "Requisite", "darkblue", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + r = graph_one_property(bus, u, "Wants", "grey66", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + r = graph_one_property(bus, u, "Conflicts", "red", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + } + + return 0; +} + +static int expand_patterns(sd_bus *bus, char **patterns, char ***ret) { + _cleanup_strv_free_ char **expanded_patterns = NULL; + char **pattern; + int r; + + STRV_FOREACH(pattern, patterns) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *unit = NULL, *unit_id = NULL; + + if (strv_extend(&expanded_patterns, *pattern) < 0) + return log_oom(); + + if (string_is_glob(*pattern)) + continue; + + unit = unit_dbus_path_from_name(*pattern); + if (!unit) + return log_oom(); + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + unit, + "org.freedesktop.systemd1.Unit", + "Id", + &error, + &unit_id); + if (r < 0) + return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r)); + + if (!streq(*pattern, unit_id)) { + if (strv_extend(&expanded_patterns, unit_id) < 0) + return log_oom(); + } + } + + *ret = expanded_patterns; + expanded_patterns = NULL; /* do not free */ + + return 0; +} + +static int dot(sd_bus *bus, char* patterns[]) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_strv_free_ char **expanded_patterns = NULL; + _cleanup_strv_free_ char **expanded_from_patterns = NULL; + _cleanup_strv_free_ char **expanded_to_patterns = NULL; + int r; + UnitInfo u; + + r = expand_patterns(bus, patterns, &expanded_patterns); + if (r < 0) + return r; + + r = expand_patterns(bus, arg_dot_from_patterns, &expanded_from_patterns); + if (r < 0) + return r; + + r = expand_patterns(bus, arg_dot_to_patterns, &expanded_to_patterns); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnits", + &error, + &reply, + ""); + if (r < 0) { + log_error("Failed to list units: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); + if (r < 0) + return bus_log_parse_error(r); + + printf("digraph systemd {\n"); + + while ((r = bus_parse_unit_info(reply, &u)) > 0) { + + r = graph_one(bus, &u, expanded_patterns, expanded_from_patterns, expanded_to_patterns); + if (r < 0) + return r; + } + if (r < 0) + return bus_log_parse_error(r); + + printf("}\n"); + + log_info(" Color legend: black = Requires\n" + " dark blue = Requisite\n" + " dark grey = Wants\n" + " red = Conflicts\n" + " green = After\n"); + + if (on_tty()) + log_notice("-- You probably want to process this output with graphviz' dot tool.\n" + "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n"); + + return 0; +} + +static int dump(sd_bus *bus, char **args) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *text = NULL; + int r; + + if (!strv_isempty(args)) { + log_error("Too many arguments."); + return -E2BIG; + } + + pager_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Dump", + &error, + &reply, + ""); + if (r < 0) + return log_error_errno(r, "Failed issue method call: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "s", &text); + if (r < 0) + return bus_log_parse_error(r); + + fputs(text, stdout); + return 0; +} + +static int set_log_level(sd_bus *bus, char **args) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(args); + + if (strv_length(args) != 1) { + log_error("This command expects one argument only."); + return -E2BIG; + } + + r = sd_bus_set_property( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LogLevel", + &error, + "s", + args[0]); + if (r < 0) + return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r)); + + return 0; +} + +static int set_log_target(sd_bus *bus, char **args) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(args); + + if (strv_length(args) != 1) { + log_error("This command expects one argument only."); + return -E2BIG; + } + + r = sd_bus_set_property( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LogTarget", + &error, + "s", + args[0]); + if (r < 0) + return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r)); + + return 0; +} + +static void help(void) { + + pager_open_if_enabled(); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Profile systemd, show unit dependencies, check unit files.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --system Operate on system systemd instance\n" + " --user Operate on user systemd instance\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --order Show only order in the graph\n" + " --require Show only requirement in the graph\n" + " --from-pattern=GLOB Show only origins in the graph\n" + " --to-pattern=GLOB Show only destinations in the graph\n" + " --fuzz=SECONDS Also print also services which finished SECONDS\n" + " earlier than the latest in the branch\n" + " --man[=BOOL] Do [not] check for existence of man pages\n\n" + "Commands:\n" + " time Print time spent in the kernel\n" + " blame Print list of running units ordered by time to init\n" + " critical-chain Print a tree of the time critical chain of units\n" + " plot Output SVG graphic showing service initialization\n" + " dot Output dependency graph in dot(1) format\n" + " set-log-level LEVEL Set logging threshold for manager\n" + " set-log-target TARGET Set logging target for manager\n" + " dump Output state serialization of service manager\n" + " verify FILE... Check unit files for correctness\n" + , program_invocation_short_name); + + /* When updating this list, including descriptions, apply + * changes to shell-completion/bash/systemd-analyze and + * shell-completion/zsh/_systemd-analyze too. */ +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_ORDER, + ARG_REQUIRE, + ARG_USER, + ARG_SYSTEM, + ARG_DOT_FROM_PATTERN, + ARG_DOT_TO_PATTERN, + ARG_FUZZ, + ARG_NO_PAGER, + ARG_MAN, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "order", no_argument, NULL, ARG_ORDER }, + { "require", no_argument, NULL, ARG_REQUIRE }, + { "user", no_argument, NULL, ARG_USER }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN }, + { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN }, + { "fuzz", required_argument, NULL, ARG_FUZZ }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "man", optional_argument, NULL, ARG_MAN }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + {} + }; + + int r, c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_USER: + arg_user = true; + break; + + case ARG_SYSTEM: + arg_user = false; + break; + + case ARG_ORDER: + arg_dot = DEP_ORDER; + break; + + case ARG_REQUIRE: + arg_dot = DEP_REQUIRE; + break; + + case ARG_DOT_FROM_PATTERN: + if (strv_extend(&arg_dot_from_patterns, optarg) < 0) + return log_oom(); + + break; + + case ARG_DOT_TO_PATTERN: + if (strv_extend(&arg_dot_to_patterns, optarg) < 0) + return log_oom(); + + break; + + case ARG_FUZZ: + r = parse_sec(optarg, &arg_fuzz); + if (r < 0) + return r; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_MAN: + if (optarg) { + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --man= argument."); + return -EINVAL; + } + + arg_man = !!r; + } else + arg_man = true; + + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option code."); + } + + return 1; /* work to do */ +} + +int main(int argc, char *argv[]) { + int r; + + setlocale(LC_ALL, ""); + setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */ + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (streq_ptr(argv[optind], "verify")) + r = verify_units(argv+optind+1, + arg_user ? MANAGER_USER : MANAGER_SYSTEM, + arg_man); + else { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + + r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus); + if (r < 0) { + log_error_errno(r, "Failed to create bus connection: %m"); + goto finish; + } + + if (!argv[optind] || streq(argv[optind], "time")) + r = analyze_time(bus); + else if (streq(argv[optind], "blame")) + r = analyze_blame(bus); + else if (streq(argv[optind], "critical-chain")) + r = analyze_critical_chain(bus, argv+optind+1); + else if (streq(argv[optind], "plot")) + r = analyze_plot(bus); + else if (streq(argv[optind], "dot")) + r = dot(bus, argv+optind+1); + else if (streq(argv[optind], "dump")) + r = dump(bus, argv+optind+1); + else if (streq(argv[optind], "set-log-level")) + r = set_log_level(bus, argv+optind+1); + else if (streq(argv[optind], "set-log-target")) + r = set_log_target(bus, argv+optind+1); + else + log_error("Unknown operation '%s'.", argv[optind]); + } + +finish: + pager_close(); + + strv_free(arg_dot_from_patterns); + strv_free(arg_dot_to_patterns); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-bootchart/Makefile b/src/systemd-bootchart/Makefile new file mode 100644 index 0000000000..5af4cebd20 --- /dev/null +++ b/src/systemd-bootchart/Makefile @@ -0,0 +1,47 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +if ENABLE_BOOTCHART +systemd_bootchart_SOURCES = \ + src/bootchart/bootchart.c \ + src/bootchart/bootchart.h \ + src/bootchart/store.c \ + src/bootchart/store.h \ + src/bootchart/svg.c \ + src/bootchart/svg.h + +systemd_bootchart_LDADD = \ + libshared.la + +rootlibexec_PROGRAMS += \ + systemd-bootchart + +dist_pkgsysconf_DATA += \ + src/bootchart/bootchart.conf + +nodist_systemunit_DATA += \ + units/systemd-bootchart.service +endif + +EXTRA_DIST += \ + units/systemd-bootchart.service.in + diff --git a/src/systemd-bootchart/bootchart.c b/src/systemd-bootchart/bootchart.c new file mode 100644 index 0000000000..77d158f5f9 --- /dev/null +++ b/src/systemd-bootchart/bootchart.c @@ -0,0 +1,531 @@ +/*** + This file is part of systemd. + + Copyright (C) 2009-2013 Intel Corporation + + Authors: + Auke Kok + + 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 . + ***/ + +/*** + + Many thanks to those who contributed ideas and code: + - Ziga Mahkovec - Original bootchart author + - Anders Norgaard - PyBootchartgui + - Michael Meeks - bootchart2 + - Scott James Remnant - Ubuntu C-based logger + - Arjan van der Ven - for the idea to merge bootgraph.pl functionality + + ***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sd-journal.h" + +#include "alloc-util.h" +#include "bootchart.h" +#include "conf-parser.h" +#include "def.h" +#include "fd-util.h" +#include "fileio.h" +#include "io-util.h" +#include "list.h" +#include "macro.h" +#include "parse-util.h" +#include "path-util.h" +#include "store.h" +#include "string-util.h" +#include "strxcpyx.h" +#include "svg.h" +#include "util.h" + +static int exiting = 0; + +#define DEFAULT_SAMPLES_LEN 500 +#define DEFAULT_HZ 25.0 +#define DEFAULT_SCALE_X 100.0 /* 100px = 1sec */ +#define DEFAULT_SCALE_Y 20.0 /* 16px = 1 process bar */ +#define DEFAULT_INIT ROOTLIBEXECDIR "/systemd" +#define DEFAULT_OUTPUT "/run/log" + +/* graph defaults */ +bool arg_entropy = false; +bool arg_initcall = true; +bool arg_relative = false; +bool arg_filter = true; +bool arg_show_cmdline = false; +bool arg_show_cgroup = false; +bool arg_pss = false; +bool arg_percpu = false; +int arg_samples_len = DEFAULT_SAMPLES_LEN; /* we record len+1 (1 start sample) */ +double arg_hz = DEFAULT_HZ; +double arg_scale_x = DEFAULT_SCALE_X; +double arg_scale_y = DEFAULT_SCALE_Y; + +char arg_init_path[PATH_MAX] = DEFAULT_INIT; +char arg_output_path[PATH_MAX] = DEFAULT_OUTPUT; + +static void signal_handler(int sig) { + exiting = 1; +} + +#define BOOTCHART_MAX (16*1024*1024) + +static void parse_conf(void) { + char *init = NULL, *output = NULL; + const ConfigTableItem items[] = { + { "Bootchart", "Samples", config_parse_int, 0, &arg_samples_len }, + { "Bootchart", "Frequency", config_parse_double, 0, &arg_hz }, + { "Bootchart", "Relative", config_parse_bool, 0, &arg_relative }, + { "Bootchart", "Filter", config_parse_bool, 0, &arg_filter }, + { "Bootchart", "Output", config_parse_path, 0, &output }, + { "Bootchart", "Init", config_parse_path, 0, &init }, + { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &arg_pss }, + { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &arg_entropy }, + { "Bootchart", "ScaleX", config_parse_double, 0, &arg_scale_x }, + { "Bootchart", "ScaleY", config_parse_double, 0, &arg_scale_y }, + { "Bootchart", "ControlGroup", config_parse_bool, 0, &arg_show_cgroup }, + { "Bootchart", "PerCPU", config_parse_bool, 0, &arg_percpu }, + { NULL, NULL, NULL, 0, NULL } + }; + + config_parse_many(PKGSYSCONFDIR "/bootchart.conf", + CONF_PATHS_NULSTR("systemd/bootchart.conf.d"), + NULL, config_item_table_lookup, items, true, NULL); + + if (init != NULL) + strscpy(arg_init_path, sizeof(arg_init_path), init); + if (output != NULL) + strscpy(arg_output_path, sizeof(arg_output_path), output); +} + +static void help(void) { + printf("Usage: %s [OPTIONS]\n\n" + "Options:\n" + " -r --rel Record time relative to recording\n" + " -f --freq=FREQ Sample frequency [%g]\n" + " -n --samples=N Stop sampling at [%d] samples\n" + " -x --scale-x=N Scale the graph horizontally [%g] \n" + " -y --scale-y=N Scale the graph vertically [%g] \n" + " -p --pss Enable PSS graph (CPU intensive)\n" + " -e --entropy Enable the entropy_avail graph\n" + " -o --output=PATH Path to output files [%s]\n" + " -i --init=PATH Path to init executable [%s]\n" + " -F --no-filter Disable filtering of unimportant or ephemeral processes\n" + " -C --cmdline Display full command lines with arguments\n" + " -c --control-group Display process control group\n" + " --per-cpu Draw each CPU utilization and wait bar also\n" + " -h --help Display this message\n\n" + "See bootchart.conf for more information.\n", + program_invocation_short_name, + DEFAULT_HZ, + DEFAULT_SAMPLES_LEN, + DEFAULT_SCALE_X, + DEFAULT_SCALE_Y, + DEFAULT_OUTPUT, + DEFAULT_INIT); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_PERCPU = 0x100, + }; + + static const struct option options[] = { + {"rel", no_argument, NULL, 'r' }, + {"freq", required_argument, NULL, 'f' }, + {"samples", required_argument, NULL, 'n' }, + {"pss", no_argument, NULL, 'p' }, + {"output", required_argument, NULL, 'o' }, + {"init", required_argument, NULL, 'i' }, + {"no-filter", no_argument, NULL, 'F' }, + {"cmdline", no_argument, NULL, 'C' }, + {"control-group", no_argument, NULL, 'c' }, + {"help", no_argument, NULL, 'h' }, + {"scale-x", required_argument, NULL, 'x' }, + {"scale-y", required_argument, NULL, 'y' }, + {"entropy", no_argument, NULL, 'e' }, + {"per-cpu", no_argument, NULL, ARG_PERCPU}, + {} + }; + int c, r; + + if (getpid() == 1) + opterr = 0; + + while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0) + switch (c) { + + case 'r': + arg_relative = true; + break; + case 'f': + r = safe_atod(optarg, &arg_hz); + if (r < 0) + log_warning_errno(r, "failed to parse --freq/-f argument '%s': %m", + optarg); + break; + case 'F': + arg_filter = false; + break; + case 'C': + arg_show_cmdline = true; + break; + case 'c': + arg_show_cgroup = true; + break; + case 'n': + r = safe_atoi(optarg, &arg_samples_len); + if (r < 0) + log_warning_errno(r, "failed to parse --samples/-n argument '%s': %m", + optarg); + break; + case 'o': + path_kill_slashes(optarg); + strscpy(arg_output_path, sizeof(arg_output_path), optarg); + break; + case 'i': + path_kill_slashes(optarg); + strscpy(arg_init_path, sizeof(arg_init_path), optarg); + break; + case 'p': + arg_pss = true; + break; + case 'x': + r = safe_atod(optarg, &arg_scale_x); + if (r < 0) + log_warning_errno(r, "failed to parse --scale-x/-x argument '%s': %m", + optarg); + break; + case 'y': + r = safe_atod(optarg, &arg_scale_y); + if (r < 0) + log_warning_errno(r, "failed to parse --scale-y/-y argument '%s': %m", + optarg); + break; + case 'e': + arg_entropy = true; + break; + case ARG_PERCPU: + arg_percpu = true; + break; + case 'h': + help(); + return 0; + case '?': + if (getpid() != 1) + return -EINVAL; + else + return 0; + default: + assert_not_reached("Unhandled option code."); + } + + if (arg_hz <= 0) { + log_error("Frequency needs to be > 0"); + return -EINVAL; + } + + return 1; +} + +static int do_journal_append(char *file) { + _cleanup_free_ char *bootchart_message = NULL; + _cleanup_free_ char *bootchart_file = NULL; + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = -1; + struct iovec iovec[5]; + int r, j = 0; + ssize_t n; + + bootchart_file = strappend("BOOTCHART_FILE=", file); + if (!bootchart_file) + return log_oom(); + + IOVEC_SET_STRING(iovec[j++], bootchart_file); + IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518"); + IOVEC_SET_STRING(iovec[j++], "PRIORITY=7"); + bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL); + if (!bootchart_message) + return log_oom(); + + IOVEC_SET_STRING(iovec[j++], bootchart_message); + + p = malloc(10 + BOOTCHART_MAX); + if (!p) + return log_oom(); + + memcpy(p, "BOOTCHART=", 10); + + fd = open(file, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open bootchart data \"%s\": %m", file); + + n = loop_read(fd, p + 10, BOOTCHART_MAX, false); + if (n < 0) + return log_error_errno(n, "Failed to read bootchart data: %m"); + + iovec[j].iov_base = p; + iovec[j].iov_len = 10 + n; + j++; + + r = sd_journal_sendv(iovec, j); + if (r < 0) + log_error_errno(r, "Failed to send bootchart: %m"); + + return 0; +} + +int main(int argc, char *argv[]) { + static struct list_sample_data *sampledata; + _cleanup_closedir_ DIR *proc = NULL; + _cleanup_free_ char *build = NULL; + _cleanup_fclose_ FILE *of = NULL; + _cleanup_close_ int sysfd = -1; + struct ps_struct *ps_first; + double graph_start; + double log_start; + double interval; + char output_file[PATH_MAX]; + char datestr[200]; + int pscount = 0; + int n_cpus = 0; + int overrun = 0; + time_t t = 0; + int r, samples; + struct ps_struct *ps; + struct rlimit rlim; + struct list_sample_data *head; + struct sigaction sig = { + .sa_handler = signal_handler, + }; + + parse_conf(); + + r = parse_argv(argc, argv); + if (r < 0) + return EXIT_FAILURE; + + if (r == 0) + return EXIT_SUCCESS; + + /* + * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then + * fork: + * - parent execs executable specified via init_path[] (/usr/lib/systemd/systemd by default) as pid=1 + * - child logs data + */ + if (getpid() == 1) { + if (fork()) + /* parent */ + execl(arg_init_path, arg_init_path, NULL); + } + argv[0][0] = '@'; + + rlim.rlim_cur = 4096; + rlim.rlim_max = 4096; + (void) setrlimit(RLIMIT_NOFILE, &rlim); + + /* start with empty ps LL */ + ps_first = new0(struct ps_struct, 1); + if (!ps_first) { + log_oom(); + return EXIT_FAILURE; + } + + /* handle TERM/INT nicely */ + sigaction(SIGHUP, &sig, NULL); + + interval = (1.0 / arg_hz) * 1000000000.0; + + if (arg_relative) + graph_start = log_start = gettime_ns(); + else { + struct timespec n; + double uptime; + + clock_gettime(clock_boottime_or_monotonic(), &n); + uptime = (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC)); + + log_start = gettime_ns(); + graph_start = log_start - uptime; + } + + if (graph_start < 0.0) { + log_error("Failed to setup graph start time.\n\n" + "The system uptime probably includes time that the system was suspended. " + "Use --rel to bypass this issue."); + return EXIT_FAILURE; + } + + LIST_HEAD_INIT(head); + + /* main program loop */ + for (samples = 0; !exiting && samples < arg_samples_len; samples++) { + int res; + double sample_stop; + double elapsed; + double timeleft; + + sampledata = new0(struct list_sample_data, 1); + if (sampledata == NULL) { + log_oom(); + return EXIT_FAILURE; + } + + sampledata->sampletime = gettime_ns(); + sampledata->counter = samples; + + if (sysfd < 0) + sysfd = open("/sys", O_RDONLY|O_CLOEXEC); + + if (!build) { + if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &build, NULL) == -ENOENT) + parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &build, NULL); + } + + if (proc) + rewinddir(proc); + else + proc = opendir("/proc"); + + /* wait for /proc to become available, discarding samples */ + if (proc) { + r = log_sample(proc, samples, ps_first, &sampledata, &pscount, &n_cpus); + if (r < 0) + return EXIT_FAILURE; + } + + sample_stop = gettime_ns(); + + elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0; + timeleft = interval - elapsed; + + /* + * check if we have not consumed our entire timeslice. If we + * do, don't sleep and take a new sample right away. + * we'll lose all the missed samples and overrun our total + * time + */ + if (timeleft > 0) { + struct timespec req; + + req.tv_sec = (time_t)(timeleft / 1000000000.0); + req.tv_nsec = (long)(timeleft - (req.tv_sec * 1000000000.0)); + + res = nanosleep(&req, NULL); + if (res) { + if (errno == EINTR) + /* caught signal, probably HUP! */ + break; + log_error_errno(errno, "nanosleep() failed: %m"); + return EXIT_FAILURE; + } + } else { + overrun++; + /* calculate how many samples we lost and scrap them */ + arg_samples_len -= (int)(-timeleft / interval); + } + LIST_PREPEND(link, head, sampledata); + } + + /* do some cleanup, close fd's */ + ps = ps_first; + while (ps->next_ps) { + ps = ps->next_ps; + ps->schedstat = safe_close(ps->schedstat); + ps->sched = safe_close(ps->sched); + ps->smaps = safe_fclose(ps->smaps); + } + + if (!of) { + t = time(NULL); + r = strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t)); + assert_se(r > 0); + + snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr); + of = fopen(output_file, "we"); + } + + if (!of) { + log_error("Error opening output file '%s': %m\n", output_file); + return EXIT_FAILURE; + } + + r = svg_do(of, strna(build), head, ps_first, + samples, pscount, n_cpus, graph_start, + log_start, interval, overrun); + + if (r < 0) { + log_error_errno(r, "Error generating svg file: %m"); + return EXIT_FAILURE; + } + + log_info("systemd-bootchart wrote %s\n", output_file); + + r = do_journal_append(output_file); + if (r < 0) + return EXIT_FAILURE; + + /* nitpic cleanups */ + ps = ps_first->next_ps; + while (ps->next_ps) { + struct ps_struct *old; + + old = ps; + old->sample = ps->first; + ps = ps->next_ps; + while (old->sample->next) { + struct ps_sched_struct *oldsample = old->sample; + + old->sample = old->sample->next; + free(oldsample); + } + free(old->cgroup); + free(old->sample); + free(old); + } + + free(ps->cgroup); + free(ps->sample); + free(ps); + + sampledata = head; + while (sampledata->link_prev) { + struct list_sample_data *old_sampledata = sampledata; + sampledata = sampledata->link_prev; + free(old_sampledata); + } + free(sampledata); + + /* don't complain when overrun once, happens most commonly on 1st sample */ + if (overrun > 1) + log_warning("systemd-bootchart: sample time overrun %i times\n", overrun); + + return 0; +} diff --git a/src/systemd-bootchart/bootchart.conf b/src/systemd-bootchart/bootchart.conf new file mode 100644 index 0000000000..4f5e50936e --- /dev/null +++ b/src/systemd-bootchart/bootchart.conf @@ -0,0 +1,26 @@ +# This file is part of systemd. +# +# 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. +# +# Entries in this file show the compile time defaults. +# You can change settings by editing this file. +# Defaults can be restored by simply deleting this file. +# +# See bootchart.conf(5) for details. + +[Bootchart] +#Samples=500 +#Frequency=25 +#Relative=no +#Filter=yes +#Output= +#Init=/path/to/init-binary +#PlotMemoryUsage=no +#PlotEntropyGraph=no +#ScaleX=100 +#ScaleY=20 +#ControlGroup=no +#PerCPU=no diff --git a/src/systemd-bootchart/bootchart.h b/src/systemd-bootchart/bootchart.h new file mode 100644 index 0000000000..1b445b954b --- /dev/null +++ b/src/systemd-bootchart/bootchart.h @@ -0,0 +1,119 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2009-2013 Intel Corporation + + Authors: + Auke Kok + + 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 . +***/ + +#include + +#include "list.h" + +#define MAXCPUS 16 +#define MAXPIDS 65535 + +struct block_stat_struct { + /* /proc/vmstat pgpgin & pgpgout */ + int bi; + int bo; +}; + +struct cpu_stat_sample_struct { + /* /proc/schedstat fields 10 & 11 (after name) */ + double runtime; + double waittime; +}; + +/* per process, per sample data we will log */ +struct ps_sched_struct { + /* /proc//schedstat fields 1 & 2 */ + double runtime; + double waittime; + int pss; + struct list_sample_data *sampledata; + struct ps_sched_struct *next; + struct ps_sched_struct *prev; + struct ps_sched_struct *cross; /* cross pointer */ + struct ps_struct *ps_new; +}; + +struct list_sample_data { + double runtime[MAXCPUS]; + double waittime[MAXCPUS]; + double sampletime; + int entropy_avail; + struct block_stat_struct blockstat; + LIST_FIELDS(struct list_sample_data, link); /* DLL */ + int counter; +}; + +/* process info */ +struct ps_struct { + struct ps_struct *next_ps; /* SLL pointer */ + struct ps_struct *parent; /* ppid ref */ + struct ps_struct *children; /* children */ + struct ps_struct *next; /* siblings */ + + /* must match - otherwise it's a new process with same PID */ + char name[256]; + int pid; + int ppid; + char *cgroup; + + /* cache fd's */ + int sched; + int schedstat; + FILE *smaps; + + /* pointers to first/last seen timestamps */ + struct ps_sched_struct *first; + struct ps_sched_struct *last; + + /* records actual start time, may be way before bootchart runs */ + double starttime; + + /* record human readable total cpu time */ + double total; + + /* largest PSS size found */ + int pss_max; + + /* for drawing connection lines later */ + double pos_x; + double pos_y; + + struct ps_sched_struct *sample; +}; + +extern bool arg_relative; +extern bool arg_filter; +extern bool arg_show_cmdline; +extern bool arg_show_cgroup; +extern bool arg_pss; +extern bool arg_entropy; +extern bool arg_percpu; +extern bool arg_initcall; +extern int arg_samples_len; +extern double arg_hz; +extern double arg_scale_x; +extern double arg_scale_y; + +extern char arg_output_path[PATH_MAX]; +extern char arg_init_path[PATH_MAX]; diff --git a/src/systemd-bootchart/store.c b/src/systemd-bootchart/store.c new file mode 100644 index 0000000000..42cb8043ce --- /dev/null +++ b/src/systemd-bootchart/store.c @@ -0,0 +1,555 @@ +/*** + This file is part of systemd. + + Copyright (C) 2009-2013 Intel Corporation + + Authors: + Auke Kok + + 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 . + ***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "bootchart.h" +#include "cgroup-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "parse-util.h" +#include "store.h" +#include "string-util.h" +#include "strxcpyx.h" +#include "time-util.h" +#include "util.h" + +/* + * Alloc a static 4k buffer for stdio - primarily used to increase + * PSS buffering from the default 1k stdin buffer to reduce + * read() overhead. + */ +static char smaps_buf[4096]; +static int skip = 0; + +double gettime_ns(void) { + struct timespec n; + + clock_gettime(CLOCK_MONOTONIC, &n); + + return (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC)); +} + +static char *bufgetline(char *buf) { + char *c; + + if (!buf) + return NULL; + + c = strchr(buf, '\n'); + if (c) + c++; + + return c; +} + +static int pid_cmdline_strscpy(int procfd, char *buffer, size_t buf_len, int pid) { + char filename[PATH_MAX]; + _cleanup_close_ int fd = -1; + ssize_t n; + + sprintf(filename, "%d/cmdline", pid); + fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return -errno; + + n = read(fd, buffer, buf_len-1); + if (n > 0) { + int i; + for (i = 0; i < n; i++) + if (buffer[i] == '\0') + buffer[i] = ' '; + buffer[n] = '\0'; + } + + return 0; +} + +int log_sample(DIR *proc, + int sample, + struct ps_struct *ps_first, + struct list_sample_data **ptr, + int *pscount, + int *cpus) { + + static int vmstat = -1; + _cleanup_free_ char *buf_schedstat = NULL; + char buf[4096]; + char key[256]; + char val[256]; + char rt[256]; + char wt[256]; + char *m; + int r; + int c; + int p; + int mod; + static int e_fd = -1; + ssize_t s; + ssize_t n; + struct dirent *ent; + int fd; + struct list_sample_data *sampledata; + struct ps_sched_struct *ps_prev = NULL; + int procfd; + int taskfd = -1; + + sampledata = *ptr; + + procfd = dirfd(proc); + if (procfd < 0) + return -errno; + + if (vmstat < 0) { + /* block stuff */ + vmstat = openat(procfd, "vmstat", O_RDONLY|O_CLOEXEC); + if (vmstat < 0) + return log_error_errno(errno, "Failed to open /proc/vmstat: %m"); + } + + n = pread(vmstat, buf, sizeof(buf) - 1, 0); + if (n <= 0) { + vmstat = safe_close(vmstat); + if (n < 0) + return -errno; + return -ENODATA; + } + + buf[n] = '\0'; + + m = buf; + while (m) { + if (sscanf(m, "%s %s", key, val) < 2) + goto vmstat_next; + if (streq(key, "pgpgin")) + sampledata->blockstat.bi = atoi(val); + if (streq(key, "pgpgout")) { + sampledata->blockstat.bo = atoi(val); + break; + } +vmstat_next: + m = bufgetline(m); + if (!m) + break; + } + + /* Parse "/proc/schedstat" for overall CPU utilization */ + r = read_full_file("/proc/schedstat", &buf_schedstat, NULL); + if (r < 0) + return log_error_errno(r, "Unable to read schedstat: %m"); + + m = buf_schedstat; + while (m) { + if (sscanf(m, "%s %*s %*s %*s %*s %*s %*s %s %s", key, rt, wt) < 3) + goto schedstat_next; + + if (strstr(key, "cpu")) { + r = safe_atoi((const char*)(key+3), &c); + if (r < 0 || c > MAXCPUS -1) + /* Oops, we only have room for MAXCPUS data */ + break; + sampledata->runtime[c] = atoll(rt); + sampledata->waittime[c] = atoll(wt); + + if (c == *cpus) + *cpus = c + 1; + } +schedstat_next: + m = bufgetline(m); + if (!m) + break; + } + + if (arg_entropy) { + if (e_fd < 0) { + e_fd = openat(procfd, "sys/kernel/random/entropy_avail", O_RDONLY|O_CLOEXEC); + if (e_fd < 0) + return log_error_errno(errno, "Failed to open /proc/sys/kernel/random/entropy_avail: %m"); + } + + n = pread(e_fd, buf, sizeof(buf) - 1, 0); + if (n <= 0) { + e_fd = safe_close(e_fd); + } else { + buf[n] = '\0'; + sampledata->entropy_avail = atoi(buf); + } + } + + while ((ent = readdir(proc)) != NULL) { + char filename[PATH_MAX]; + int pid; + struct ps_struct *ps; + + if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9')) + continue; + + pid = atoi(ent->d_name); + + if (pid >= MAXPIDS) + continue; + + ps = ps_first; + while (ps->next_ps) { + ps = ps->next_ps; + if (ps->pid == pid) + break; + } + + /* end of our LL? then append a new record */ + if (ps->pid != pid) { + _cleanup_fclose_ FILE *st = NULL; + char t[32]; + struct ps_struct *parent; + + ps->next_ps = new0(struct ps_struct, 1); + if (!ps->next_ps) + return log_oom(); + + ps = ps->next_ps; + ps->pid = pid; + ps->sched = -1; + ps->schedstat = -1; + + ps->sample = new0(struct ps_sched_struct, 1); + if (!ps->sample) + return log_oom(); + + ps->sample->sampledata = sampledata; + + (*pscount)++; + + /* mark our first sample */ + ps->first = ps->last = ps->sample; + ps->sample->runtime = atoll(rt); + ps->sample->waittime = atoll(wt); + + /* get name, start time */ + if (ps->sched < 0) { + sprintf(filename, "%d/sched", pid); + ps->sched = openat(procfd, filename, O_RDONLY|O_CLOEXEC); + if (ps->sched < 0) + continue; + } + + s = pread(ps->sched, buf, sizeof(buf) - 1, 0); + if (s <= 0) { + ps->sched = safe_close(ps->sched); + continue; + } + buf[s] = '\0'; + + if (!sscanf(buf, "%s %*s %*s", key)) + continue; + + strscpy(ps->name, sizeof(ps->name), key); + + /* cmdline */ + if (arg_show_cmdline) + pid_cmdline_strscpy(procfd, ps->name, sizeof(ps->name), pid); + + /* discard line 2 */ + m = bufgetline(buf); + if (!m) + continue; + + m = bufgetline(m); + if (!m) + continue; + + if (!sscanf(m, "%*s %*s %s", t)) + continue; + + r = safe_atod(t, &ps->starttime); + if (r < 0) + continue; + + ps->starttime /= 1000.0; + + if (arg_show_cgroup) + /* if this fails, that's OK */ + cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, + ps->pid, &ps->cgroup); + + /* ppid */ + sprintf(filename, "%d/stat", pid); + fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC); + if (fd < 0) + continue; + + st = fdopen(fd, "re"); + if (!st) { + close(fd); + continue; + } + + if (!fscanf(st, "%*s %*s %*s %i", &p)) + continue; + + ps->ppid = p; + + /* + * setup child pointers + * + * these are used to paint the tree coherently later + * each parent has a LL of children, and a LL of siblings + */ + if (pid == 1) + continue; /* nothing to do for init atm */ + + /* kthreadd has ppid=0, which breaks our tree ordering */ + if (ps->ppid == 0) + ps->ppid = 1; + + parent = ps_first; + while ((parent->next_ps && parent->pid != ps->ppid)) + parent = parent->next_ps; + + if (parent->pid != ps->ppid) { + /* orphan */ + ps->ppid = 1; + parent = ps_first->next_ps; + } + + ps->parent = parent; + + if (!parent->children) { + /* it's the first child */ + parent->children = ps; + } else { + /* walk all children and append */ + struct ps_struct *children; + children = parent->children; + while (children->next) + children = children->next; + + children->next = ps; + } + } + + /* else -> found pid, append data in ps */ + + /* below here is all continuous logging parts - we get here on every + * iteration */ + + /* rt, wt */ + if (ps->schedstat < 0) { + sprintf(filename, "%d/schedstat", pid); + ps->schedstat = openat(procfd, filename, O_RDONLY|O_CLOEXEC); + if (ps->schedstat < 0) + continue; + } + + s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0); + if (s <= 0) { + /* clean up our file descriptors - assume that the process exited */ + close(ps->schedstat); + ps->schedstat = -1; + ps->sched = safe_close(ps->sched); + continue; + } + + buf[s] = '\0'; + + if (!sscanf(buf, "%s %s %*s", rt, wt)) + continue; + + ps->sample->next = new0(struct ps_sched_struct, 1); + if (!ps->sample->next) + return log_oom(); + + ps->sample->next->prev = ps->sample; + ps->sample = ps->sample->next; + ps->last = ps->sample; + ps->sample->runtime = atoll(rt); + ps->sample->waittime = atoll(wt); + ps->sample->sampledata = sampledata; + ps->sample->ps_new = ps; + if (ps_prev) + ps_prev->cross = ps->sample; + + ps_prev = ps->sample; + ps->total = (ps->last->runtime - ps->first->runtime) + / 1000000000.0; + + /* Take into account CPU runtime/waittime spent in non-main threads of the process + * by parsing "/proc/[pid]/task/[tid]/schedstat" for all [tid] != [pid] + * See https://github.com/systemd/systemd/issues/139 + */ + + /* Browse directory "/proc/[pid]/task" to know the thread ids of process [pid] */ + snprintf(filename, sizeof(filename), PID_FMT "/task", pid); + taskfd = openat(procfd, filename, O_RDONLY|O_DIRECTORY|O_CLOEXEC); + if (taskfd >= 0) { + _cleanup_closedir_ DIR *taskdir = NULL; + + taskdir = fdopendir(taskfd); + if (!taskdir) { + safe_close(taskfd); + return -errno; + } + FOREACH_DIRENT(ent, taskdir, break) { + int tid = -1; + _cleanup_close_ int tid_schedstat = -1; + long long delta_rt; + long long delta_wt; + + if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9')) + continue; + + /* Skip main thread as it was already accounted */ + r = safe_atoi(ent->d_name, &tid); + if (r < 0 || tid == pid) + continue; + + /* Parse "/proc/[pid]/task/[tid]/schedstat" */ + snprintf(filename, sizeof(filename), PID_FMT "/schedstat", tid); + tid_schedstat = openat(taskfd, filename, O_RDONLY|O_CLOEXEC); + + if (tid_schedstat == -1) + continue; + + s = pread(tid_schedstat, buf, sizeof(buf) - 1, 0); + if (s <= 0) + continue; + buf[s] = '\0'; + + if (!sscanf(buf, "%s %s %*s", rt, wt)) + continue; + + r = safe_atolli(rt, &delta_rt); + if (r < 0) + continue; + r = safe_atolli(rt, &delta_wt); + if (r < 0) + continue; + ps->sample->runtime += delta_rt; + ps->sample->waittime += delta_wt; + } + } + + if (!arg_pss) + goto catch_rename; + + /* Pss */ + if (!ps->smaps) { + sprintf(filename, "%d/smaps", pid); + fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC); + if (fd < 0) + continue; + ps->smaps = fdopen(fd, "re"); + if (!ps->smaps) { + close(fd); + continue; + } + setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf)); + } else { + rewind(ps->smaps); + } + + /* test to see if we need to skip another field */ + if (skip == 0) { + if (fgets(buf, sizeof(buf), ps->smaps) == NULL) { + continue; + } + if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) { + continue; + } + if (buf[392] == 'V') { + skip = 2; + } + else { + skip = 1; + } + rewind(ps->smaps); + } + + while (1) { + int pss_kb; + + /* skip one line, this contains the object mapped. */ + if (fgets(buf, sizeof(buf), ps->smaps) == NULL) { + break; + } + /* then there's a 28 char 14 line block */ + if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) { + break; + } + pss_kb = atoi(&buf[61]); + ps->sample->pss += pss_kb; + + /* skip one more line if this is a newer kernel */ + if (skip == 2) { + if (fgets(buf, sizeof(buf), ps->smaps) == NULL) + break; + } + } + + if (ps->sample->pss > ps->pss_max) + ps->pss_max = ps->sample->pss; + +catch_rename: + /* catch process rename, try to randomize time */ + mod = (arg_hz < 4.0) ? 4.0 : (arg_hz / 4.0); + if (((sample - ps->pid) + pid) % (int)(mod) == 0) { + + /* re-fetch name */ + /* get name, start time */ + if (ps->sched < 0) { + sprintf(filename, "%d/sched", pid); + ps->sched = openat(procfd, filename, O_RDONLY|O_CLOEXEC); + if (ps->sched < 0) + continue; + } + + s = pread(ps->sched, buf, sizeof(buf) - 1, 0); + if (s <= 0) { + /* clean up file descriptors */ + ps->sched = safe_close(ps->sched); + ps->schedstat = safe_close(ps->schedstat); + continue; + } + + buf[s] = '\0'; + + if (!sscanf(buf, "%s %*s %*s", key)) + continue; + + strscpy(ps->name, sizeof(ps->name), key); + + /* cmdline */ + if (arg_show_cmdline) + pid_cmdline_strscpy(procfd, ps->name, sizeof(ps->name), pid); + } + } + + return 0; +} diff --git a/src/systemd-bootchart/store.h b/src/systemd-bootchart/store.h new file mode 100644 index 0000000000..6e9acf2a6f --- /dev/null +++ b/src/systemd-bootchart/store.h @@ -0,0 +1,36 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2009-2013 Intel Corporation + + Authors: + Auke Kok + + 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 . +***/ + +#include + +#include "bootchart.h" + +double gettime_ns(void); +void log_uptime(void); +int log_sample(DIR *proc, + int sample, + struct ps_struct *ps_first, + struct list_sample_data **ptr, + int *pscount, + int *cpus); diff --git a/src/systemd-bootchart/svg.c b/src/systemd-bootchart/svg.c new file mode 100644 index 0000000000..f2af535061 --- /dev/null +++ b/src/systemd-bootchart/svg.c @@ -0,0 +1,1375 @@ +/*** + This file is part of systemd. + + Copyright (C) 2009-2013 Intel Corporation + + Authors: + Auke Kok + + 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 . + ***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "architecture.h" +#include "bootchart.h" +#include "fd-util.h" +#include "fileio.h" +#include "list.h" +#include "macro.h" +#include "stdio-util.h" +#include "store.h" +#include "svg.h" +#include "utf8.h" +#include "util.h" + +#define time_to_graph(t) ((t) * arg_scale_x) +#define ps_to_graph(n) ((n) * arg_scale_y) +#define kb_to_graph(m) ((m) * arg_scale_y * 0.0001) +#define to_color(n) (192.0 - ((n) * 192.0)) + +static const char * const colorwheel[12] = { + "rgb(255,32,32)", // red + "rgb(32,192,192)", // cyan + "rgb(255,128,32)", // orange + "rgb(128,32,192)", // blue-violet + "rgb(255,255,32)", // yellow + "rgb(192,32,128)", // red-violet + "rgb(32,255,32)", // green + "rgb(255,64,32)", // red-orange + "rgb(32,32,255)", // blue + "rgb(255,192,32)", // yellow-orange + "rgb(192,32,192)", // violet + "rgb(32,192,32)" // yellow-green +}; + +static double idletime = -1.0; +static int pfiltered = 0; +static int pcount = 0; +static int kcount = 0; +static double psize = 0; +static double ksize = 0; +static double esize = 0; +static struct list_sample_data *sampledata; +static struct list_sample_data *prev_sampledata; + +static void svg_header(FILE *of, struct list_sample_data *head, double graph_start, int n_cpus) { + double w; + double h; + struct list_sample_data *sampledata_last; + + assert(head); + + sampledata_last = head; + LIST_FOREACH_BEFORE(link, sampledata, head) { + sampledata_last = sampledata; + } + + /* min width is about 1600px due to the label */ + w = 150.0 + 10.0 + time_to_graph(sampledata_last->sampletime - graph_start); + w = ((w < 1600.0) ? 1600.0 : w); + + /* height is variable based on pss, psize, ksize */ + h = 400.0 + (arg_scale_y * 30.0) /* base graphs and title */ + + (arg_pss ? (100.0 * arg_scale_y) + (arg_scale_y * 7.0) : 0.0) /* pss estimate */ + + psize + ksize + esize + (n_cpus * 15 * arg_scale_y); + + fprintf(of, "\n"); + fprintf(of, "\n"); + + //fprintf(of, "\n", 1000 + 150 + (pcount * 20)); + fprintf(of, "\n\n"); + + /* write some basic info as a comment, including some help */ + fprintf(of, "\n"); + fprintf(of, "\n"); + fprintf(of, "\n"); + fprintf(of, "\n"); + fprintf(of, "\n\n"); + + fprintf(of, "\n", VERSION); + fprintf(of, "\n", arg_hz, arg_samples_len); + fprintf(of, "\n", arg_scale_x, arg_scale_y); + fprintf(of, "\n", arg_relative, arg_filter); + fprintf(of, "\n", arg_pss, arg_entropy); + fprintf(of, "\n\n", arg_output_path, arg_init_path); + + /* style sheet */ + fprintf(of, "\n \n\n\n"); +} + +static int svg_title(FILE *of, const char *build, int pscount, double log_start, int overrun) { + _cleanup_free_ char *cmdline = NULL; + _cleanup_free_ char *model = NULL; + _cleanup_free_ char *buf = NULL; + char date[256] = "Unknown"; + const char *cpu; + char *c; + time_t t; + int r; + struct utsname uts; + + r = read_one_line_file("/proc/cmdline", &cmdline); + if (r < 0) { + log_error_errno(r, "Unable to read cmdline: %m"); + return r; + } + + /* extract root fs so we can find disk model name in sysfs */ + /* FIXME: this works only in the simple case */ + c = strstr(cmdline, "root=/dev/"); + if (c) { + char rootbdev[4]; + char filename[32]; + + strncpy(rootbdev, &c[10], sizeof(rootbdev) - 1); + rootbdev[3] = '\0'; + xsprintf(filename, "/sys/block/%s/device/model", rootbdev); + + r = read_one_line_file(filename, &model); + if (r < 0) + log_info("Error reading disk model for %s: %m\n", rootbdev); + } + + /* various utsname parameters */ + r = uname(&uts); + if (r < 0) { + log_error("Error getting uname info\n"); + return -errno; + } + + /* date */ + t = time(NULL); + r = strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", localtime(&t)); + assert_se(r > 0); + + /* CPU type */ + r = get_proc_field("/proc/cpuinfo", PROC_CPUINFO_MODEL, "\n", &buf); + if (r < 0) + cpu = "Unknown"; + else + cpu = buf; + + fprintf(of, "Bootchart for %s - %s\n", + uts.nodename, date); + fprintf(of, "System: %s %s %s %s\n", + uts.sysname, uts.release, uts.version, uts.machine); + fprintf(of, "CPU: %s\n", cpu); + if (model) + fprintf(of, "Disk: %s\n", model); + fprintf(of, "Boot options: %s\n", cmdline); + fprintf(of, "Build: %s\n", build); + fprintf(of, "Log start time: %.03fs\n", log_start); + fprintf(of, "Idle time: "); + + if (idletime >= 0.0) + fprintf(of, "%.03fs", idletime); + else + fprintf(of, "Not detected"); + + fprintf(of, "\n"); + fprintf(of, "Graph data: %.03f samples/sec, recorded %i total, dropped %i samples, %i processes, %i filtered\n", + arg_hz, arg_samples_len, overrun, pscount, pfiltered); + + return 0; +} + +static void svg_graph_box(FILE *of, struct list_sample_data *head, int height, double graph_start) { + double d = 0.0; + int i = 0; + double finalsample = 0.0; + struct list_sample_data *sampledata_last; + + sampledata_last = head; + LIST_FOREACH_BEFORE(link, sampledata, head) { + sampledata_last = sampledata; + } + + finalsample = sampledata_last->sampletime; + + /* outside box, fill */ + fprintf(of, "\n", + time_to_graph(0.0), + time_to_graph(finalsample - graph_start), + ps_to_graph(height)); + + for (d = graph_start; d <= finalsample; + d += (arg_scale_x < 2.0 ? 60.0 : arg_scale_x < 10.0 ? 1.0 : 0.1)) { + /* lines for each second */ + if (i % 50 == 0) + fprintf(of, " \n", + time_to_graph(d - graph_start), + time_to_graph(d - graph_start), + ps_to_graph(height)); + else if (i % 10 == 0) + fprintf(of, " \n", + time_to_graph(d - graph_start), + time_to_graph(d - graph_start), + ps_to_graph(height)); + else + fprintf(of, " \n", + time_to_graph(d - graph_start), + time_to_graph(d - graph_start), + ps_to_graph(height)); + + /* time label */ + if (i % 10 == 0) + fprintf(of, " %.01fs\n", + time_to_graph(d - graph_start), + -5.0, d - graph_start); + + i++; + } +} + +/* xml comments must not contain "--" */ +static char* xml_comment_encode(const char* name) { + char *enc_name, *p; + + enc_name = strdup(name); + if (!enc_name) + return NULL; + + for (p = enc_name; *p; p++) + if (p[0] == '-' && p[1] == '-') + p[1] = '_'; + + return enc_name; +} + +static void svg_pss_graph(FILE *of, + struct list_sample_data *head, + struct ps_struct *ps_first, + double graph_start) { + struct ps_struct *ps; + int i; + struct list_sample_data *sampledata_last; + + sampledata_last = head; + LIST_FOREACH_BEFORE(link, sampledata, head) { + sampledata_last = sampledata; + } + + + fprintf(of, "\n\n\n"); + + fprintf(of, "\n Memory allocation - Pss\n"); + + /* vsize 1000 == 1000mb */ + svg_graph_box(of, head, 100, graph_start); + /* draw some hlines for usable memory sizes */ + for (i = 100000; i < 1000000; i += 100000) { + fprintf(of, " \n", + time_to_graph(.0), + kb_to_graph(i), + time_to_graph(sampledata_last->sampletime - graph_start), + kb_to_graph(i)); + fprintf(of, " %dM\n", + time_to_graph(sampledata_last->sampletime - graph_start) + 5, + kb_to_graph(i), (1000000 - i) / 1000); + } + fprintf(of, "\n"); + + /* now plot the graph itself */ + i = 1; + prev_sampledata = head; + LIST_FOREACH_BEFORE(link, sampledata, head) { + int bottom; + int top; + struct ps_sched_struct *cross_place; + + bottom = 0; + top = 0; + + /* put all the small pss blocks into the bottom */ + ps = ps_first; + while (ps->next_ps) { + ps = ps->next_ps; + if (!ps) + continue; + ps->sample = ps->first; + while (ps->sample->next) { + ps->sample = ps->sample->next; + if (ps->sample->sampledata == sampledata) + break; + } + if (ps->sample->sampledata == sampledata) { + if (ps->sample->pss <= (100 * arg_scale_y)) + top += ps->sample->pss; + break; + } + } + while (ps->sample->cross) { + cross_place = ps->sample->cross; + ps = ps->sample->cross->ps_new; + ps->sample = cross_place; + if (ps->sample->pss <= (100 * arg_scale_y)) + top += ps->sample->pss; + } + + fprintf(of, " \n", + "rgb(64,64,64)", + time_to_graph(prev_sampledata->sampletime - graph_start), + kb_to_graph(1000000.0 - top), + time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), + kb_to_graph(top - bottom)); + bottom = top; + + /* now plot the ones that are of significant size */ + ps = ps_first; + while (ps->next_ps) { + ps = ps->next_ps; + if (!ps) + continue; + ps->sample = ps->first; + while (ps->sample->next) { + ps->sample = ps->sample->next; + if (ps->sample->sampledata == sampledata) + break; + } + /* don't draw anything smaller than 2mb */ + if (ps->sample->sampledata != sampledata) + continue; + if (ps->sample->pss > (100 * arg_scale_y)) { + top = bottom + ps->sample->pss; + fprintf(of, " \n", + colorwheel[ps->pid % 12], + time_to_graph(prev_sampledata->sampletime - graph_start), + kb_to_graph(1000000.0 - top), + time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), + kb_to_graph(top - bottom)); + bottom = top; + } + break; + } + + while ((cross_place = ps->sample->cross)) { + ps = ps->sample->cross->ps_new; + ps->sample = cross_place; + if (ps->sample->pss > (100 * arg_scale_y)) { + top = bottom + ps->sample->pss; + fprintf(of, " \n", + colorwheel[ps->pid % 12], + time_to_graph(prev_sampledata->sampletime - graph_start), + kb_to_graph(1000000.0 - top), + time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), + kb_to_graph(top - bottom)); + bottom = top; + } + } + + prev_sampledata = sampledata; + i++; + } + + /* overlay all the text labels */ + i = 1; + LIST_FOREACH_BEFORE(link, sampledata, head) { + int bottom; + int top = 0; + struct ps_sched_struct *prev_sample; + struct ps_sched_struct *cross_place; + + /* put all the small pss blocks into the bottom */ + ps = ps_first->next_ps; + while (ps->next_ps) { + ps = ps->next_ps; + if (!ps) + continue; + + ps->sample = ps->first; + while (ps->sample->next) { + ps->sample = ps->sample->next; + if (ps->sample->sampledata == sampledata) + break; + } + + if (ps->sample->sampledata == sampledata) { + if (ps->sample->pss <= (100 * arg_scale_y)) + top += ps->sample->pss; + + break; + } + } + + while ((cross_place = ps->sample->cross)) { + ps = ps->sample->cross->ps_new; + ps->sample = cross_place; + if (ps->sample->pss <= (100 * arg_scale_y)) + top += ps->sample->pss; + } + bottom = top; + + /* now plot the ones that are of significant size */ + ps = ps_first; + while (ps->next_ps) { + prev_sample = ps->sample; + ps = ps->next_ps; + if (!ps) + continue; + ps->sample = ps->first; + while (ps->sample->next) { + prev_sample = ps->sample; + ps->sample = ps->sample->next; + if (ps->sample->sampledata == sampledata) + break; + } + /* don't draw anything smaller than 2mb */ + if (ps->sample->sampledata == sampledata) { + if (ps->sample->pss > (100 * arg_scale_y)) { + top = bottom + ps->sample->pss; + /* draw a label with the process / PID */ + if ((i == 1) || (prev_sample->pss <= (100 * arg_scale_y))) + fprintf(of, " %s [%i]\n", + time_to_graph(sampledata->sampletime - graph_start), + kb_to_graph(1000000.0 - bottom - ((top - bottom) / 2)), + ps->name, ps->pid); + bottom = top; + } + break; + } + } + while ((cross_place = ps->sample->cross)) { + ps = ps->sample->cross->ps_new; + ps->sample = cross_place; + prev_sample = ps->sample->prev; + if (ps->sample->pss > (100 * arg_scale_y)) { + top = bottom + ps->sample->pss; + /* draw a label with the process / PID */ + if ((i == 1) || (prev_sample->pss <= (100 * arg_scale_y))) + fprintf(of, " %s [%i]\n", + time_to_graph(sampledata->sampletime - graph_start), + kb_to_graph(1000000.0 - bottom - ((top - bottom) / 2)), + ps->name, ps->pid); + bottom = top; + } + } + + i++; + } + + /* debug output - full data dump */ + fprintf(of, "\n\n\n"); + ps = ps_first; + while (ps->next_ps) { + _cleanup_free_ char *enc_name = NULL; + ps = ps->next_ps; + if (!ps) + continue; + + enc_name = xml_comment_encode(ps->name); + if (!enc_name) + continue; + + fprintf(of, "\n"); + } + +} + +static void svg_io_bi_bar(FILE *of, + struct list_sample_data *head, + int n_samples, + double graph_start, + double interval) { + + double max = 0.0; + double range; + int max_here = 0; + int i; + int k; + struct list_sample_data *start_sampledata; + struct list_sample_data *stop_sampledata; + + fprintf(of, "\n"); + fprintf(of, "IO utilization - read\n"); + + /* + * calculate rounding range + * + * We need to round IO data since IO block data is not updated on + * each poll. Applying a smoothing function loses some burst data, + * so keep the smoothing range short. + */ + range = 0.25 / (1.0 / arg_hz); + if (range < 2.0) + range = 2.0; /* no smoothing */ + + /* surrounding box */ + svg_graph_box(of, head, 5, graph_start); + + /* find the max IO first */ + i = 1; + LIST_FOREACH_BEFORE(link, sampledata, head) { + int start; + int stop; + int diff; + double tot; + + start = MAX(i - ((range / 2) - 1), 0); + stop = MIN(i + (range / 2), n_samples - 1); + diff = (stop - start); + + start_sampledata = sampledata; + stop_sampledata = sampledata; + + for (k = 0; k < ((range/2) - 1) && start_sampledata->link_next; k++) + start_sampledata = start_sampledata->link_next; + + for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) + stop_sampledata = stop_sampledata->link_prev; + + tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi) / diff; + + if (tot > max) { + max = tot; + max_here = i; + } + + tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo) / diff; + + if (tot > max) + max = tot; + + i++; + } + + /* plot bi */ + i = 1; + prev_sampledata = head; + LIST_FOREACH_BEFORE(link, sampledata, head) { + int start; + int stop; + int diff; + double tot; + double pbi = 0; + + start = MAX(i - ((range / 2) - 1), 0); + stop = MIN(i + (range / 2), n_samples); + diff = (stop - start); + + start_sampledata = sampledata; + stop_sampledata = sampledata; + + for (k = 0; k < ((range/2)-1) && start_sampledata->link_next; k++) + start_sampledata = start_sampledata->link_next; + + for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) + stop_sampledata = stop_sampledata->link_prev; + + tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi) / diff; + + if (max > 0) + pbi = tot / max; + + if (pbi > 0.001) + fprintf(of, "\n", + time_to_graph(prev_sampledata->sampletime - graph_start), + (arg_scale_y * 5) - (pbi * (arg_scale_y * 5)), + time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), + pbi * (arg_scale_y * 5)); + + /* labels around highest value */ + if (i == max_here) + fprintf(of, " %0.2fmb/sec\n", + time_to_graph(sampledata->sampletime - graph_start) + 5, + ((arg_scale_y * 5) - (pbi * (arg_scale_y * 5))) + 15, + max / 1024.0 / (interval / 1000000000.0)); + + i++; + prev_sampledata = sampledata; + } +} + +static void svg_io_bo_bar(FILE *of, + struct list_sample_data *head, + int n_samples, + double graph_start, + double interval) { + double max = 0.0; + double range; + int max_here = 0; + int i; + int k; + struct list_sample_data *start_sampledata; + struct list_sample_data *stop_sampledata; + + fprintf(of, "\n"); + fprintf(of, "IO utilization - write\n"); + + /* + * calculate rounding range + * + * We need to round IO data since IO block data is not updated on + * each poll. Applying a smoothing function loses some burst data, + * so keep the smoothing range short. + */ + range = 0.25 / (1.0 / arg_hz); + if (range < 2.0) + range = 2.0; /* no smoothing */ + + /* surrounding box */ + svg_graph_box(of, head, 5, graph_start); + + /* find the max IO first */ + i = 0; + LIST_FOREACH_BEFORE(link, sampledata, head) { + int start; + int stop; + int diff; + double tot; + + start = MAX(i - ((range / 2) - 1), 0); + stop = MIN(i + (range / 2), n_samples - 1); + diff = (stop - start); + + start_sampledata = sampledata; + stop_sampledata = sampledata; + + for (k = 0; k < (range/2) - 1 && start_sampledata->link_next; k++) + start_sampledata = start_sampledata->link_next; + + for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) + stop_sampledata = stop_sampledata->link_prev; + + tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi) / diff; + if (tot > max) + max = tot; + + tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo) / diff; + if (tot > max) { + max = tot; + max_here = i; + } + + i++; + } + + /* plot bo */ + prev_sampledata = head; + i = 1; + + LIST_FOREACH_BEFORE(link, sampledata, head) { + int start, stop, diff; + double tot, pbo; + + pbo = 0; + + start = MAX(i - ((range / 2) - 1), 0); + stop = MIN(i + (range / 2), n_samples); + diff = (stop - start); + + start_sampledata = sampledata; + stop_sampledata = sampledata; + + for (k = 0; k < ((range/2)-1) && start_sampledata->link_next; k++) + start_sampledata = start_sampledata->link_next; + + for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) + stop_sampledata = stop_sampledata->link_prev; + + tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo) + / diff; + + if (max > 0) + pbo = tot / max; + + if (pbo > 0.001) + fprintf(of, "\n", + time_to_graph(prev_sampledata->sampletime - graph_start), + (arg_scale_y * 5) - (pbo * (arg_scale_y * 5)), + time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), + pbo * (arg_scale_y * 5)); + + /* labels around highest bo value */ + if (i == max_here) + fprintf(of, " %0.2fmb/sec\n", + time_to_graph(sampledata->sampletime - graph_start) + 5, + ((arg_scale_y * 5) - (pbo * (arg_scale_y * 5))), + max / 1024.0 / (interval / 1000000000.0)); + + i++; + prev_sampledata = sampledata; + } +} + +static void svg_cpu_bar(FILE *of, struct list_sample_data *head, int n_cpus, int cpu_num, double graph_start) { + + fprintf(of, "\n"); + + if (cpu_num < 0) + fprintf(of, "CPU[overall] utilization\n"); + else + fprintf(of, "CPU[%d] utilization\n", cpu_num); + + /* surrounding box */ + svg_graph_box(of, head, 5, graph_start); + + /* bars for each sample, proportional to the CPU util. */ + prev_sampledata = head; + LIST_FOREACH_BEFORE(link, sampledata, head) { + int c; + double trt; + double ptrt; + + ptrt = trt = 0.0; + + if (cpu_num < 0) + for (c = 0; c < n_cpus; c++) + trt += sampledata->runtime[c] - prev_sampledata->runtime[c]; + else + trt = sampledata->runtime[cpu_num] - prev_sampledata->runtime[cpu_num]; + + trt = trt / 1000000000.0; + + if (cpu_num < 0) + trt = trt / (double)n_cpus; + + if (trt > 0.0) + ptrt = trt / (sampledata->sampletime - prev_sampledata->sampletime); + + if (ptrt > 1.0) + ptrt = 1.0; + + if (ptrt > 0.001) + fprintf(of, "\n", + time_to_graph(prev_sampledata->sampletime - graph_start), + (arg_scale_y * 5) - (ptrt * (arg_scale_y * 5)), + time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), + ptrt * (arg_scale_y * 5)); + + prev_sampledata = sampledata; + } +} + +static void svg_wait_bar(FILE *of, struct list_sample_data *head, int n_cpus, int cpu_num, double graph_start) { + + fprintf(of, "\n"); + + if (cpu_num < 0) + fprintf(of, "CPU[overall] wait\n"); + else + fprintf(of, "CPU[%d] wait\n", cpu_num); + + /* surrounding box */ + svg_graph_box(of, head, 5, graph_start); + + /* bars for each sample, proportional to the CPU util. */ + prev_sampledata = head; + LIST_FOREACH_BEFORE(link, sampledata, head) { + int c; + double twt; + double ptwt; + + ptwt = twt = 0.0; + + if (cpu_num < 0) + for (c = 0; c < n_cpus; c++) + twt += sampledata->waittime[c] - prev_sampledata->waittime[c]; + else + twt = sampledata->waittime[cpu_num] - prev_sampledata->waittime[cpu_num]; + + twt = twt / 1000000000.0; + + if (cpu_num < 0) + twt = twt / (double)n_cpus; + + if (twt > 0.0) + ptwt = twt / (sampledata->sampletime - prev_sampledata->sampletime); + + if (ptwt > 1.0) + ptwt = 1.0; + + if (ptwt > 0.001) + fprintf(of, "\n", + time_to_graph(prev_sampledata->sampletime - graph_start), + ((arg_scale_y * 5) - (ptwt * (arg_scale_y * 5))), + time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), + ptwt * (arg_scale_y * 5)); + + prev_sampledata = sampledata; + } +} + +static void svg_entropy_bar(FILE *of, struct list_sample_data *head, double graph_start) { + + fprintf(of, "\n"); + + fprintf(of, "Entropy pool size\n"); + /* surrounding box */ + svg_graph_box(of, head, 5, graph_start); + + /* bars for each sample, scale 0-4096 */ + prev_sampledata = head; + LIST_FOREACH_BEFORE(link, sampledata, head) { + fprintf(of, "\n", + time_to_graph(prev_sampledata->sampletime - graph_start), + ((arg_scale_y * 5) - ((sampledata->entropy_avail / 4096.) * (arg_scale_y * 5))), + time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), + (sampledata->entropy_avail / 4096.) * (arg_scale_y * 5)); + prev_sampledata = sampledata; + } +} + +static struct ps_struct *get_next_ps(struct ps_struct *ps, struct ps_struct *ps_first) { + /* + * walk the list of processes and return the next one to be + * painted + */ + if (ps == ps_first) + return ps->next_ps; + + /* go deep */ + if (ps->children) + return ps->children; + + /* find siblings */ + if (ps->next) + return ps->next; + + /* go back for parent siblings */ + for (;;) { + if (ps->parent && ps->parent->next) + return ps->parent->next; + + ps = ps->parent; + if (!ps) + return ps; + } + + return NULL; +} + +static bool ps_filter(struct ps_struct *ps) { + if (!arg_filter) + return false; + + /* can't draw data when there is only 1 sample (need start + stop) */ + if (ps->first == ps->last) + return true; + + /* don't filter kthreadd */ + if (ps->pid == 2) + return false; + + /* drop stuff that doesn't use any real CPU time */ + if (ps->total <= 0.001) + return true; + + return 0; +} + +static void svg_do_initcall(FILE *of, struct list_sample_data *head, int count_only, double graph_start) { + _cleanup_pclose_ FILE *f = NULL; + double t; + char func[256]; + int ret; + int usecs; + + /* can't plot initcall when disabled or in relative mode */ + if (!arg_initcall || arg_relative) { + kcount = 0; + return; + } + + if (!count_only) { + fprintf(of, "\n"); + fprintf(of, "Kernel init threads\n"); + /* surrounding box */ + svg_graph_box(of, head, kcount, graph_start); + } + + kcount = 0; + + /* + * Initcall graphing - parses dmesg buffer and displays kernel threads + * This somewhat uses the same methods and scaling to show processes + * but looks a lot simpler. It's overlaid entirely onto the PS graph + * when appropriate. + */ + + f = popen("dmesg", "r"); + if (!f) + return; + + while (!feof(f)) { + int c; + int z = 0; + char l[256]; + + if (fgets(l, sizeof(l) - 1, f) == NULL) + continue; + + c = sscanf(l, "[%lf] initcall %s %*s %d %*s %d %*s", + &t, func, &ret, &usecs); + if (c != 4) { + /* also parse initcalls done by module loading */ + c = sscanf(l, "[%lf] initcall %s %*s %*s %d %*s %d %*s", + &t, func, &ret, &usecs); + if (c != 4) + continue; + } + + /* chop the +0xXX/0xXX stuff */ + while(func[z] != '+') + z++; + func[z] = 0; + + if (count_only) { + /* filter out irrelevant stuff */ + if (usecs >= 1000) + kcount++; + continue; + } + + fprintf(of, "\n", + func, t, usecs, ret); + + if (usecs < 1000) + continue; + + /* rect */ + fprintf(of, " \n", + time_to_graph(t - (usecs / 1000000.0)), + ps_to_graph(kcount), + time_to_graph(usecs / 1000000.0), + ps_to_graph(1)); + + /* label */ + fprintf(of, " %s %.03fs\n", + time_to_graph(t - (usecs / 1000000.0)) + 5, + ps_to_graph(kcount) + 15, + func, usecs / 1000000.0); + + kcount++; + } +} + +static void svg_ps_bars(FILE *of, + struct list_sample_data *head, + int n_samples, + int n_cpus, + struct ps_struct *ps_first, + double graph_start, + double interval) { + + struct ps_struct *ps; + int i = 0; + int j = 0; + int pid; + double w = 0.0; + + fprintf(of, "\n"); + fprintf(of, "Processes\n"); + + /* surrounding box */ + svg_graph_box(of, head, pcount, graph_start); + + /* pass 2 - ps boxes */ + ps = ps_first; + while ((ps = get_next_ps(ps, ps_first))) { + _cleanup_free_ char *enc_name = NULL, *escaped = NULL; + double endtime; + double starttime; + int t; + + if (!utf8_is_printable(ps->name, strlen(ps->name))) + escaped = utf8_escape_non_printable(ps->name); + + enc_name = xml_comment_encode(escaped ? escaped : ps->name); + if (!enc_name) + continue; + + /* leave some trace of what we actually filtered etc. */ + fprintf(of, "\n", enc_name, ps->pid, + ps->ppid, ps->total); + + starttime = ps->first->sampledata->sampletime; + + if (!ps_filter(ps)) { + /* remember where _to_ our children need to draw a line */ + ps->pos_x = time_to_graph(starttime - graph_start); + ps->pos_y = ps_to_graph(j+1); /* bottom left corner */ + } else if (ps->parent){ + /* hook children to our parent coords instead */ + ps->pos_x = ps->parent->pos_x; + ps->pos_y = ps->parent->pos_y; + + /* if this is the last child, we might still need to draw a connecting line */ + if ((!ps->next) && (ps->parent)) + fprintf(of, " \n", + ps->parent->pos_x, + ps_to_graph(j-1) + 10.0, /* whee, use the last value here */ + ps->parent->pos_x, + ps->parent->pos_y); + continue; + } + + endtime = ps->last->sampledata->sampletime; + fprintf(of, " \n", + time_to_graph(starttime - graph_start), + ps_to_graph(j), + time_to_graph(ps->last->sampledata->sampletime - starttime), + ps_to_graph(1)); + + /* paint cpu load over these */ + ps->sample = ps->first; + t = 1; + while (ps->sample->next) { + double rt, prt; + double wt, wrt; + struct ps_sched_struct *prev; + + prev = ps->sample; + ps->sample = ps->sample->next; + + /* calculate over interval */ + rt = ps->sample->runtime - prev->runtime; + wt = ps->sample->waittime - prev->waittime; + + prt = (rt / 1000000000) / (ps->sample->sampledata->sampletime - prev->sampledata->sampletime); + wrt = (wt / 1000000000) / (ps->sample->sampledata->sampletime - prev->sampledata->sampletime); + + /* this can happen if timekeeping isn't accurate enough */ + if (prt > 1.0) + prt = 1.0; + if (wrt > 1.0) + wrt = 1.0; + + if ((prt < 0.1) && (wrt < 0.1)) /* =~ 26 (color threshold) */ + continue; + + fprintf(of, " \n", + time_to_graph(prev->sampledata->sampletime - graph_start), + ps_to_graph(j), + time_to_graph(ps->sample->sampledata->sampletime - prev->sampledata->sampletime), + ps_to_graph(wrt)); + + /* draw cpu over wait - TODO figure out how/why run + wait > interval */ + fprintf(of, " \n", + time_to_graph(prev->sampledata->sampletime - graph_start), + ps_to_graph(j + (1.0 - prt)), + time_to_graph(ps->sample->sampledata->sampletime - prev->sampledata->sampletime), + ps_to_graph(prt)); + t++; + } + + /* determine where to display the process name */ + if ((endtime - starttime) < 1.5) + /* too small to fit label inside the box */ + w = endtime; + else + w = starttime; + + /* text label of process name */ + fprintf(of, " %s [%i]%.03fs %s\n", + time_to_graph(w - graph_start) + 5.0, + ps_to_graph(j) + 14.0, + escaped ? escaped : ps->name, + ps->pid, + (ps->last->runtime - ps->first->runtime) / 1000000000.0, + arg_show_cgroup ? ps->cgroup : ""); + /* paint lines to the parent process */ + if (ps->parent) { + /* horizontal part */ + fprintf(of, " \n", + time_to_graph(starttime - graph_start), + ps_to_graph(j) + 10.0, + ps->parent->pos_x, + ps_to_graph(j) + 10.0); + + /* one vertical line connecting all the horizontal ones up */ + if (!ps->next) + fprintf(of, " \n", + ps->parent->pos_x, + ps_to_graph(j) + 10.0, + ps->parent->pos_x, + ps->parent->pos_y); + } + + j++; /* count boxes */ + + fprintf(of, "\n"); + } + + /* last pass - determine when idle */ + pid = getpid(); + /* make sure we start counting from the point where we actually have + * data: assume that bootchart's first sample is when data started + */ + + ps = ps_first; + while (ps->next_ps) { + ps = ps->next_ps; + if (ps->pid == pid) + break; + } + + /* need to know last node first */ + ps->sample = ps->first; + i = ps->sample->next->sampledata->counter; + + while (ps->sample->next && i<(n_samples-(arg_hz/2))) { + double crt; + double brt; + int c; + int ii; + struct ps_sched_struct *sample_hz; + + ps->sample = ps->sample->next; + sample_hz = ps->sample; + for (ii = 0; (ii < (int)arg_hz/2) && sample_hz->next; ii++) + sample_hz = sample_hz->next; + + /* subtract bootchart cpu utilization from total */ + crt = 0.0; + for (c = 0; c < n_cpus; c++) + crt += sample_hz->sampledata->runtime[c] - ps->sample->sampledata->runtime[c]; + + brt = sample_hz->runtime - ps->sample->runtime; + /* + * our definition of "idle": + * + * if for (hz / 2) we've used less CPU than (interval / 2) ... + * defaults to 4.0%, which experimentally, is where atom idles + */ + if ((crt - brt) < (interval / 2.0)) { + idletime = ps->sample->sampledata->sampletime - graph_start; + fprintf(of, "\n\n", idletime); + fprintf(of, "\n", + time_to_graph(idletime), + -arg_scale_y, + time_to_graph(idletime), + ps_to_graph(pcount) + arg_scale_y); + fprintf(of, "%.01fs\n", + time_to_graph(idletime) + 5.0, + ps_to_graph(pcount) + arg_scale_y, + idletime); + break; + } + + i++; + } +} + +static void svg_top_ten_cpu(FILE *of, struct ps_struct *ps_first) { + struct ps_struct *top[10]; + struct ps_struct emptyps = {}; + struct ps_struct *ps; + int n, m; + + for (n = 0; n < (int) ELEMENTSOF(top); n++) + top[n] = &emptyps; + + /* walk all ps's and setup ptrs */ + ps = ps_first; + while ((ps = get_next_ps(ps, ps_first))) { + for (n = 0; n < 10; n++) { + if (ps->total <= top[n]->total) + continue; + /* cascade insert */ + for (m = 9; m > n; m--) + top[m] = top[m-1]; + top[n] = ps; + break; + } + } + + fprintf(of, "Top CPU consumers:\n"); + for (n = 0; n < 10; n++) + fprintf(of, "%3.03fs - %s [%d]\n", + 20 + (n * 13), + top[n]->total, + top[n]->name, + top[n]->pid); +} + +static void svg_top_ten_pss(FILE *of, struct ps_struct *ps_first) { + struct ps_struct *top[10]; + struct ps_struct emptyps = {}; + struct ps_struct *ps; + int n, m; + + for (n = 0; n < (int) ELEMENTSOF(top); n++) + top[n] = &emptyps; + + /* walk all ps's and setup ptrs */ + ps = ps_first; + while ((ps = get_next_ps(ps, ps_first))) { + for (n = 0; n < 10; n++) { + if (ps->pss_max <= top[n]->pss_max) + continue; + + /* cascade insert */ + for (m = 9; m > n; m--) + top[m] = top[m-1]; + top[n] = ps; + break; + } + } + + fprintf(of, "Top PSS consumers:\n"); + for (n = 0; n < 10; n++) + fprintf(of, "%dK - %s [%d]\n", + 20 + (n * 13), + top[n]->pss_max, + top[n]->name, + top[n]->pid); +} + +int svg_do(FILE *of, + const char *build, + struct list_sample_data *head, + struct ps_struct *ps_first, + int n_samples, + int pscount, + int n_cpus, + double graph_start, + double log_start, + double interval, + int overrun) { + + struct ps_struct *ps; + double offset = 7; + int r, c; + + sampledata = head; + LIST_FIND_TAIL(link, sampledata, head); + ps = ps_first; + + /* count initcall thread count first */ + svg_do_initcall(of, head, 1, graph_start); + ksize = kcount ? ps_to_graph(kcount) + (arg_scale_y * 2) : 0; + + /* then count processes */ + while ((ps = get_next_ps(ps, ps_first))) { + if (!ps_filter(ps)) + pcount++; + else + pfiltered++; + } + psize = ps_to_graph(pcount) + (arg_scale_y * 2); + + esize = (arg_entropy ? arg_scale_y * 7 : 0); + + /* after this, we can draw the header with proper sizing */ + svg_header(of, head, graph_start, arg_percpu ? n_cpus : 0); + fprintf(of, "\n\n"); + + fprintf(of, "\n"); + svg_io_bi_bar(of, head, n_samples, graph_start, interval); + fprintf(of, "\n\n"); + + fprintf(of, "\n", 400.0 + (arg_scale_y * offset)); + svg_io_bo_bar(of, head, n_samples, graph_start, interval); + fprintf(of, "\n\n"); + + for (c = -1; c < (arg_percpu ? n_cpus : 0); c++) { + offset += 7; + fprintf(of, "\n", 400.0 + (arg_scale_y * offset)); + svg_cpu_bar(of, head, n_cpus, c, graph_start); + fprintf(of, "\n\n"); + + offset += 7; + fprintf(of, "\n", 400.0 + (arg_scale_y * offset)); + svg_wait_bar(of, head, n_cpus, c, graph_start); + fprintf(of, "\n\n"); + } + + if (kcount) { + offset += 7; + fprintf(of, "\n", 400.0 + (arg_scale_y * offset)); + svg_do_initcall(of, head, 0, graph_start); + fprintf(of, "\n\n"); + } + + offset += 7; + fprintf(of, "\n", 400.0 + (arg_scale_y * offset) + ksize); + svg_ps_bars(of, head, n_samples, n_cpus, ps_first, graph_start, interval); + fprintf(of, "\n\n"); + + fprintf(of, "\n"); + r = svg_title(of, build, pscount, log_start, overrun); + fprintf(of, "\n\n"); + + if (r < 0) + return r; + + fprintf(of, "\n"); + svg_top_ten_cpu(of, ps_first); + fprintf(of, "\n\n"); + + if (arg_entropy) { + fprintf(of, "\n", 400.0 + (arg_scale_y * offset) + ksize + psize); + svg_entropy_bar(of, head, graph_start); + fprintf(of, "\n\n"); + } + + if (arg_pss) { + fprintf(of, "\n", 400.0 + (arg_scale_y * offset) + ksize + psize + esize); + svg_pss_graph(of, head, ps_first, graph_start); + fprintf(of, "\n\n"); + + fprintf(of, "\n"); + svg_top_ten_pss(of, ps_first); + fprintf(of, "\n\n"); + } + + /* fprintf footer */ + fprintf(of, "\n\n"); + + return 0; +} diff --git a/src/systemd-bootchart/svg.h b/src/systemd-bootchart/svg.h new file mode 100644 index 0000000000..6e06b5ad97 --- /dev/null +++ b/src/systemd-bootchart/svg.h @@ -0,0 +1,35 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2009-2013 Intel Corporation + + Authors: + Auke Kok + + 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 . +***/ + +int svg_do(FILE *of, + const char *build, + struct list_sample_data *head, + struct ps_struct *ps_first, + int n_samples, + int pscount, + int n_cpus, + double graph_start, + double log_start, + double interval, + int overrun); diff --git a/src/systemd/Makefile b/src/systemd/Makefile deleted file mode 100644 index e873ba3f94..0000000000 --- a/src/systemd/Makefile +++ /dev/null @@ -1,63 +0,0 @@ -# -*- Mode: makefile; indent-tabs-mode: t -*- -# -# This file is part of systemd. -# -# Copyright 2010-2012 Lennart Poettering -# Copyright 2010-2012 Kay Sievers -# Copyright 2013 Zbigniew Jędrzejewski-Szmek -# Copyright 2013 David Strauss -# Copyright 2016 Luke Shumaker -# -# 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 . - -systemd_SOURCES = \ - src/core/main.c - -systemd_CFLAGS = \ - $(AM_CFLAGS) \ - $(SECCOMP_CFLAGS) \ - $(MOUNT_CFLAGS) - -systemd_LDADD = \ - libcore.la - -dist_pkgsysconf_DATA += \ - src/core/system.conf \ - src/core/user.conf - -dist_dbuspolicy_DATA += \ - src/core/org.freedesktop.systemd1.conf - -dist_dbussystemservice_DATA += \ - src/core/org.freedesktop.systemd1.service - -polkitpolicy_in_in_files += \ - src/core/org.freedesktop.systemd1.policy.in.in - -pkgconfigdata_DATA += \ - src/core/systemd.pc - -nodist_rpmmacros_DATA = \ - src/core/macros.systemd - -EXTRA_DIST += \ - src/core/systemd.pc.in \ - src/core/macros.systemd.in \ - src/core/triggers.systemd.in - -dist_systemunit_DATA_busnames += \ - units/org.freedesktop.systemd1.busname - -BUSNAMES_TARGET_WANTS += \ - org.freedesktop.systemd1.busname diff --git a/src/systemd/macros.systemd.in b/src/systemd/macros.systemd.in deleted file mode 100644 index 2cace3d3ba..0000000000 --- a/src/systemd/macros.systemd.in +++ /dev/null @@ -1,105 +0,0 @@ -# -*- Mode: rpm-spec; indent-tabs-mode: nil -*- */ -# -# This file is part of systemd. -# -# Copyright 2012 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 . - -# RPM macros for packages installing systemd unit files - -%_unitdir @systemunitdir@ -%_userunitdir @userunitdir@ -%_presetdir @systempresetdir@ -%_udevhwdbdir @udevhwdbdir@ -%_udevrulesdir @udevrulesdir@ -%_journalcatalogdir @catalogdir@ -%_tmpfilesdir @tmpfilesdir@ -%_sysusersdir @sysusersdir@ -%_sysctldir @sysctldir@ -%_binfmtdir @binfmtdir@ - -%systemd_requires \ -Requires(post): systemd \ -Requires(preun): systemd \ -Requires(postun): systemd \ -%{nil} - -%systemd_post() \ -if [ $1 -eq 1 ] ; then \ - # Initial installation \ - systemctl --no-reload preset %{?*} >/dev/null 2>&1 || : \ -fi \ -%{nil} - -%systemd_user_post() %{expand:%systemd_post \\--user \\--global %%{?*}} - -%systemd_preun() \ -if [ $1 -eq 0 ] ; then \ - # Package removal, not upgrade \ - systemctl --no-reload disable --now %{?*} > /dev/null 2>&1 || : \ -fi \ -%{nil} - -%systemd_user_preun() \ -if [ $1 -eq 0 ] ; then \ - # Package removal, not upgrade \ - systemctl --no-reload --user --global disable %{?*} > /dev/null 2>&1 || : \ -fi \ -%{nil} - -%systemd_postun() %{nil} - -%systemd_user_postun() %{nil} - -%systemd_postun_with_restart() \ -if [ $1 -ge 1 ] ; then \ - # Package upgrade, not uninstall \ - systemctl try-restart %{?*} >/dev/null 2>&1 || : \ -fi \ -%{nil} - -%systemd_user_postun_with_restart() %{nil} - -%udev_hwdb_update() \ -udevadm hwdb --update >/dev/null 2>&1 || : \ -%{nil} - -%udev_rules_update() \ -udevadm control --reload >/dev/null 2>&1 || : \ -%{nil} - -%journal_catalog_update() \ -journalctl --update-catalog >/dev/null 2>&1 || : \ -%{nil} - -%tmpfiles_create() \ -systemd-tmpfiles --create %{?*} >/dev/null 2>&1 || : \ -%{nil} - -%sysusers_create() \ -systemd-sysusers %{?*} >/dev/null 2>&1 || : \ -%{nil} - -%sysusers_create_inline() \ -echo %{?*} | systemd-sysusers - >/dev/null 2>&1 || : \ -%{nil} - -%sysctl_apply() \ -@rootlibexecdir@/systemd-sysctl %{?*} >/dev/null 2>&1 || : \ -%{nil} - -%binfmt_apply() \ -@rootlibexecdir@/systemd-binfmt %{?*} >/dev/null 2>&1 || : \ -%{nil} diff --git a/src/systemd/main.c b/src/systemd/main.c deleted file mode 100644 index 5346392d73..0000000000 --- a/src/systemd/main.c +++ /dev/null @@ -1,2142 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_SECCOMP -#include -#endif -#ifdef HAVE_VALGRIND_VALGRIND_H -#include -#endif - -#include "sd-bus.h" -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "architecture.h" -#include "build.h" -#include "bus-error.h" -#include "bus-util.h" -#include "capability-util.h" -#include "clock-util.h" -#include "conf-parser.h" -#include "cpu-set-util.h" -#include "dbus-manager.h" -#include "def.h" -#include "env-util.h" -#include "fd-util.h" -#include "fdset.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "hostname-setup.h" -#include "ima-setup.h" -#include "killall.h" -#include "kmod-setup.h" -#include "load-fragment.h" -#include "log.h" -#include "loopback-setup.h" -#include "machine-id-setup.h" -#include "manager.h" -#include "missing.h" -#include "mount-setup.h" -#include "pager.h" -#include "parse-util.h" -#include "proc-cmdline.h" -#include "process-util.h" -#include "rlimit-util.h" -#include "selinux-setup.h" -#include "selinux-util.h" -#include "signal-util.h" -#include "smack-setup.h" -#include "special.h" -#include "stat-util.h" -#include "stdio-util.h" -#include "strv.h" -#include "switch-root.h" -#include "terminal-util.h" -#include "user-util.h" -#include "virt.h" -#include "watchdog.h" - -static enum { - ACTION_RUN, - ACTION_HELP, - ACTION_VERSION, - ACTION_TEST, - ACTION_DUMP_CONFIGURATION_ITEMS, - ACTION_DONE -} arg_action = ACTION_RUN; -static char *arg_default_unit = NULL; -static ManagerRunningAs arg_running_as = _MANAGER_RUNNING_AS_INVALID; -static bool arg_dump_core = true; -static int arg_crash_chvt = -1; -static bool arg_crash_shell = false; -static bool arg_crash_reboot = false; -static bool arg_confirm_spawn = false; -static ShowStatus arg_show_status = _SHOW_STATUS_UNSET; -static bool arg_switched_root = false; -static int arg_no_pager = -1; -static char ***arg_join_controllers = NULL; -static ExecOutput arg_default_std_output = EXEC_OUTPUT_JOURNAL; -static ExecOutput arg_default_std_error = EXEC_OUTPUT_INHERIT; -static usec_t arg_default_restart_usec = DEFAULT_RESTART_USEC; -static usec_t arg_default_timeout_start_usec = DEFAULT_TIMEOUT_USEC; -static usec_t arg_default_timeout_stop_usec = DEFAULT_TIMEOUT_USEC; -static usec_t arg_default_start_limit_interval = DEFAULT_START_LIMIT_INTERVAL; -static unsigned arg_default_start_limit_burst = DEFAULT_START_LIMIT_BURST; -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 = 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; -static FILE* arg_serialization = NULL; -static bool arg_default_cpu_accounting = false; -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) { - - if (arg_no_pager <= 0) - return; - - pager_open(false); -} - -noreturn static void freeze_or_reboot(void) { - - if (arg_crash_reboot) { - log_notice("Rebooting in 10s..."); - (void) sleep(10); - - log_notice("Rebooting now..."); - (void) reboot(RB_AUTOBOOT); - log_emergency_errno(errno, "Failed to reboot: %m"); - } - - log_emergency("Freezing execution."); - freeze(); -} - -noreturn static void crash(int sig) { - struct sigaction sa; - pid_t pid; - - if (getpid() != 1) - /* Pass this on immediately, if this is not PID 1 */ - (void) raise(sig); - else if (!arg_dump_core) - log_emergency("Caught <%s>, not dumping core.", signal_to_string(sig)); - else { - sa = (struct sigaction) { - .sa_handler = nop_signal_handler, - .sa_flags = SA_NOCLDSTOP|SA_RESTART, - }; - - /* We want to wait for the core process, hence let's enable SIGCHLD */ - (void) sigaction(SIGCHLD, &sa, NULL); - - pid = raw_clone(SIGCHLD, NULL); - if (pid < 0) - log_emergency_errno(errno, "Caught <%s>, cannot fork for core dump: %m", signal_to_string(sig)); - else if (pid == 0) { - /* Enable default signal handler for core dump */ - - sa = (struct sigaction) { - .sa_handler = SIG_DFL, - }; - (void) sigaction(sig, &sa, NULL); - - /* Don't limit the coredump size */ - (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY)); - - /* Just to be sure... */ - (void) chdir("/"); - - /* Raise the signal again */ - pid = raw_getpid(); - (void) kill(pid, sig); /* raise() would kill the parent */ - - assert_not_reached("We shouldn't be here..."); - _exit(EXIT_FAILURE); - } else { - siginfo_t status; - int r; - - /* Order things nicely. */ - r = wait_for_terminate(pid, &status); - if (r < 0) - log_emergency_errno(r, "Caught <%s>, waitpid() failed: %m", signal_to_string(sig)); - else if (status.si_code != CLD_DUMPED) - log_emergency("Caught <%s>, core dump failed (child "PID_FMT", code=%s, status=%i/%s).", - signal_to_string(sig), - pid, sigchld_code_to_string(status.si_code), - status.si_status, - strna(status.si_code == CLD_EXITED - ? exit_status_to_string(status.si_status, EXIT_STATUS_FULL) - : signal_to_string(status.si_status))); - else - log_emergency("Caught <%s>, dumped core as pid "PID_FMT".", signal_to_string(sig), pid); - } - } - - if (arg_crash_chvt >= 0) - (void) chvt(arg_crash_chvt); - - sa = (struct sigaction) { - .sa_handler = SIG_IGN, - .sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT|SA_RESTART, - }; - - /* Let the kernel reap children for us */ - (void) sigaction(SIGCHLD, &sa, NULL); - - if (arg_crash_shell) { - log_notice("Executing crash shell in 10s..."); - (void) sleep(10); - - pid = raw_clone(SIGCHLD, NULL); - if (pid < 0) - log_emergency_errno(errno, "Failed to fork off crash shell: %m"); - else if (pid == 0) { - (void) setsid(); - (void) make_console_stdio(); - (void) execle("/bin/sh", "/bin/sh", NULL, environ); - - log_emergency_errno(errno, "execle() failed: %m"); - _exit(EXIT_FAILURE); - } else { - log_info("Spawned crash shell as PID "PID_FMT".", pid); - (void) wait_for_terminate(pid, NULL); - } - } - - freeze_or_reboot(); -} - -static void install_crash_handler(void) { - static const struct sigaction sa = { - .sa_handler = crash, - .sa_flags = SA_NODEFER, /* So that we can raise the signal again from the signal handler */ - }; - int r; - - /* We ignore the return value here, since, we don't mind if we - * cannot set up a crash handler */ - r = sigaction_many(&sa, SIGNALS_CRASH_HANDLER, -1); - if (r < 0) - log_debug_errno(r, "I had trouble setting up the crash handler, ignoring: %m"); -} - -static int console_setup(void) { - _cleanup_close_ int tty_fd = -1; - int r; - - tty_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); - if (tty_fd < 0) - return log_error_errno(tty_fd, "Failed to open /dev/console: %m"); - - /* We don't want to force text mode. plymouth may be showing - * pictures already from initrd. */ - r = reset_terminal_fd(tty_fd, false); - if (r < 0) - return log_error_errno(r, "Failed to reset /dev/console: %m"); - - return 0; -} - -static int parse_crash_chvt(const char *value) { - int b; - - if (safe_atoi(value, &arg_crash_chvt) >= 0) - return 0; - - b = parse_boolean(value); - if (b < 0) - return b; - - if (b > 0) - arg_crash_chvt = 0; /* switch to where kmsg goes */ - else - arg_crash_chvt = -1; /* turn off switching */ - - 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; - - assert(key); - - if (streq(key, "systemd.unit") && value) { - - if (!in_initrd()) - return free_and_strdup(&arg_default_unit, value); - - } else if (streq(key, "rd.systemd.unit") && value) { - - if (in_initrd()) - return free_and_strdup(&arg_default_unit, value); - - } else if (streq(key, "systemd.dump_core") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse dump core switch %s. Ignoring.", value); - else - arg_dump_core = r; - - } else if (streq(key, "systemd.crash_chvt") && value) { - - if (parse_crash_chvt(value) < 0) - log_warning("Failed to parse crash chvt switch %s. Ignoring.", value); - - } else if (streq(key, "systemd.crash_shell") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse crash shell switch %s. Ignoring.", value); - else - arg_crash_shell = r; - - } else if (streq(key, "systemd.crash_reboot") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse crash reboot switch %s. Ignoring.", value); - else - arg_crash_reboot = r; - - } else if (streq(key, "systemd.confirm_spawn") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse confirm spawn switch %s. Ignoring.", value); - else - arg_confirm_spawn = r; - - } else if (streq(key, "systemd.show_status") && value) { - - r = parse_show_status(value, &arg_show_status); - if (r < 0) - log_warning("Failed to parse show status switch %s. Ignoring.", value); - - } else if (streq(key, "systemd.default_standard_output") && value) { - - r = exec_output_from_string(value); - if (r < 0) - log_warning("Failed to parse default standard output switch %s. Ignoring.", value); - else - arg_default_std_output = r; - - } else if (streq(key, "systemd.default_standard_error") && value) { - - r = exec_output_from_string(value); - if (r < 0) - log_warning("Failed to parse default standard error switch %s. Ignoring.", value); - else - arg_default_std_error = r; - - } else if (streq(key, "systemd.setenv") && value) { - - if (env_assignment_is_valid(value)) { - char **env; - - env = strv_env_set(arg_default_environment, value); - if (env) - arg_default_environment = env; - else - log_warning_errno(ENOMEM, "Setting environment variable '%s' failed, ignoring: %m", 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) - arg_show_status = SHOW_STATUS_AUTO; - - } else if (streq(key, "debug") && !value) { - - /* Note that log_parse_environment() handles 'debug' - * too, and sets the log level to LOG_DEBUG. */ - - if (detect_container() > 0) - log_set_target(LOG_TARGET_CONSOLE); - - } else if (!in_initrd() && !value) { - const char *target; - - /* SysV compatibility */ - target = runlevel_to_target(key); - if (target) - return free_and_strdup(&arg_default_unit, target); - } - - return 0; -} - -#define DEFINE_SETTER(name, func, descr) \ - static int name(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 r; \ - \ - assert(filename); \ - assert(lvalue); \ - assert(rvalue); \ - \ - r = func(rvalue); \ - if (r < 0) \ - log_syntax(unit, LOG_ERR, filename, line, r, \ - "Invalid " descr "'%s': %m", \ - rvalue); \ - \ - return 0; \ - } - -DEFINE_SETTER(config_parse_level2, log_set_max_level_from_string, "log level") -DEFINE_SETTER(config_parse_target, log_set_target_from_string, "target") -DEFINE_SETTER(config_parse_color, log_show_color_from_string, "color" ) -DEFINE_SETTER(config_parse_location, log_show_location_from_string, "location") - -static int config_parse_cpu_affinity2( - 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_cpu_free_ cpu_set_t *c = NULL; - int ncpus; - - ncpus = parse_cpu_set_and_warn(rvalue, &c, unit, filename, line, lvalue); - if (ncpus < 0) - return ncpus; - - if (sched_setaffinity(0, CPU_ALLOC_SIZE(ncpus), c) < 0) - log_warning("Failed to set CPU affinity: %m"); - - return 0; -} - -static int config_parse_show_status( - 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 k; - ShowStatus *b = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - k = parse_show_status(rvalue, b); - if (k < 0) { - log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse show status setting, ignoring: %s", rvalue); - return 0; - } - - return 0; -} - -static int config_parse_crash_chvt( - 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 r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = parse_crash_chvt(rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CrashChangeVT= setting, ignoring: %s", rvalue); - return 0; - } - - return 0; -} - -static int config_parse_join_controllers(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 *whole_rvalue = rvalue; - unsigned n = 0; - - assert(filename); - assert(lvalue); - assert(rvalue); - - arg_join_controllers = strv_free_free(arg_join_controllers); - - for (;;) { - _cleanup_free_ char *word = NULL; - char **l; - int r; - - r = extract_first_word(&rvalue, &word, WHITESPACE, EXTRACT_QUOTES); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Invalid value for %s: %s", lvalue, whole_rvalue); - return r; - } - if (r == 0) - break; - - l = strv_split(word, ","); - if (!l) - return log_oom(); - strv_uniq(l); - - if (strv_length(l) <= 1) { - strv_free(l); - continue; - } - - if (!arg_join_controllers) { - arg_join_controllers = new(char**, 2); - if (!arg_join_controllers) { - strv_free(l); - return log_oom(); - } - - arg_join_controllers[0] = l; - arg_join_controllers[1] = NULL; - - n = 1; - } else { - char ***a; - char ***t; - - t = new0(char**, n+2); - if (!t) { - strv_free(l); - return log_oom(); - } - - n = 0; - - for (a = arg_join_controllers; *a; a++) { - - if (strv_overlap(*a, l)) { - if (strv_extend_strv(&l, *a, false) < 0) { - strv_free(l); - strv_free_free(t); - return log_oom(); - } - - } else { - char **c; - - c = strv_copy(*a); - if (!c) { - strv_free(l); - strv_free_free(t); - return log_oom(); - } - - t[n++] = c; - } - } - - t[n++] = strv_uniq(l); - - strv_free_free(arg_join_controllers); - arg_join_controllers = t; - } - } - if (!isempty(rvalue)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - return 0; -} - -static int parse_config_file(void) { - - const ConfigTableItem items[] = { - { "Manager", "LogLevel", config_parse_level2, 0, NULL }, - { "Manager", "LogTarget", config_parse_target, 0, NULL }, - { "Manager", "LogColor", config_parse_color, 0, NULL }, - { "Manager", "LogLocation", config_parse_location, 0, NULL }, - { "Manager", "DumpCore", config_parse_bool, 0, &arg_dump_core }, - { "Manager", "CrashChVT", /* legacy */ config_parse_crash_chvt, 0, NULL }, - { "Manager", "CrashChangeVT", config_parse_crash_chvt, 0, NULL }, - { "Manager", "CrashShell", config_parse_bool, 0, &arg_crash_shell }, - { "Manager", "CrashReboot", config_parse_bool, 0, &arg_crash_reboot }, - { "Manager", "ShowStatus", config_parse_show_status, 0, &arg_show_status }, - { "Manager", "CPUAffinity", config_parse_cpu_affinity2, 0, NULL }, - { "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_capability_set, 0, &arg_capability_bounding_set }, -#ifdef HAVE_SECCOMP - { "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs }, -#endif - { "Manager", "TimerSlackNSec", config_parse_nsec, 0, &arg_timer_slack_nsec }, - { "Manager", "DefaultTimerAccuracySec", config_parse_sec, 0, &arg_default_timer_accuracy_usec }, - { "Manager", "DefaultStandardOutput", config_parse_output, 0, &arg_default_std_output }, - { "Manager", "DefaultStandardError", config_parse_output, 0, &arg_default_std_error }, - { "Manager", "DefaultTimeoutStartSec", config_parse_sec, 0, &arg_default_timeout_start_usec }, - { "Manager", "DefaultTimeoutStopSec", config_parse_sec, 0, &arg_default_timeout_stop_usec }, - { "Manager", "DefaultRestartSec", config_parse_sec, 0, &arg_default_restart_usec }, - { "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_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 }, - { "Manager", "DefaultTasksAccounting", config_parse_bool, 0, &arg_default_tasks_accounting }, - { "Manager", "DefaultTasksMax", config_parse_tasks_max, 0, &arg_default_tasks_max }, - {} - }; - - const char *fn, *conf_dirs_nulstr; - - fn = arg_running_as == MANAGER_SYSTEM ? - PKGSYSCONFDIR "/system.conf" : - PKGSYSCONFDIR "/user.conf"; - - conf_dirs_nulstr = arg_running_as == MANAGER_SYSTEM ? - 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); - - /* 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; -} - -static void manager_set_defaults(Manager *m) { - - assert(m); - - m->default_timer_accuracy_usec = arg_default_timer_accuracy_usec; - m->default_std_output = arg_default_std_output; - m->default_std_error = arg_default_std_error; - m->default_timeout_start_usec = arg_default_timeout_start_usec; - m->default_timeout_stop_usec = arg_default_timeout_stop_usec; - m->default_restart_usec = arg_default_restart_usec; - m->default_start_limit_interval = arg_default_start_limit_interval; - m->default_start_limit_burst = arg_default_start_limit_burst; - m->default_cpu_accounting = arg_default_cpu_accounting; - m->default_blockio_accounting = arg_default_blockio_accounting; - m->default_memory_accounting = arg_default_memory_accounting; - m->default_tasks_accounting = arg_default_tasks_accounting; - m->default_tasks_max = arg_default_tasks_max; - - manager_set_default_rlimits(m, arg_default_rlimit); - manager_environment_add(m, NULL, arg_default_environment); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_LOG_LEVEL = 0x100, - ARG_LOG_TARGET, - ARG_LOG_COLOR, - ARG_LOG_LOCATION, - ARG_UNIT, - ARG_SYSTEM, - ARG_USER, - ARG_TEST, - ARG_NO_PAGER, - ARG_VERSION, - ARG_DUMP_CONFIGURATION_ITEMS, - ARG_DUMP_CORE, - ARG_CRASH_CHVT, - ARG_CRASH_SHELL, - ARG_CRASH_REBOOT, - ARG_CONFIRM_SPAWN, - ARG_SHOW_STATUS, - ARG_DESERIALIZE, - ARG_SWITCHED_ROOT, - ARG_DEFAULT_STD_OUTPUT, - ARG_DEFAULT_STD_ERROR, - ARG_MACHINE_ID - }; - - static const struct option options[] = { - { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, - { "log-target", required_argument, NULL, ARG_LOG_TARGET }, - { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, - { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, - { "unit", required_argument, NULL, ARG_UNIT }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "test", no_argument, NULL, ARG_TEST }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS }, - { "dump-core", optional_argument, NULL, ARG_DUMP_CORE }, - { "crash-chvt", required_argument, NULL, ARG_CRASH_CHVT }, - { "crash-shell", optional_argument, NULL, ARG_CRASH_SHELL }, - { "crash-reboot", optional_argument, NULL, ARG_CRASH_REBOOT }, - { "confirm-spawn", optional_argument, NULL, ARG_CONFIRM_SPAWN }, - { "show-status", optional_argument, NULL, ARG_SHOW_STATUS }, - { "deserialize", required_argument, NULL, ARG_DESERIALIZE }, - { "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 }, - {} - }; - - int c, r; - - assert(argc >= 1); - assert(argv); - - if (getpid() == 1) - opterr = 0; - - while ((c = getopt_long(argc, argv, "hDbsz:", options, NULL)) >= 0) - - switch (c) { - - case ARG_LOG_LEVEL: - r = log_set_max_level_from_string(optarg); - if (r < 0) { - log_error("Failed to parse log level %s.", optarg); - return r; - } - - break; - - case ARG_LOG_TARGET: - r = log_set_target_from_string(optarg); - if (r < 0) { - log_error("Failed to parse log target %s.", optarg); - return r; - } - - break; - - case ARG_LOG_COLOR: - - if (optarg) { - r = log_show_color_from_string(optarg); - if (r < 0) { - log_error("Failed to parse log color setting %s.", optarg); - return r; - } - } else - log_show_color(true); - - break; - - case ARG_LOG_LOCATION: - if (optarg) { - r = log_show_location_from_string(optarg); - if (r < 0) { - log_error("Failed to parse log location setting %s.", optarg); - return r; - } - } else - log_show_location(true); - - break; - - case ARG_DEFAULT_STD_OUTPUT: - r = exec_output_from_string(optarg); - if (r < 0) { - log_error("Failed to parse default standard output setting %s.", optarg); - return r; - } else - arg_default_std_output = r; - break; - - case ARG_DEFAULT_STD_ERROR: - r = exec_output_from_string(optarg); - if (r < 0) { - log_error("Failed to parse default standard error output setting %s.", optarg); - return r; - } else - arg_default_std_error = r; - break; - - case ARG_UNIT: - - r = free_and_strdup(&arg_default_unit, optarg); - if (r < 0) - return log_error_errno(r, "Failed to set default unit %s: %m", optarg); - - break; - - case ARG_SYSTEM: - arg_running_as = MANAGER_SYSTEM; - break; - - case ARG_USER: - arg_running_as = MANAGER_USER; - break; - - case ARG_TEST: - arg_action = ACTION_TEST; - if (arg_no_pager < 0) - arg_no_pager = true; - break; - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case ARG_VERSION: - arg_action = ACTION_VERSION; - break; - - case ARG_DUMP_CONFIGURATION_ITEMS: - arg_action = ACTION_DUMP_CONFIGURATION_ITEMS; - break; - - case ARG_DUMP_CORE: - if (!optarg) - arg_dump_core = true; - else { - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse dump core boolean: %s", optarg); - arg_dump_core = r; - } - break; - - case ARG_CRASH_CHVT: - r = parse_crash_chvt(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse crash virtual terminal index: %s", optarg); - break; - - case ARG_CRASH_SHELL: - if (!optarg) - arg_crash_shell = true; - else { - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse crash shell boolean: %s", optarg); - arg_crash_shell = r; - } - break; - - case ARG_CRASH_REBOOT: - if (!optarg) - arg_crash_reboot = true; - else { - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse crash shell boolean: %s", optarg); - arg_crash_reboot = r; - } - break; - - case ARG_CONFIRM_SPAWN: - r = optarg ? parse_boolean(optarg) : 1; - if (r < 0) { - log_error("Failed to parse confirm spawn boolean %s.", optarg); - return r; - } - arg_confirm_spawn = r; - break; - - case ARG_SHOW_STATUS: - if (optarg) { - r = parse_show_status(optarg, &arg_show_status); - if (r < 0) { - log_error("Failed to parse show status boolean %s.", optarg); - return r; - } - } else - arg_show_status = SHOW_STATUS_YES; - break; - - case ARG_DESERIALIZE: { - int fd; - FILE *f; - - r = safe_atoi(optarg, &fd); - if (r < 0 || fd < 0) { - log_error("Failed to parse deserialize option %s.", optarg); - return -EINVAL; - } - - (void) fd_cloexec(fd, true); - - f = fdopen(fd, "r"); - if (!f) - return log_error_errno(errno, "Failed to open serialization fd: %m"); - - safe_fclose(arg_serialization); - arg_serialization = f; - - break; - } - - case ARG_SWITCHED_ROOT: - 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) - arg_no_pager = true; - break; - - case 'D': - log_set_max_level(LOG_DEBUG); - break; - - case 'b': - case 's': - case 'z': - /* Just to eat away the sysvinit kernel - * cmdline args without getopt() error - * messages that we'll parse in - * parse_proc_cmdline_word() or ignore. */ - - case '?': - if (getpid() != 1) - return -EINVAL; - else - return 0; - - default: - assert_not_reached("Unhandled option code."); - } - - if (optind < argc && getpid() != 1) { - /* Hmm, when we aren't run as init system - * let's complain about excess arguments */ - - log_error("Excess arguments."); - return -EINVAL; - } - - return 0; -} - -static int help(void) { - - printf("%s [OPTIONS...]\n\n" - "Starts up and maintains the system or user services.\n\n" - " -h --help Show this help\n" - " --test Determine startup sequence, dump it and exit\n" - " --no-pager Do not pipe output into a pager\n" - " --dump-configuration-items Dump understood unit configuration items\n" - " --unit=UNIT Set default unit\n" - " --system Run a system instance, even if PID != 1\n" - " --user Run a user instance\n" - " --dump-core[=BOOL] Dump core on crash\n" - " --crash-vt=NR Change to specified VT on crash\n" - " --crash-reboot[=BOOL] Reboot on crash\n" - " --crash-shell[=BOOL] Run shell on crash\n" - " --confirm-spawn[=BOOL] Ask for confirmation when spawning processes\n" - " --show-status[=BOOL] Show status updates on the console during bootup\n" - " --log-target=TARGET Set log target (console, journal, kmsg, journal-or-kmsg, null)\n" - " --log-level=LEVEL Set log level (debug, info, notice, warning, err, crit, alert, emerg)\n" - " --log-color[=BOOL] Highlight important log messages\n" - " --log-location[=BOOL] Include code location in log messages\n" - " --default-standard-output= Set default standard output for services\n" - " --default-standard-error= Set default standard error output for services\n", - program_invocation_short_name); - - return 0; -} - -static int prepare_reexecute(Manager *m, FILE **_f, FDSet **_fds, bool switching_root) { - _cleanup_fdset_free_ FDSet *fds = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(m); - assert(_f); - assert(_fds); - - r = manager_open_serialization(m, &f); - if (r < 0) - return log_error_errno(r, "Failed to create serialization file: %m"); - - /* Make sure nothing is really destructed when we shut down */ - m->n_reloading ++; - bus_manager_send_reloading(m, true); - - fds = fdset_new(); - if (!fds) - return log_oom(); - - r = manager_serialize(m, f, fds, switching_root); - if (r < 0) - return log_error_errno(r, "Failed to serialize state: %m"); - - if (fseeko(f, 0, SEEK_SET) == (off_t) -1) - return log_error_errno(errno, "Failed to rewind serialization fd: %m"); - - r = fd_cloexec(fileno(f), false); - if (r < 0) - return log_error_errno(r, "Failed to disable O_CLOEXEC for serialization: %m"); - - r = fdset_cloexec(fds, false); - if (r < 0) - return log_error_errno(r, "Failed to disable O_CLOEXEC for serialization fds: %m"); - - *_f = f; - *_fds = fds; - - f = NULL; - fds = NULL; - - return 0; -} - -static int bump_rlimit_nofile(struct rlimit *saved_rlimit) { - struct rlimit nl; - int r; - - assert(saved_rlimit); - - /* Save the original RLIMIT_NOFILE so that we can reset it - * later when transitioning from the initrd to the main - * systemd or suchlike. */ - if (getrlimit(RLIMIT_NOFILE, saved_rlimit) < 0) - return log_error_errno(errno, "Reading RLIMIT_NOFILE failed: %m"); - - /* Make sure forked processes get the default kernel setting */ - if (!arg_default_rlimit[RLIMIT_NOFILE]) { - struct rlimit *rl; - - rl = newdup(struct rlimit, saved_rlimit, 1); - if (!rl) - return log_oom(); - - arg_default_rlimit[RLIMIT_NOFILE] = rl; - } - - /* Bump up the resource limit for ourselves substantially */ - nl.rlim_cur = nl.rlim_max = 64*1024; - r = setrlimit_closest(RLIMIT_NOFILE, &nl); - if (r < 0) - return log_error_errno(r, "Setting RLIMIT_NOFILE failed: %m"); - - return 0; -} - -static void test_usr(void) { - - /* Check that /usr is not a separate fs */ - - if (dir_is_empty("/usr") <= 0) - return; - - log_warning("/usr appears to be on its own filesystem and is not already mounted. This is not a supported setup. " - "Some things will probably break (sometimes even silently) in mysterious ways. " - "Consult http://freedesktop.org/wiki/Software/systemd/separate-usr-is-broken for more information."); -} - -static int initialize_join_controllers(void) { - /* By default, mount "cpu" + "cpuacct" together, and "net_cls" - * + "net_prio". We'd like to add "cpuset" to the mix, but - * "cpuset" doesn't really work for groups with no initialized - * attributes. */ - - arg_join_controllers = new(char**, 3); - if (!arg_join_controllers) - return -ENOMEM; - - arg_join_controllers[0] = strv_new("cpu", "cpuacct", NULL); - if (!arg_join_controllers[0]) - goto oom; - - arg_join_controllers[1] = strv_new("net_cls", "net_prio", NULL); - if (!arg_join_controllers[1]) - goto oom; - - arg_join_controllers[2] = NULL; - return 0; - -oom: - arg_join_controllers = strv_free_free(arg_join_controllers); - return -ENOMEM; -} - -static int enforce_syscall_archs(Set *archs) { -#ifdef HAVE_SECCOMP - scmp_filter_ctx *seccomp; - Iterator i; - void *id; - int r; - - seccomp = seccomp_init(SCMP_ACT_ALLOW); - if (!seccomp) - return log_oom(); - - SET_FOREACH(id, arg_syscall_archs, i) { - r = seccomp_arch_add(seccomp, PTR_TO_UINT32(id) - 1); - if (r == -EEXIST) - continue; - if (r < 0) { - log_error_errno(r, "Failed to add architecture to seccomp: %m"); - goto finish; - } - } - - r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); - if (r < 0) { - log_error_errno(r, "Failed to unset NO_NEW_PRIVS: %m"); - goto finish; - } - - r = seccomp_load(seccomp); - if (r < 0) - log_error_errno(r, "Failed to add install architecture seccomp: %m"); - -finish: - seccomp_release(seccomp); - return r; -#else - return 0; -#endif -} - -static int status_welcome(void) { - _cleanup_free_ char *pretty_name = NULL, *ansi_color = NULL; - int r; - - r = parse_env_file("/etc/os-release", NEWLINE, - "PRETTY_NAME", &pretty_name, - "ANSI_COLOR", &ansi_color, - NULL); - if (r == -ENOENT) - r = parse_env_file("/usr/lib/os-release", NEWLINE, - "PRETTY_NAME", &pretty_name, - "ANSI_COLOR", &ansi_color, - NULL); - - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to read os-release file: %m"); - - return status_printf(NULL, false, false, - "\nWelcome to \x1B[%sm%s\x1B[0m!\n", - isempty(ansi_color) ? "1" : ansi_color, - isempty(pretty_name) ? "GNU/Linux" : pretty_name); -} - -static int write_container_id(void) { - const char *c; - int r; - - c = getenv("container"); - if (isempty(c)) - return 0; - - r = write_string_file("/run/systemd/container", c, WRITE_STRING_FILE_CREATE); - if (r < 0) - return log_warning_errno(r, "Failed to write /run/systemd/container, ignoring: %m"); - - return 1; -} - -static int bump_unix_max_dgram_qlen(void) { - _cleanup_free_ char *qlen = NULL; - unsigned long v; - int r; - - /* Let's bump the net.unix.max_dgram_qlen sysctl. The kernel - * default of 16 is simply too low. We set the value really - * really early during boot, so that it is actually applied to - * all our sockets, including the $NOTIFY_SOCKET one. */ - - r = read_one_line_file("/proc/sys/net/unix/max_dgram_qlen", &qlen); - if (r < 0) - return log_warning_errno(r, "Failed to read AF_UNIX datagram queue length, ignoring: %m"); - - r = safe_atolu(qlen, &v); - if (r < 0) - return log_warning_errno(r, "Failed to parse AF_UNIX datagram queue length, ignoring: %m"); - - if (v >= DEFAULT_UNIX_MAX_DGRAM_QLEN) - return 0; - - qlen = mfree(qlen); - if (asprintf(&qlen, "%lu\n", DEFAULT_UNIX_MAX_DGRAM_QLEN) < 0) - return log_oom(); - - r = write_string_file("/proc/sys/net/unix/max_dgram_qlen", qlen, 0); - if (r < 0) - return log_full_errno(IN_SET(r, -EROFS, -EPERM, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to bump AF_UNIX datagram queue length, ignoring: %m"); - - return 1; -} - -int main(int argc, char *argv[]) { - Manager *m = NULL; - int r, retval = EXIT_FAILURE; - usec_t before_startup, after_startup; - char timespan[FORMAT_TIMESPAN_MAX]; - FDSet *fds = NULL; - bool reexecute = false; - const char *shutdown_verb = NULL; - dual_timestamp initrd_timestamp = DUAL_TIMESTAMP_NULL; - dual_timestamp userspace_timestamp = DUAL_TIMESTAMP_NULL; - dual_timestamp kernel_timestamp = DUAL_TIMESTAMP_NULL; - dual_timestamp security_start_timestamp = DUAL_TIMESTAMP_NULL; - dual_timestamp security_finish_timestamp = DUAL_TIMESTAMP_NULL; - static char systemd[] = "systemd"; - bool skip_setup = false; - unsigned j; - bool loaded_policy = false; - bool arm_reboot_watchdog = false; - bool queue_default_job = false; - bool empty_etc = false; - char *switch_root_dir = NULL, *switch_root_init = NULL; - struct rlimit saved_rlimit_nofile = RLIMIT_MAKE_CONST(0); - const char *error_message = NULL; - -#ifdef HAVE_SYSV_COMPAT - if (getpid() != 1 && strstr(program_invocation_short_name, "init")) { - /* This is compatibility support for SysV, where - * calling init as a user is identical to telinit. */ - - errno = -ENOENT; - execv(SYSTEMCTL_BINARY_PATH, argv); - log_error_errno(errno, "Failed to exec " SYSTEMCTL_BINARY_PATH ": %m"); - return 1; - } -#endif - - dual_timestamp_from_monotonic(&kernel_timestamp, 0); - dual_timestamp_get(&userspace_timestamp); - - /* Determine if this is a reexecution or normal bootup. We do - * the full command line parsing much later, so let's just - * have a quick peek here. */ - if (strv_find(argv+1, "--deserialize")) - skip_setup = true; - - /* If we have switched root, do all the special setup - * things */ - if (strv_find(argv+1, "--switched-root")) - skip_setup = false; - - /* If we get started via the /sbin/init symlink then we are - called 'init'. After a subsequent reexecution we are then - called 'systemd'. That is confusing, hence let's call us - systemd right-away. */ - program_invocation_short_name = systemd; - prctl(PR_SET_NAME, systemd); - - saved_argv = argv; - saved_argc = argc; - - log_show_color(isatty(STDERR_FILENO) > 0); - log_set_upgrade_syslog_to_journal(true); - - /* Disable the umask logic */ - if (getpid() == 1) - umask(0); - - if (getpid() == 1 && detect_container() <= 0) { - - /* Running outside of a container as PID 1 */ - arg_running_as = MANAGER_SYSTEM; - make_null_stdio(); - log_set_target(LOG_TARGET_KMSG); - log_open(); - - if (in_initrd()) - initrd_timestamp = userspace_timestamp; - - if (!skip_setup) { - 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"; - goto finish; - } else if (ima_setup() < 0) { - error_message = "Failed to load IMA policy"; - goto finish; - } else if (mac_smack_setup(&loaded_policy) < 0) { - error_message = "Failed to load SMACK policy"; - goto finish; - } - dual_timestamp_get(&security_finish_timestamp); - } - - if (mac_selinux_init(NULL) < 0) { - error_message = "Failed to initialize SELinux policy"; - goto finish; - } - - if (!skip_setup) { - if (clock_is_localtime() > 0) { - int min; - - /* - * The very first call of settimeofday() also does a time warp in the kernel. - * - * In the rtc-in-local time mode, we set the kernel's timezone, and rely on - * external tools to take care of maintaining the RTC and do all adjustments. - * This matches the behavior of Windows, which leaves the RTC alone if the - * registry tells that the RTC runs in UTC. - */ - r = clock_set_timezone(&min); - if (r < 0) - log_error_errno(r, "Failed to apply local time delta, ignoring: %m"); - else - log_info("RTC configured in localtime, applying delta of %i minutes to system time.", min); - } else if (!in_initrd()) { - /* - * Do a dummy very first call to seal the kernel's time warp magic. - * - * Do not call this this from inside the initrd. The initrd might not - * carry /etc/adjtime with LOCAL, but the real system could be set up - * that way. In such case, we need to delay the time-warp or the sealing - * until we reach the real system. - * - * Do no set the kernel's timezone. The concept of local time cannot - * be supported reliably, the time will jump or be incorrect at every daylight - * saving time change. All kernel local time concepts will be treated - * as UTC that way. - */ - (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 - * open the logs like this for now. Note that if we - * are transitioning from the initrd there might still - * be journal fd open, and we shouldn't attempt - * opening that before we parsed /proc/cmdline which - * might redirect output elsewhere. */ - log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); - - } else if (getpid() == 1) { - /* Running inside a container, as PID 1 */ - arg_running_as = MANAGER_SYSTEM; - log_set_target(LOG_TARGET_CONSOLE); - log_close_console(); /* force reopen of /dev/console */ - log_open(); - - /* For the later on, see above... */ - log_set_target(LOG_TARGET_JOURNAL); - - /* clear the kernel timestamp, - * because we are in a container */ - kernel_timestamp.monotonic = 0ULL; - kernel_timestamp.realtime = 0ULL; - - } else { - /* Running as user instance */ - arg_running_as = MANAGER_USER; - log_set_target(LOG_TARGET_AUTO); - log_open(); - - /* clear the kernel timestamp, - * because we are not PID 1 */ - kernel_timestamp = DUAL_TIMESTAMP_NULL; - } - - if (getpid() == 1) { - /* Don't limit the core dump size, so that coredump handlers such as systemd-coredump (which honour the limit) - * will process core dumps for system services by default. */ - (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY)); - - /* But at the same time, turn off the core_pattern logic by default, so that no coredumps are stored - * until the systemd-coredump tool is enabled via sysctl. */ - if (!skip_setup) - (void) write_string_file("/proc/sys/kernel/core_pattern", "|/bin/false", 0); - } - - /* Initialize default unit */ - r = free_and_strdup(&arg_default_unit, SPECIAL_DEFAULT_TARGET); - if (r < 0) { - log_emergency_errno(r, "Failed to set default unit %s: %m", SPECIAL_DEFAULT_TARGET); - error_message = "Failed to set default unit"; - goto finish; - } - - r = initialize_join_controllers(); - if (r < 0) { - error_message = "Failed to initialize cgroup controllers"; - goto finish; - } - - /* Mount /proc, /sys and friends, so that /proc/cmdline and - * /proc/$PID/fd is available. */ - if (getpid() == 1) { - - /* Load the kernel modules early, so that we kdbus.ko is loaded before kdbusfs shall be mounted */ - if (!skip_setup) - kmod_setup(); - - r = mount_setup(loaded_policy); - if (r < 0) { - error_message = "Failed to mount API filesystems"; - goto finish; - } - } - - /* Reset all signal handlers. */ - (void) reset_all_signal_handlers(); - (void) ignore_signals(SIGNALS_IGNORE, -1); - - if (parse_config_file() < 0) { - error_message = "Failed to parse config file"; - goto finish; - } - - if (arg_running_as == MANAGER_SYSTEM) { - r = parse_proc_cmdline(parse_proc_cmdline_item); - if (r < 0) - log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - } - - /* Note that this also parses bits from the kernel command - * line, including "debug". */ - log_parse_environment(); - - if (parse_argv(argc, argv) < 0) { - error_message = "Failed to parse commandline arguments"; - goto finish; - } - - if (arg_action == ACTION_TEST && - geteuid() == 0) { - log_error("Don't run test mode as root."); - goto finish; - } - - if (arg_running_as == MANAGER_USER && - arg_action == ACTION_RUN && - sd_booted() <= 0) { - log_error("Trying to run as user instance, but the system has not been booted with systemd."); - goto finish; - } - - if (arg_running_as == MANAGER_SYSTEM && - arg_action == ACTION_RUN && - running_in_chroot() > 0) { - log_error("Cannot be run in a chroot() environment."); - goto finish; - } - - if (arg_action == ACTION_TEST) - skip_setup = true; - - pager_open_if_enabled(); - - if (arg_action == ACTION_HELP) { - retval = help(); - goto finish; - } else if (arg_action == ACTION_VERSION) { - retval = version(); - goto finish; - } else if (arg_action == ACTION_DUMP_CONFIGURATION_ITEMS) { - unit_dump_config_items(stdout); - retval = EXIT_SUCCESS; - goto finish; - } else if (arg_action == ACTION_DONE) { - retval = EXIT_SUCCESS; - goto finish; - } - - if (arg_running_as == MANAGER_USER && - !getenv("XDG_RUNTIME_DIR")) { - log_error("Trying to run as user instance, but $XDG_RUNTIME_DIR is not set."); - goto finish; - } - - assert_se(arg_action == ACTION_RUN || arg_action == ACTION_TEST); - - /* Close logging fds, in order not to confuse fdset below */ - log_close(); - - /* Remember open file descriptors for later deserialization */ - r = fdset_new_fill(&fds); - if (r < 0) { - log_emergency_errno(r, "Failed to allocate fd set: %m"); - error_message = "Failed to allocate fd set"; - goto finish; - } else - fdset_cloexec(fds, true); - - if (arg_serialization) - assert_se(fdset_remove(fds, fileno(arg_serialization)) >= 0); - - if (arg_running_as == MANAGER_SYSTEM) - /* Become a session leader if we aren't one yet. */ - setsid(); - - /* Move out of the way, so that we won't block unmounts */ - assert_se(chdir("/") == 0); - - /* Reset the console, but only if this is really init and we - * are freshly booted */ - if (arg_running_as == MANAGER_SYSTEM && arg_action == ACTION_RUN) { - - /* If we are init, we connect stdin/stdout/stderr to - * /dev/null and make sure we don't have a controlling - * tty. */ - release_terminal(); - - if (getpid() == 1 && !skip_setup) - console_setup(); - } - - /* Open the logging devices, if possible and necessary */ - log_open(); - - if (arg_show_status == _SHOW_STATUS_UNSET) - arg_show_status = SHOW_STATUS_YES; - - /* Make sure we leave a core dump without panicing the - * kernel. */ - if (getpid() == 1) { - install_crash_handler(); - - r = mount_cgroup_controllers(arg_join_controllers); - if (r < 0) - goto finish; - } - - if (arg_running_as == MANAGER_SYSTEM) { - int v; - - log_info(PACKAGE_STRING " running in %ssystem mode. (" SYSTEMD_FEATURES ")", - arg_action == ACTION_TEST ? "test " : "" ); - - v = detect_virtualization(); - if (v > 0) - log_info("Detected virtualization %s.", virtualization_to_string(v)); - - write_container_id(); - - log_info("Detected architecture %s.", architecture_to_string(uname_architecture())); - - if (in_initrd()) - log_info("Running in initial RAM disk."); - - /* Let's check whether /etc is already populated. We - * don't actually really check for that, but use - * /etc/machine-id as flag file. This allows container - * managers and installers to provision a couple of - * files already. If the container manager wants to - * provision the machine ID itself it should pass - * $container_uuid to PID 1. */ - - empty_etc = access("/etc/machine-id", F_OK) < 0; - if (empty_etc) - log_info("Running with unpopulated /etc."); - } else { - _cleanup_free_ char *t; - - t = uid_to_name(getuid()); - log_debug(PACKAGE_STRING " running in %suser mode for user "UID_FMT"/%s. (" SYSTEMD_FEATURES ")", - arg_action == ACTION_TEST ? " test" : "", getuid(), t); - } - - if (arg_running_as == MANAGER_SYSTEM && !skip_setup) { - if (arg_show_status > 0) - status_welcome(); - - hostname_setup(); - machine_id_setup(NULL, arg_machine_id); - loopback_setup(); - bump_unix_max_dgram_qlen(); - - test_usr(); - } - - if (arg_running_as == MANAGER_SYSTEM && arg_runtime_watchdog > 0) - watchdog_set_timeout(&arg_runtime_watchdog); - - if (arg_timer_slack_nsec != NSEC_INFINITY) - if (prctl(PR_SET_TIMERSLACK, arg_timer_slack_nsec) < 0) - log_error_errno(errno, "Failed to adjust timer slack: %m"); - - 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, true); - if (r < 0) { - log_emergency_errno(r, "Failed to drop capability bounding set: %m"); - error_message = "Failed to drop capability bounding set"; - goto finish; - } - } - - if (arg_syscall_archs) { - r = enforce_syscall_archs(arg_syscall_archs); - if (r < 0) { - error_message = "Failed to set syscall architectures"; - goto finish; - } - } - - if (arg_running_as == MANAGER_USER) - /* Become reaper of our children */ - if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) - log_warning_errno(errno, "Failed to make us a subreaper: %m"); - - if (arg_running_as == MANAGER_SYSTEM) { - bump_rlimit_nofile(&saved_rlimit_nofile); - - if (empty_etc) { - r = unit_file_preset_all(UNIT_FILE_SYSTEM, false, NULL, UNIT_FILE_PRESET_ENABLE_ONLY, false, NULL, 0); - if (r < 0) - log_full_errno(r == -EEXIST ? LOG_NOTICE : LOG_WARNING, r, "Failed to populate /etc with preset unit settings, ignoring: %m"); - else - log_info("Populated /etc with preset unit settings."); - } - } - - r = manager_new(arg_running_as, arg_action == ACTION_TEST, &m); - if (r < 0) { - log_emergency_errno(r, "Failed to allocate manager object: %m"); - error_message = "Failed to allocate manager object"; - goto finish; - } - - m->confirm_spawn = arg_confirm_spawn; - m->runtime_watchdog = arg_runtime_watchdog; - m->shutdown_watchdog = arg_shutdown_watchdog; - m->userspace_timestamp = userspace_timestamp; - m->kernel_timestamp = kernel_timestamp; - m->initrd_timestamp = initrd_timestamp; - m->security_start_timestamp = security_start_timestamp; - m->security_finish_timestamp = security_finish_timestamp; - - manager_set_defaults(m); - manager_set_show_status(m, arg_show_status); - manager_set_first_boot(m, empty_etc); - - /* Remember whether we should queue the default job */ - queue_default_job = !arg_serialization || arg_switched_root; - - before_startup = now(CLOCK_MONOTONIC); - - r = manager_startup(m, arg_serialization, fds); - if (r < 0) - log_error_errno(r, "Failed to fully start up daemon: %m"); - - /* This will close all file descriptors that were opened, but - * not claimed by any unit. */ - fds = fdset_free(fds); - - arg_serialization = safe_fclose(arg_serialization); - - if (queue_default_job) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - Unit *target = NULL; - Job *default_unit_job; - - log_debug("Activating default unit: %s", arg_default_unit); - - r = manager_load_unit(m, arg_default_unit, NULL, &error, &target); - if (r < 0) - log_error("Failed to load default target: %s", bus_error_message(&error, r)); - else if (target->load_state == UNIT_ERROR || target->load_state == UNIT_NOT_FOUND) - log_error_errno(target->load_error, "Failed to load default target: %m"); - else if (target->load_state == UNIT_MASKED) - log_error("Default target masked."); - - if (!target || target->load_state != UNIT_LOADED) { - log_info("Trying to load rescue target..."); - - r = manager_load_unit(m, SPECIAL_RESCUE_TARGET, NULL, &error, &target); - if (r < 0) { - log_emergency("Failed to load rescue target: %s", bus_error_message(&error, r)); - error_message = "Failed to load rescue target"; - goto finish; - } else if (target->load_state == UNIT_ERROR || target->load_state == UNIT_NOT_FOUND) { - log_emergency_errno(target->load_error, "Failed to load rescue target: %m"); - error_message = "Failed to load rescue target"; - goto finish; - } else if (target->load_state == UNIT_MASKED) { - log_emergency("Rescue target masked."); - error_message = "Rescue target masked"; - goto finish; - } - } - - assert(target->load_state == UNIT_LOADED); - - if (arg_action == ACTION_TEST) { - printf("-> By units:\n"); - manager_dump_units(m, stdout, "\t"); - } - - r = manager_add_job(m, JOB_START, target, JOB_ISOLATE, &error, &default_unit_job); - if (r == -EPERM) { - log_debug("Default target could not be isolated, starting instead: %s", bus_error_message(&error, r)); - - sd_bus_error_free(&error); - - r = manager_add_job(m, JOB_START, target, JOB_REPLACE, &error, &default_unit_job); - if (r < 0) { - log_emergency("Failed to start default target: %s", bus_error_message(&error, r)); - error_message = "Failed to start default target"; - goto finish; - } - } else if (r < 0) { - log_emergency("Failed to isolate default target: %s", bus_error_message(&error, r)); - error_message = "Failed to isolate default target"; - goto finish; - } - - m->default_unit_job_id = default_unit_job->id; - - after_startup = now(CLOCK_MONOTONIC); - log_full(arg_action == ACTION_TEST ? LOG_INFO : LOG_DEBUG, - "Loaded units and determined initial transaction in %s.", - format_timespan(timespan, sizeof(timespan), after_startup - before_startup, 100 * USEC_PER_MSEC)); - - if (arg_action == ACTION_TEST) { - printf("-> By jobs:\n"); - manager_dump_jobs(m, stdout, "\t"); - retval = EXIT_SUCCESS; - goto finish; - } - } - - for (;;) { - r = manager_loop(m); - if (r < 0) { - log_emergency_errno(r, "Failed to run main loop: %m"); - error_message = "Failed to run main loop"; - goto finish; - } - - switch (m->exit_code) { - - case MANAGER_RELOAD: - log_info("Reloading."); - - r = parse_config_file(); - if (r < 0) - log_error("Failed to parse config file."); - - manager_set_defaults(m); - - r = manager_reload(m); - if (r < 0) - log_error_errno(r, "Failed to reload: %m"); - break; - - case MANAGER_REEXECUTE: - - if (prepare_reexecute(m, &arg_serialization, &fds, false) < 0) { - error_message = "Failed to prepare for reexecution"; - goto finish; - } - - reexecute = true; - log_notice("Reexecuting."); - goto finish; - - case MANAGER_SWITCH_ROOT: - /* Steal the switch root parameters */ - switch_root_dir = m->switch_root; - switch_root_init = m->switch_root_init; - m->switch_root = m->switch_root_init = NULL; - - if (!switch_root_init) - if (prepare_reexecute(m, &arg_serialization, &fds, true) < 0) { - error_message = "Failed to prepare for reexecution"; - goto finish; - } - - reexecute = true; - log_notice("Switching root."); - goto finish; - - case MANAGER_EXIT: - retval = m->return_value; - - if (m->running_as == MANAGER_USER) { - log_debug("Exit."); - goto finish; - } - - /* fallthrough */ - case MANAGER_REBOOT: - case MANAGER_POWEROFF: - case MANAGER_HALT: - case MANAGER_KEXEC: { - static const char * const table[_MANAGER_EXIT_CODE_MAX] = { - [MANAGER_EXIT] = "exit", - [MANAGER_REBOOT] = "reboot", - [MANAGER_POWEROFF] = "poweroff", - [MANAGER_HALT] = "halt", - [MANAGER_KEXEC] = "kexec" - }; - - assert_se(shutdown_verb = table[m->exit_code]); - arm_reboot_watchdog = m->exit_code == MANAGER_REBOOT; - - log_notice("Shutting down."); - goto finish; - } - - default: - assert_not_reached("Unknown exit code."); - } - } - -finish: - pager_close(); - - if (m) - arg_shutdown_watchdog = m->shutdown_watchdog; - - m = manager_free(m); - - for (j = 0; j < ELEMENTSOF(arg_default_rlimit); j++) - arg_default_rlimit[j] = mfree(arg_default_rlimit[j]); - - arg_default_unit = mfree(arg_default_unit); - arg_join_controllers = strv_free_free(arg_join_controllers); - arg_default_environment = strv_free(arg_default_environment); - arg_syscall_archs = set_free(arg_syscall_archs); - - mac_selinux_finish(); - - if (reexecute) { - const char **args; - unsigned i, args_size; - - /* Close and disarm the watchdog, so that the new - * instance can reinitialize it, but doesn't get - * rebooted while we do that */ - watchdog_close(true); - - /* Reset the RLIMIT_NOFILE to the kernel default, so - * that the new systemd can pass the kernel default to - * its child processes */ - if (saved_rlimit_nofile.rlim_cur > 0) - (void) setrlimit(RLIMIT_NOFILE, &saved_rlimit_nofile); - - if (switch_root_dir) { - /* Kill all remaining processes from the - * initrd, but don't wait for them, so that we - * can handle the SIGCHLD for them after - * deserializing. */ - broadcast_signal(SIGTERM, false, true); - - /* And switch root with MS_MOVE, because we remove the old directory afterwards and detach it. */ - r = switch_root(switch_root_dir, "/mnt", true, MS_MOVE); - if (r < 0) - log_error_errno(r, "Failed to switch root, trying to continue: %m"); - } - - args_size = MAX(6, argc+1); - args = newa(const char*, args_size); - - if (!switch_root_init) { - char sfd[DECIMAL_STR_MAX(int) + 1]; - - /* First try to spawn ourselves with the right - * path, and with full serialization. We do - * this only if the user didn't specify an - * explicit init to spawn. */ - - assert(arg_serialization); - assert(fds); - - xsprintf(sfd, "%i", fileno(arg_serialization)); - - i = 0; - args[i++] = SYSTEMD_BINARY_PATH; - if (switch_root_dir) - args[i++] = "--switched-root"; - args[i++] = arg_running_as == MANAGER_SYSTEM ? "--system" : "--user"; - args[i++] = "--deserialize"; - args[i++] = sfd; - args[i++] = NULL; - - /* do not pass along the environment we inherit from the kernel or initrd */ - if (switch_root_dir) - (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); - } - - /* Try the fallback, if there is any, without any - * serialization. We pass the original argv[] and - * envp[]. (Well, modulo the ordering changes due to - * getopt() in argv[], and some cleanups in envp[], - * but let's hope that doesn't matter.) */ - - arg_serialization = safe_fclose(arg_serialization); - fds = fdset_free(fds); - - /* Reopen the console */ - (void) make_console_stdio(); - - for (j = 1, i = 1; j < (unsigned) argc; j++) - args[i++] = argv[j]; - args[i++] = NULL; - assert(i <= args_size); - - /* Reenable any blocked signals, especially important - * if we switch from initial ramdisk to init=... */ - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - if (switch_root_init) { - args[0] = switch_root_init; - (void) execv(args[0], (char* const*) args); - log_warning_errno(errno, "Failed to execute configured init, trying fallback: %m"); - } - - args[0] = "/sbin/init"; - (void) execv(args[0], (char* const*) args); - - if (errno == ENOENT) { - log_warning("No /sbin/init, trying fallback"); - - args[0] = "/bin/sh"; - args[1] = NULL; - (void) execv(args[0], (char* const*) args); - log_error_errno(errno, "Failed to execute /bin/sh, giving up: %m"); - } else - log_warning_errno(errno, "Failed to execute /sbin/init, giving up: %m"); - } - - arg_serialization = safe_fclose(arg_serialization); - fds = fdset_free(fds); - -#ifdef HAVE_VALGRIND_VALGRIND_H - /* If we are PID 1 and running under valgrind, then let's exit - * here explicitly. valgrind will only generate nice output on - * exit(), not on exec(), hence let's do the former not the - * latter here. */ - if (getpid() == 1 && RUNNING_ON_VALGRIND) - return 0; -#endif - - if (shutdown_verb) { - char log_level[DECIMAL_STR_MAX(int) + 1]; - char exit_code[DECIMAL_STR_MAX(uint8_t) + 1]; - const char* command_line[11] = { - SYSTEMD_SHUTDOWN_BINARY_PATH, - shutdown_verb, - "--log-level", log_level, - "--log-target", - }; - unsigned pos = 5; - _cleanup_strv_free_ char **env_block = NULL; - - assert(command_line[pos] == NULL); - env_block = strv_copy(environ); - - xsprintf(log_level, "%d", log_get_max_level()); - - switch (log_get_target()) { - - case LOG_TARGET_KMSG: - case LOG_TARGET_JOURNAL_OR_KMSG: - case LOG_TARGET_SYSLOG_OR_KMSG: - command_line[pos++] = "kmsg"; - break; - - case LOG_TARGET_NULL: - command_line[pos++] = "null"; - break; - - case LOG_TARGET_CONSOLE: - default: - command_line[pos++] = "console"; - break; - }; - - if (log_get_show_color()) - command_line[pos++] = "--log-color"; - - if (log_get_show_location()) - command_line[pos++] = "--log-location"; - - if (streq(shutdown_verb, "exit")) { - command_line[pos++] = "--exit-code"; - command_line[pos++] = exit_code; - xsprintf(exit_code, "%d", retval); - } - - assert(pos < ELEMENTSOF(command_line)); - - if (arm_reboot_watchdog && arg_shutdown_watchdog > 0) { - char *e; - - /* If we reboot let's set the shutdown - * watchdog and tell the shutdown binary to - * repeatedly ping it */ - r = watchdog_set_timeout(&arg_shutdown_watchdog); - watchdog_close(r < 0); - - /* Tell the binary how often to ping, ignore failure */ - if (asprintf(&e, "WATCHDOG_USEC="USEC_FMT, arg_shutdown_watchdog) > 0) - (void) strv_push(&env_block, e); - } else - watchdog_close(true); - - /* Avoid the creation of new processes forked by the - * kernel; at this point, we will not listen to the - * signals anyway */ - if (detect_container() <= 0) - (void) cg_uninstall_release_agent(SYSTEMD_CGROUP_CONTROLLER); - - execve(SYSTEMD_SHUTDOWN_BINARY_PATH, (char **) command_line, env_block); - log_error_errno(errno, "Failed to execute shutdown binary, %s: %m", - getpid() == 1 ? "freezing" : "quitting"); - } - - if (getpid() == 1) { - if (error_message) - manager_status_printf(NULL, STATUS_TYPE_EMERGENCY, - ANSI_HIGHLIGHT_RED "!!!!!!" ANSI_NORMAL, - "%s, freezing.", error_message); - freeze_or_reboot(); - } - - return retval; -} diff --git a/src/systemd/org.freedesktop.systemd1.conf b/src/systemd/org.freedesktop.systemd1.conf deleted file mode 100644 index 6a7a37ee92..0000000000 --- a/src/systemd/org.freedesktop.systemd1.conf +++ /dev/null @@ -1,212 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/systemd/org.freedesktop.systemd1.policy.in.in b/src/systemd/org.freedesktop.systemd1.policy.in.in deleted file mode 100644 index cc39a9e1c3..0000000000 --- a/src/systemd/org.freedesktop.systemd1.policy.in.in +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - The systemd Project - http://www.freedesktop.org/wiki/Software/systemd - - - <_description>Send passphrase back to system - <_message>Authentication is required to send the entered passphrase back to the system. - - no - no - auth_admin_keep - - @rootlibexecdir@/systemd-reply-password - - - - <_description>Manage system services or other units - <_message>Authentication is required to manage system services or other units. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Manage system service or unit files - <_message>Authentication is required to manage system service or unit files. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Set or unset system and service manager environment variables - <_message>Authentication is required to set or unset system and service manager environment variables. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Reload the systemd state - <_message>Authentication is required to reload the systemd state. - - auth_admin - auth_admin - auth_admin_keep - - - - diff --git a/src/systemd/org.freedesktop.systemd1.service b/src/systemd/org.freedesktop.systemd1.service deleted file mode 100644 index d4df3e93a2..0000000000 --- a/src/systemd/org.freedesktop.systemd1.service +++ /dev/null @@ -1,11 +0,0 @@ -# This file is part of systemd. -# -# 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. - -[D-BUS Service] -Name=org.freedesktop.systemd1 -Exec=/bin/false -User=root diff --git a/src/systemd/system.conf b/src/systemd/system.conf deleted file mode 100644 index e2ded27333..0000000000 --- a/src/systemd/system.conf +++ /dev/null @@ -1,60 +0,0 @@ -# This file is part of systemd. -# -# 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. -# -# Entries in this file show the compile time defaults. -# You can change settings by editing this file. -# Defaults can be restored by simply deleting this file. -# -# See systemd-system.conf(5) for details. - -[Manager] -#LogLevel=info -#LogTarget=journal-or-kmsg -#LogColor=yes -#LogLocation=no -#DumpCore=yes -#ShowStatus=yes -#CrashChangeVT=no -#CrashShell=no -#CrashReboot=no -#CPUAffinity=1 2 -#JoinControllers=cpu,cpuacct net_cls,net_prio -#RuntimeWatchdogSec=0 -#ShutdownWatchdogSec=10min -#CapabilityBoundingSet= -#SystemCallArchitectures= -#TimerSlackNSec= -#DefaultTimerAccuracySec=1min -#DefaultStandardOutput=journal -#DefaultStandardError=inherit -#DefaultTimeoutStartSec=90s -#DefaultTimeoutStopSec=90s -#DefaultRestartSec=100ms -#DefaultStartLimitInterval=10s -#DefaultStartLimitBurst=5 -#DefaultEnvironment= -#DefaultCPUAccounting=no -#DefaultBlockIOAccounting=no -#DefaultMemoryAccounting=no -#DefaultTasksAccounting=yes -#DefaultTasksMax=512 -#DefaultLimitCPU= -#DefaultLimitFSIZE= -#DefaultLimitDATA= -#DefaultLimitSTACK= -#DefaultLimitCORE= -#DefaultLimitRSS= -#DefaultLimitNOFILE= -#DefaultLimitAS= -#DefaultLimitNPROC= -#DefaultLimitMEMLOCK= -#DefaultLimitLOCKS= -#DefaultLimitSIGPENDING= -#DefaultLimitMSGQUEUE= -#DefaultLimitNICE= -#DefaultLimitRTPRIO= -#DefaultLimitRTTIME= diff --git a/src/systemd/systemd.pc.in b/src/systemd/systemd.pc.in deleted file mode 100644 index ac52b30dd3..0000000000 --- a/src/systemd/systemd.pc.in +++ /dev/null @@ -1,34 +0,0 @@ -# This file is part of systemd. -# -# 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. - -prefix=@prefix@ -systemdutildir=@rootlibexecdir@ -systemdsystemunitdir=@systemunitdir@ -systemdsystempresetdir=@systempresetdir@ -systemduserunitdir=@userunitdir@ -systemduserpresetdir=@userpresetdir@ -systemdsystemconfdir=@pkgsysconfdir@/system -systemduserconfdir=@pkgsysconfdir@/user -systemdsystemunitpath=${systemdsystemconfdir}:/etc/systemd/system:/run/systemd/system:/usr/local/lib/systemd/system:${systemdsystemunitdir}:/usr/lib/systemd/system:/lib/systemd/system -systemduserunitpath=${systemduserconfdir}:/etc/systemd/user:/run/systemd/user:/usr/local/lib/systemd/user:/usr/local/share/systemd/user:${systemduserunitdir}:/usr/lib/systemd/user:/usr/share/systemd/user -systemdsystemgeneratordir=@systemgeneratordir@ -systemdusergeneratordir=@usergeneratordir@ -systemdsleepdir=@systemsleepdir@ -systemdshutdowndir=@systemshutdowndir@ -tmpfilesdir=@tmpfilesdir@ -sysusersdir=@sysusersdir@ -sysctldir=@sysctldir@ -binfmtdir=@binfmtdir@ -modulesloaddir=@modulesloaddir@ -catalogdir=@catalogdir@ -systemuidmax=@systemuidmax@ -systemgidmax=@systemgidmax@ - -Name: systemd -Description: systemd System and Service Manager -URL: @PACKAGE_URL@ -Version: @PACKAGE_VERSION@ diff --git a/src/systemd/triggers.systemd.in b/src/systemd/triggers.systemd.in deleted file mode 100644 index 9e18a39a67..0000000000 --- a/src/systemd/triggers.systemd.in +++ /dev/null @@ -1,64 +0,0 @@ -# -*- Mode: rpm-spec; indent-tabs-mode: nil -*- */ -# -# This file is part of systemd. -# -# Copyright 2015 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 . - -# The contents of this are an example to be copied into systemd.spec. - -%transfiletriggerin -P 900900 -p -- @systemunitdir@ /etc/systemd/system --- This script will run after any package is initially installed or --- upgraded. We care about the case where a package is initially --- installed, because other cases are covered by the *un scriptlets, --- so sometimes we will reload needlessly. - -pid = posix.fork() -if pid == 0 then - assert(posix.exec("%{_bindir}/systemctl", "daemon-reload")) -elseif pid > 0 then - posix.wait(pid) -end - -%transfiletriggerun -p -- @systemunitdir@ /etc/systemd/system --- On removal, we need to run daemon-reload after any units have been --- removed. %transfiletriggerpostun would be ideal, but it does not get --- executed for some reason. --- On upgrade, we need to run daemon-reload after any new unit files --- have been installed, but before %postun scripts in packages get --- executed. %transfiletriggerun gets the right list of files --- but it is invoked too early (before changes happen). --- %filetriggerpostun happens at the right time, but it fires for --- every package. --- To execute the reload at the right time, we create a state --- file in %transfiletriggerun and execute the daemon-reload in --- the first %filetriggerpostun. - -posix.mkdir("%{_localstatedir}/lib") -posix.mkdir("%{_localstatedir}/lib/rpm-state") -posix.mkdir("%{_localstatedir}/lib/rpm-state/systemd") -io.open("%{_localstatedir}/lib/rpm-state/systemd/needs-reload", "w") - -%filetriggerpostun -P 1000100 -p -- @systemunitdir@ /etc/systemd/system -if posix.access("%{_localstatedir}/lib/rpm-state/systemd/needs-reload") then - posix.unlink("%{_localstatedir}/lib/rpm-state/systemd/needs-reload") - posix.rmdir("%{_localstatedir}/lib/rpm-state/systemd") - pid = posix.fork() - if pid == 0 then - assert(posix.exec("%{_bindir}/systemctl", "daemon-reload")) - elseif pid > 0 then - posix.wait(pid) - end -end diff --git a/src/systemd/user.conf b/src/systemd/user.conf deleted file mode 100644 index 87c8164378..0000000000 --- a/src/systemd/user.conf +++ /dev/null @@ -1,44 +0,0 @@ -# This file is part of systemd. -# -# 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. -# -# You can override the directives in this file by creating files in -# /etc/systemd/user.conf.d/*.conf. -# -# See systemd-user.conf(5) for details - -[Manager] -#LogLevel=info -#LogTarget=console -#LogColor=yes -#LogLocation=no -#SystemCallArchitectures= -#TimerSlackNSec= -#DefaultTimerAccuracySec=1min -#DefaultStandardOutput=inherit -#DefaultStandardError=inherit -#DefaultTimeoutStartSec=90s -#DefaultTimeoutStopSec=90s -#DefaultRestartSec=100ms -#DefaultStartLimitInterval=10s -#DefaultStartLimitBurst=5 -#DefaultEnvironment= -#DefaultLimitCPU= -#DefaultLimitFSIZE= -#DefaultLimitDATA= -#DefaultLimitSTACK= -#DefaultLimitCORE= -#DefaultLimitRSS= -#DefaultLimitNOFILE= -#DefaultLimitAS= -#DefaultLimitNPROC= -#DefaultLimitMEMLOCK= -#DefaultLimitLOCKS= -#DefaultLimitSIGPENDING= -#DefaultLimitMSGQUEUE= -#DefaultLimitNICE= -#DefaultLimitRTPRIO= -#DefaultLimitRTTIME= -- cgit v1.2.3